SlideShare a Scribd company logo
2D Platformer遊戲
Revised on January 1, 2020
 背景動畫與背景音樂
 玩家角色控制
 隨機生成敵人
 空投道具
 使用道具
 顯示分數
 暫停遊戲控制
 建立2D專案,my2DPlatformer
 選單命令File> Save As…,將預設場景另存新檔
 Exercise.unity
 調整Main Camera
 Position(X, Y, Z) = (-4.43, -0.15, -10)
 Background(R, G, B, A) = (163, 187, 196, 5)
 Size = 11
 Free Aspect
 滙入遊戲資源
 選單命令Assets> Import Package> Custom Package…,滙入
platformer2d.unitypackage
建立專案 1/3
2
 新增Tags
 ground、Crate、Enemy、Wall、Obstacle、
Bullet、BombPickup、ExplosionFX、
HealthBar
 新增Sorting Layers (遊戲圖層)
 Background、Character、Foreground、UI
 新增Layers (碰撞管理圖層)
 Bombs、Player、Enemies、Pickups、
Ground
建立專案 2/3
3
 選單命令Edit> Project Settings…
 Physics 2D Layer Collicion Matrix
建立專案 3/3
4
 將Assets/Prefebs/Environment/backgrounds.prefab加到場景
 Sorting Layer = Background
 設定所有子物件Sorting Layer = Background
 將Assets/Prefabs/Environment/Foregrounds.prefab加到場景
 設定所有子物件Sorting Layer = Foreground
建立場景
5
 選取Prefabs/Props/swan.prefab
 Sorting Layer = Background
 選取Prefabs/Environment/Cab.prefab
 Sorting Layer = Background
 Wheels子物件
 Sorting Layer = Background
 選取Prefabs/Environment/Bus.prefab
 Sorting Layer = Background
 Wheels子物件
 Sorting Layer = Background
設定背景動畫預製物件
6
 在_Scripts目錄下新增BackgroundPropSpawner.cs程式腳本
using UnityEngine;
using System.Collections;
public class BackgroundPropSpawner : MonoBehaviour {
public Rigidbody2D backgroundProp; //待生成的背景預製物件
public float leftSpawnPosX; //左側生成位置x座標
public float rightSpawnPosX; //右側生成位置x座標
public float minSpawnPosY; //生成位置y座標區間最小值
public float maxSpawnPosY; //生成位置y座標區間最大值
public float minTimeBetweenSpawns; //生成間隔時間最小值
public float maxTimeBetweenSpawns; //生成間隔時間最大值
public float minSpeed; //最小移動速度
public float maxSpeed; //移動速度最大值
void Start () {
Random.InitState(System.DateTime.Today.Millisecond);
StartCoroutine("Spawn"); //起始Spawn程序
}
隨機產生背景動畫物件 1/6
7
IEnumerator Spawn () {
float waitTime = Random.Range(minTimeBetweenSpawns, maxTimeBetweenSpawns);
yield return new WaitForSeconds(waitTime); //隨機等待一段時間
bool facingLeft = Random.Range(0,2) == 0; //隨機設定預製物件方向
float posX = facingLeft ? rightSpawnPosX : leftSpawnPosX;
float posY = Random.Range(minSpawnPosY, maxSpawnPosY);
Vector3 spawnPos = new Vector3(posX, posY, transform.position.z);
Rigidbody2D propInstance =
Instantiate(backgroundProp, spawnPos, Quaternion.identity) as Rigidbody2D;
if (!facingLeft) {
Vector3 scale = propInstance.transform.localScale;
scale.x *= -1;
propInstance.transform.localScale = scale;
}
float speed = Random.Range(minSpeed, maxSpeed); //隨機設定速度
speed *= facingLeft ? -1f : 1f;
propInstance.velocity = new Vector2(speed, 0);
StartCoroutine(Spawn());
隨機產生背景動畫物件 2/6
8
while (propInstance != null) {
if (facingLeft) {
if (propInstance.transform.position.x < leftSpawnPosX - 0.5f)
Destroy(propInstance.gameObject);
}
else {
if (propInstance.transform.position.x > rightSpawnPosX + 0.5f)
Destroy(propInstance.gameObject);
}
yield return null;
}
}
}
隨機產生背景動畫物件 3/6
9
 新增空物件,命名為swanCreator
 重置Transform
 加入BackgroundPropSpawner.cs程式腳本
 拖曳Prefabs/Props/swan.prefab到Prop欄
 Left Spawn Pos X = -24
 Right Spawn Pos X = 24
 Min Spawn Pos Y = 4
 Max Spawn Pos Y = 8
 Min Time Between Spawns = 2
 Max Time Between Spawns = 8
 Min Speed = 5
 Max Speed = 8
 測試遊戲,天空會隨機有天鵝飛過
隨機產生背景動畫物件 4/6
10
 新增空物件,命名為busCreator
 重置Transform
 加入BackgroundPropSpawner.cs程式腳本
 拖曳Prefabs/Environment/Bus.prefab到Prop欄
 Left Spawn Pos X = -24
 Right Spawn Pos X = 24
 Min Spawn Pos Y = -5.5
 Max Spawn Pos Y = -5.5
 Min Time Between Spawns = 8
 Max Time Between Spawns = 18
 Min Speed = 5
 Max Speed = 8
 測試遊戲,街道會隨機有巴士通過
隨機產生背景動畫物件 5/6
11
 新增空物件,命名為cabCreator
 重置Transform
 加入BackgroundPropSpawner.cs程式腳本
 拖曳Prefabs/Environment/Cab.prefab到Prop欄
 Left Spawn Pos X = -24
 Right Spawn Pos X = 24
 Min Spawn Pos Y = -6.4
 Max Spawn Pos Y = -6.4
 Min Time Between Spawns = 10
 Max Time Between Spawns = 15
 Min Speed = 5
 Max Speed = 8
 測試遊戲,街道會隨機有計程車通過
隨機產生背景動畫物件 6/6
12
 新增空物件,命名為music
 重置Transform
 加入AudioSource元件
 AudioClip = MainTheme
 勾選Play On Awake
 勾選Loop
 Volume = 0.1
 Reverb Zone Mix = 0
 測試專案,遊戲執行時會持續撥放背景音樂
加入背景音樂
13
 將Assets/Prefabs/Chractera/Mr.Bean.prefab加倒場景
 設定所有子物件Sorting Layer = Character
 Tag = Player
 Layer = Player
建立玩家角色
14
 Mr.Bean動作控制器
玩家角色控制 1/5
15
 在Mr.Bean物件加入PlayerControl.cs程式腳本
using UnityEngine;
using System.Collections;
public class PlayerControl : MonoBehaviour {
[HideInInspector]
public bool facingRight = true; //方向旗號,不顯示在屬性窗格,但可提其它程式腳本存取
[HideInInspector]
public bool jump = false; //跳躍旗號,不顯示在屬性窗格,但可提其它程式腳本存取
public float moveForce = 365f; //行進力道
public float maxSpeed = 5f; //移動速度上限
public AudioClip[] jumpClips; //跳躍動作音效庫
public float jumpForce = 1000f; //跳躍力道
private Transform groundCheck; //用來檢查玩家角色是否站在地面
private bool grounded = false; //落地旗號
private Animator anim; //玩家角色動作控制器參照
玩家角色控制 2/5
16
void Awake() {
groundCheck = transform.Find("groundCheck");
anim = GetComponent<Animator>();
}
void Update() {
grounded = Physics2D.Linecast(transform.position,
groundCheck.position, 1 << LayerMask.NameToLayer("Ground"));
//玩家角色位於在地面,並按下Jump鍵
if (Input.GetButtonDown("Jump") && grounded)
jump = true; //執行跳躍動作
}
玩家角色控制 3/5
17
void FixedUpdate () {
float h = Input.GetAxis("Horizontal");
anim.SetFloat("Speed", Mathf.Abs(h));
if (h * GetComponent<Rigidbody2D>().velocity.x < maxSpeed)
GetComponent<Rigidbody2D>().AddForce(Vector2.right * h * moveForce); //加速
if (Mathf.Abs(GetComponent<Rigidbody2D>().velocity.x) > maxSpeed)
GetComponent<Rigidbody2D>().velocity =
new Vector2(Mathf.Sign(GetComponent<Rigidbody2D>().velocity.x) * maxSpeed,
GetComponent<Rigidbody2D>().velocity.y);
if (h > 0 && !facingRight) Flip(); //反轉方向
else if (h < 0 && facingRight) Flip(); //反轉方向
if (jump) {
anim.SetTrigger("Jump");
int i = Random.Range(0, jumpClips.Length); //隨機撥放跳躍音效
AudioSource.PlayClipAtPoint(jumpClips[i], transform.position);
GetComponent<Rigidbody2D>().AddForce(new Vector2(0f, jumpForce)); //跳躍
jump = false; //防止重複跳躍
}
}
玩家角色控制 4/5
18
void Flip () {
facingRight = !facingRight;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
}
 拖曳Audio/Player/Jumps/Player-
jump[1-3].wav到Jump Clips欄
 測試遊戲,可操控主角移動
 適度調整移動速度及跳躍力道
玩家角色控制 5/5
19
 新增空物件,命名為healthUI
 Tag = HealthBar
 Position(X, Y, Z) = (0, 100, 0)
 在healthUI下新增Sprite物件,命名為HealthBar
 Position(X, Y, Z) = (-0.83, 0, 0)
 拖曳Assets/Sprites/_UI/Health.png到Sprite Renderer之Sprite欄
 拖曳Assets/Materials/Health.mat到Sprite Renderer之Material欄
 Sorting Layer = Foreground
建立玩家生命條 1/4
20
 在healthUI下新增Sprite物件,命名為HealthOutline
 Position(X, Y, Z) = (0, 0, 0)
 拖曳Assets/Sprites/_UI/Health-bg.png到Sprite Renderer之
Sprite欄
 拖曳Assets/Materials/DefaultPixelSnap.mat到Sprite Renderer
之Material欄
 Sorting Layer = Foreground
建立玩家生命條 2/4
21
 在healthUI加入FollowPlayer.cs程式腳本
using UnityEngine;
using System.Collections;
public class FollowPlayer : MonoBehaviour {
public Vector3 offset; //顯示位置偏移值
private Transform player;
void Awake () {
player = GameObject.FindGameObjectWithTag("Player").transform;
}
void Update () {
transform.position = player.position + offset;
}
}
建立玩家生命條 3/4
22
 設定healthUI之FollowPlayer
 Offset(X, Y, Z) = (0, 1.2, 0)
 測試遊戲,玩家角色上方會顯示生命條並且跟隨移動
建立玩家生命條 4/4
23
 在Mr.Bean物件加入PlayerHealth.cs程式腳本
using UnityEngine;
using System.Collections;
public class PlayerHealth : MonoBehaviour {
public float health = 100f; //玩家生命值
public float repeatDamagePeriod = 1f; //玩家連續受傷之時時間隔
public AudioClip[] ouchClips; //玩家受傷音效
public float hurtForce = 100f; //玩家受傷時受到之推力
public float damageAmount = 10f; //每次受傷之損血值
private SpriteRenderer healthBar; //玩家生命條之sprite renderer參照
private float lastHitTime; //前次受傷時間戳記
private Vector3 healthScale; //生命條滿���時之大小
private PlayerControl playerControl; //玩家PlayerControl程式腳本參照
private Animator anim; //玩家動作控制器參照
玩家生命值管理 1/4
24
void Awake () {
playerControl = GetComponent<PlayerControl>(); //玩家PlayerControl參照
healthBar = GameObject.Find("HealthBar").GetComponent<SpriteRenderer>();
anim = GetComponent<Animator>(); //玩家動作控制器參照
healthScale = healthBar.transform.localScale; //記錄生命條滿格之大小
}
void OnCollisionEnter2D (Collision2D col) {
if (col.gameObject.tag == "Enemy") { //敵人撞到玩家
if (Time.time > lastHitTime + repeatDamagePeriod) { //已達連續受傷之時間間隔
if (health > 0f) {
TakeDamage(col.transform); //使玩家損血
lastHitTime = Time.time; //記錄受傷時間
}
else { //玩家死亡,使玩家摔落河裡
Collider2D[] cols = GetComponents<Collider2D>();
foreach (Collider2D c in cols) { //將所有Collider2D調整為觸發器
c.isTrigger = true;
}
SpriteRenderer[] spr = GetComponentsInChildren<SpriteRenderer>();
玩家生命值管理 2/4
25
foreach(SpriteRenderer s in spr) { //將玩家角色移到UI圖層
s.sortingLayerName = "UI";
}
GetComponent<PlayerControl>().enabled = false; //停止PlayerControl程式腳本
GetComponentInChildren<Gun>().enabled = false; //停止Gun程式腳本
anim.SetTrigger("Die"); //觸發Die動畫
}
}
}
}
void TakeDamage (Transform enemy) {
playerControl.jump = false; //停止跳躍
Vector3 hurtVector = transform.position - enemy.position + Vector3.up * 5f;
GetComponent<Rigidbody2D>().AddForce(hurtVector * hurtForce);//向玩家施加衝撞力
health -= damageAmount; //扣減玩家血量
UpdateHealthBar(); //更新顯示生命條
int i = Random.Range (0, ouchClips.Length); //隨機撥放玩家受傷音效
AudioSource.PlayClipAtPoint(ouchClips[i], transform.position);
}
玩家生命值管理 3/4
26
public void UpdateHealthBar () {
//根據玩家的生命值,將生命條的顏色設置為綠色和紅色之間的比例
healthBar.material.color = Color.Lerp(Color.green, Color.red, 1 - health * 0.01f);
//根據玩家的生命值,調整生命條的寬度
healthBar.transform.localScale = new Vector3(healthScale.x * health * 0.01f, 1, 1);
}
}
 拖曳Assets/Audio/Player/Ouch/Player-ouch[1-4].wav到Ouch
Clips欄
玩家生命值管理 4/4
27
攝影機跟隨玩家移動 1/4
28
場景活動範圍
攝影機可視範圍
 在Main Camera加入CameraFollow.cs程式腳本
using UnityEngine;
using System.Collections;
public class CameraFollow : MonoBehaviour {
public float xMargin = 1f; //攝影機啟動跟隨前允許玩家移動的水平位移
public float yMargin = 1f; //攝影機啟動跟隨前允許玩家移動的垂直位移
public float xSmooth = 8f; //使相機平穩地捕捉目標運動之X軸修正值
public float ySmooth = 8f; //使相機平穩地捕捉目標運動之Y軸修正值
public Vector2 maxXAndY; //攝影機位置X與Y座標最大值
public Vector2 minXAndY; //攝影機位置X與Y座標最小值
private Transform player; //玩家角色transform屬性
void Awake () {
player = GameObject.FindGameObjectWithTag("Player").transform;
}
bool CheckXMargin() {
return Mathf.Abs(transform.position.x - player.position.x) > xMargin;
}
攝影機跟隨玩家移動 2/4
29
bool CheckYMargin() {
return Mathf.Abs(transform.position.y - player.position.y) > yMargin;
}
void FixedUpdate () {
TrackPlayer();
}
void TrackPlayer () {
float targetX = transform.position.x;
float targetY = transform.position.y;
if (CheckXMargin())
targetX = Mathf.Lerp(transform.position.x,
player.position.x, xSmooth * Time.deltaTime);
if (CheckYMargin())
targetY = Mathf.Lerp(transform.position.y,
player.position.y, ySmooth * Time.deltaTime);
targetX = Mathf.Clamp(targetX, minXAndY.x, maxXAndY.x);
targetY = Mathf.Clamp(targetY, minXAndY.y, maxXAndY.y);
transform.position = new Vector3(targetX, targetY, transform.position.z);
}
}
攝影機跟隨玩家移動 3/4
30
 設定Camera Follow參數
 X Margin = 2,Y Margin = 2
 X Smooth = 2,Y Smooth = 2
 Max(X, Y) = (5, 5)
 Min(X, Y) = (-5, -5)
 測試遊戲,玩家水平或垂直位移超過2時,攝影機就會自動跟隨
攝影機跟隨玩家移動 4/4
31
 選取Assets/Prefabs/Props/rocket.prefab
 子物件Sorting Layer = Character
 在Mr.Bean的Gun子物件加入Gun.cs腳本
using UnityEngine;
using System.Collections;
public class Gun : MonoBehaviour {
public Rigidbody2D rocket; //rocket預製物件
public float speed = 25f; //rocket速度
private PlayerControl playerCtrl; //PlayerControl程式腳本參照
private Animator anim; //角色動畫控制器參照
void Awake() {
anim = transform.root.gameObject.GetComponent<Animator>();
playerCtrl = transform.root.GetComponent<PlayerControl>();
}
Mr.Bean射擊控制 1/3
32
void Update () {
if (Input.GetButtonDown("Fire1")) { //按下發射鍵
anim.SetTrigger("Shoot");
GetComponent<AudioSource>().Play();
if (playerCtrl.facingRight) { //向右發射
Rigidbody2D bulletInstance = Instantiate(rocket, transform.position,
Quaternion.Euler(new Vector3(0, 0, 0))) as Rigidbody2D;
bulletInstance.velocity = new Vector2(speed, 0);
}
else { //向左發射
Rigidbody2D bulletInstance = Instantiate(rocket, transform.position,
Quaternion.Euler(new Vector3(0, 0, 180f))) as Rigidbody2D;
bulletInstance.velocity = new Vector2(-speed, 0);
}
}
}
}
Mr.Bean射擊控制 2/3
33
 拖曳Assets/Prefabs/Props/rocket.prefab到Gun腳本的Rocket
資料欄
 選取Assets/Prefabs/Props/rocketExplosion.prefab
 Sorting Layer = Foreground
 測試遊戲,點擊滑鼠左鍵可發射火箭彈
Mr.Bean射擊控制 3/3
34
 選單命令GameObject> UI> Text,命名為Score
 Anchor Presets = top, center
 Pos(X, Y, Z) = (0, -10, 0)
 Width = 500, Height = 100
 Pivot(X, Y) = (0.5, 1)
 Text = Score
 Font = BradBunR
 Font Size = 80
 Alignment = Center
 Color = white
加入計分板 1/5
35
 在Score加入Score.cs程式腳本
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class Score : MonoBehaviour {
public int score = 0; //玩家分數值
void Update () {
GetComponent<Text>().text = "Score: " + score; //更新顯示分數
}
}
加入計分板 2/5
36
 選單命令GameObject> UI> Text,命名為Score-shadow
 Anchor Presets = top, center
 Pos(X, Y, Z) = (0, -14, 0)
 Width = 500, Height = 100
 Pivot(X, Y) = (0.5, 1)
 Text = Score
 Font = BradBunR
 Font Size = 80
 Alignment = Center
 Color = black
加入計分板 3/5
37
 在Score-shadow加入ScoreShadow.cs程式腳本
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class ScoreShadow : MonoBehaviour {
public GameObject guiCopy; // Score物件參照
void Awake () {
Vector3 behindPos = transform.position;
behindPos = new Vector3(guiCopy.transform.position.x,
guiCopy.transform.position.y - 4f,
guiCopy.transform.position.z);
transform.position = behindPos;
}
void Update () {
GetComponent<Text>().text = guiCopy.GetComponent<Text>().text; //同步更新資料
}
}
加入計分板 4/5
38
 拖曳Score到Score-shadow之Gui Copy欄
 調整Hierarhy窗格中物件順序,使Score位於Score-shadow下方
 測試遊戲,場景中央上方會顯示陰影效果的得分值
加入計分板 5/5
39
 選取Assets/Prefabs/Characters/enemy1.prefab
 子物件Sorting Layer = Character
 選取Assets/Prefabs/Characters/enemy2.prefab
 子物件Sorting Layer = Character
隨機生成敵人 1/5
40
 選單命令GameObject> Create Empty建立空物件,命名為
EnemySpawners
 重置Transform
 Position(X,Y,Z) = (0, 15, 0)
 選單命令GameObject> Create Empty Child,在EnemySpawners
建立⼀個空子物件,命名為MidSpawner
 重置Transform
 Position(X,Y,Z) = (0.27, 0, 0)
 在MidSpawner加入EnemySpawner.cs腳本
隨機生成敵人 2/5
41
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemySpawner : MonoBehaviour {
public float spawnTime = 5f; //敵人生成間隔時間
public float spawnDelay = 3f; //延遲時間後才開始生成
public GameObject[] enemies; //敵人預製物件庫
void Start () {
InvokeRepeating("Spawn", spawnDelay, spawnTime);
}
void Spawn () {
int enemyIndex = Random.Range(0, enemies.Length);
Instantiate(enemies[enemyIndex], transform.position, transform.rotation);
}
}
隨機生成敵人 3/5
42
 拖曳Assets/Prefabs/Characters/enemy[1~2].prefab到
EnemySpawner腳本的Enemies資料欄
隨機生成敵人 4/5
43
 複製2份MidSpawner,更名為LeftSpawner及RightSpawner
 重置Transform
 LeftSpawner Position(X,Y,Z) = (-13.8, 0, 0)
 RightSpawner Position(X,Y,Z) = (14.5, 0, 0)
 選取Assets/Prefabs/UI/ui_100points.prefab
 子物件Sorting Layer = UI
 測試遊戲
 碰到敵人會損血,擊斃敵人會得分
 玩家及敵人掉落河裡後,並不會被銷毀
隨機生成敵人 5/5
44
 選單命令GameObject> Create Empty建立空物件,命名Destroyer
 Position(X, Y, Z) = (0.54, -13.5, 0)
 Scale(X, Y, Z) = (1.9, 1, 1)
 加入Box Collider 2D
 勾選Is Trigger
 Size(X, Y) = (23, 1.9)
角色溺水作業 1/4
45
 在Destroyer物件加入Remover.cs程式腳本
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
public class Remover : MonoBehaviour {
public GameObject splash; //水花噴濺特效預製物件
void OnTriggerEnter2D(Collider2D col) {
if (col.gameObject.tag == "Player") { //玩家掉落河裡
GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraFollow>()
.enabled = false;
GameObject.FindGameObjectWithTag("HealthBar").GetComponent<FollowPlayer>().
enabled = false;
Instantiate(splash, col.transform.position, transform.rotation);
Destroy (col.gameObject);
StartCoroutine("ReloadGame"); //執行重新載入關卡程序
}
角色溺水作業 2/4
46
else { //其它角色掉落河裡
Instantiate(splash, col.transform.position, transform.rotation);
Destroy (col.gameObject);
}
}
IEnumerator ReloadGame() {
yield return new WaitForSeconds(2); //等待2秒
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex,
LoadSceneMode.Single);
}
}
角色溺水作業 3/4
47
 選取Assets/Prefabs/FX/splash.prefab
 Sorting Layer = Foreground
 拖曳Assets/Prefabs/FX/splash.prefab到Splash欄
 測試遊戲
 敵人掉落河裡會濺出水花並消毀
 玩家落河後會濺出水花並重新開始關卡
角色溺水作業 4/4
48
 選取Assets/Prefabs/Props/bombCrate.prefab
 子物件Sorting Layer = Foreground
 選取Assets/Prefabs/Props/healthCrate.prefab
 子物件Sorting Layer = Foreground
 在Assets/Prefabs/Props/healthCrate.prefab之health子物件加
入HealthPickup.cs程式腳本
製作空投道具箱 1/5
49
using UnityEngine;
using System.Collections;
public class HealthPickup : MonoBehaviour {
public float healthBonus; //補充生命值
public AudioClip collect; //收集道具時之音效
private PickupSpawner pickupSpawner; //PickupSpawner程式腳本參照
private Animator anim; //動畫控制器參照
private bool landed; //道具箱落地旗號
void Awake () {
pickupSpawner = GameObject.Find("pickupManager").GetComponent<PickupSpawner>();
anim = transform.root.GetComponent<Animator>();
}
void OnTriggerEnter2D (Collider2D other) {
if (other.tag == "Player") { //玩家碰到道具箱
PlayerHealth playerHealth = other.GetComponent<PlayerHealth>();
playerHealth.health += healthBonus; //補充玩家生命值
playerHealth.health = Mathf.Clamp(playerHealth.health, 0f, 100f); //上限值100
製作空投道具箱 2/5
50
playerHealth.UpdateHealthBar(); //更新顯示玩家生命條
pickupSpawner.StartCoroutine(pickupSpawner.DeliverPickup()); //啟動下一波空投程序
AudioSource.PlayClipAtPoint(collect,transform.position); //撥放音效
Destroy(transform.root.gameObject); //銷毀道具箱
}
else if(other.tag == "ground" && !landed) { //道具箱落地
anim.SetTrigger("Land"); //觸發道具箱落地動畫
transform.parent = null;
gameObject.AddComponent<Rigidbody2D>();
landed = true;
}
}
}
 拖曳Assets/Audio/FX/healthPickup.ogg到
Collect欄
製作空投道具箱 3/5
51
 在Assets/Prefabs/Props/bombCrate.prefab之crate子物件加入
BombPickup.cs程式腳本
using UnityEngine;
using System.Collections;
public class BombPickup : MonoBehaviour {
public AudioClip pickupClip; //收集道具時之音效
private Animator anim; //動畫控制器參照
private bool landed = false; //道具箱落地旗號
void Awake() {
anim = transform.root.GetComponent<Animator>();
}
void OnTriggerEnter2D (Collider2D other) {
if (other.tag == "Player") { //玩家碰到道具箱
AudioSource.PlayClipAtPoint(pickupClip, transform.position);
other.GetComponent<LayBombs>().bombCount++; //增加玩家炸彈數量
Destroy(transform.root.gameObject); //銷毀道具箱
}
製作空投道具箱 4/5
52
else if (other.tag == "ground" && !landed) { //道具箱落地
anim.SetTrigger("Land"); //觸發道具箱落地動畫
transform.parent = null;
gameObject.AddComponent<Rigidbody2D>();
landed = true;
}
}
}
 拖曳Assets/Audio/Player/Taunts/Player-IDefyYou.wav到
Pickup Clip欄
製作空投道具箱 5/5
53
 選單命令GameObject> Create Empty建立空物件,命名
pickupManager
 重置Transform
 加入PickupSpawner.cs程式腳本
空投道具 1/4
54
using UnityEngine;
using System.Collections;
public class PickupSpawner : MonoBehaviour {
public GameObject[] pickups; //道具預製物件陣列
public float pickupDeliveryTime = 5f; //傳送道具等待時間
public float dropRangeLeft; //道具空投範圍左邊界坐標
public float dropRangeRight; //道具空投範圍右邊界坐標
public float highHealthThreshold = 75f; //玩家生命值超過此設定值時,只空投炸彈道具
public float lowHealthThreshold = 25f; //玩家生命值低於此設定值時,只空投傷藥道具
private PlayerHealth playerHealth; //PlayerHealth程式腳本參照
void Awake () {
playerHealth = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerHea
lth>();
}
空投道具 2/4
55
void Start () {
StartCoroutine(DeliverPickup()); //啟動DeliverPickup程序
}
public IEnumerator DeliverPickup() {
yield return new WaitForSeconds(pickupDeliveryTime); //等待一段指定時間
float dropPosX = Random.Range(dropRangeLeft, dropRangeRight);//隨機空投X坐標
Vector3 dropPos = new Vector3(dropPosX, 15f, 1f); //空投位置
if (playerHealth.health >= highHealthThreshold) //空投炸彈
Instantiate(pickups[0], dropPos, Quaternion.identity);
else if (playerHealth.health <= lowHealthThreshold) //空投傷藥
Instantiate(pickups[1], dropPos, Quaternion.identity);
else { //隨機空投道具
int pickupIndex = Random.Range(0, pickups.Length);
Instantiate(pickups[pickupIndex], dropPos, Quaternion.identity);
}
}
}
空投道具 3/4
56
 設定Pickups Spawner
 拖曳Assets/Prefabs/Props/bombCrate.prefab到Pickups欄
 拖曳Assets/Prefabs/Props/healthCrate.prefab到Pickups欄
 Pickup Delivery Time = 5
 Drop Range Left = -15
 Drop Range Right = 15
 High Health Threshold = 75
 Low Health Threshold = 25
 測試遊戲,會隨機飄下道具
 玩家Player Health之Health值小於25時,⼀定飄下急救箱
 玩家Player Health之Health值大於75時,⼀定飄下炸彈
空投道具 4/4
57
 選單命令GameObject> UI> Raw Image,命名為ui_bombHUD
 Anchor Presets = bottom, left
 Pos(X, Y, Z) = (10, 10, 0)
 Width = 84, Height = 70
 Pivot(X, Y) = (0, 0)
 拖曳Assets/Sprites/_Props/
prop_crate_ammo.png到Texture欄
製作炸彈圖示
58
 將Assets/Prefabs/Props/explosionParticle.prefab加到場景
 選取Assets/Prefabs/Props/bomb.prefab
 Sorting Layer = Character
 在Mr.Bean加入LayBombs.cs程式腳本
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class LayBombs : MonoBehaviour {
[HideInInspector]
public bool bombLaid = false; //玩家是否已放置炸彈
public int bombCount = 0; //玩家擁有的炸彈道具個數
public AudioClip bombsAway; //玩家放置炸彈時的語音
public GameObject bomb; //炸彈預製物件
private RawImage bombHUD; //炸彈道具圖示參照,當玩家擁有後就會開啟圖示
玩家放置炸彈 1/3
59
void Awake () {
bombHUD = GameObject.Find("ui_bombHUD").GetComponent<RawImage>();
}
void Update () {
if (Input.GetButtonDown("Fire2") && !bombLaid && bombCount > 0) { //放置炸彈
bombCount--;
bombLaid = true;
AudioSource.PlayClipAtPoint(bombsAway,transform.position);
Instantiate(bomb, transform.position, transform.rotation);
}
bombHUD.enabled = bombCount > 0; //更新炸彈圖示狀態
}
}
玩家放置炸彈 2/3
60
 拖曳Assets/Prefabs/Props/bomb.prefab到Bomb欄
 拖曳Assets/Audio/Player/Taunts/Player-BombsAway.ogg到
Bombs Away欄
 執行遊戲
 Bomb Count大於0時,會顯示炸彈道具圖示
 拿到炸彈道具後,使用滑鼠右���放置炸彈
玩家放置炸彈 3/3
61
 選單命令GameObject> Create Empty建立空物件,命名GameManager
 在GameManager加入Pauser.cs程式腳本
using UnityEngine;
using System.Collections;
public class Pauser : MonoBehaviour {
private bool paused = false;
void Update () {
if (Input.GetKeyUp(KeyCode.P)) { //按了P鍵
paused = !paused; //切換暫停狀態
}
if (paused)
Time.timeScale = 0;
else
Time.timeScale = 1;
}
}
 測試遊戲,按P鍵可暫停遊戲
遊戲控制 1/2
62
 編輯GameManager.cs程式腳本,按Esc鍵可結束遊戲
using UnityEngine;
using System.Collections;
public class Pauser : MonoBehaviour {
private bool paused = false;
void Update () {
if (Input.GetKey (KeyCode.Escape)) { //按了Esc鍵
Application.Quit (); //結束遊戲
}
if (Input.GetKeyUp(KeyCode.P)) { //按了P鍵
...
}
}
註:需建立執行檔測試
遊戲控制 2/2
63
 選單命令Files>Build Settings…
 設定遊戲場景、選擇遊戲平台
 點擊Build按鈕
建立執行檔
64

More Related Content

Unity遊戲程式設計 - 2D Platformer遊戲

  • 1. 2D Platformer遊戲 Revised on January 1, 2020  背景動畫與背景音樂  玩家角色控制  隨機生成敵人  空投道具  使用道具  顯示分數  暫停遊戲控制
  • 2.  建立2D專案,my2DPlatformer  選單命令File> Save As…,將預設場景另存新檔  Exercise.unity  調整Main Camera  Position(X, Y, Z) = (-4.43, -0.15, -10)  Background(R, G, B, A) = (163, 187, 196, 5)  Size = 11  Free Aspect  滙入遊戲資源  選單命令Assets> Import Package> Custom Package…,滙入 platformer2d.unitypackage 建立專案 1/3 2
  • 3.  新增Tags  ground、Crate、Enemy、Wall、Obstacle、 Bullet、BombPickup、ExplosionFX、 HealthBar  新增Sorting Layers (遊戲圖層)  Background、Character、Foreground、UI  新增Layers (碰撞管理圖層)  Bombs、Player、Enemies、Pickups、 Ground 建立專案 2/3 3
  • 4.  選單命令Edit> Project Settings…  Physics 2D Layer Collicion Matrix 建立專案 3/3 4
  • 5.  ���Assets/Prefebs/Environment/backgrounds.prefab加到場景  Sorting Layer = Background  設定所有子物件Sorting Layer = Background  將Assets/Prefabs/Environment/Foregrounds.prefab加到場景  設定所有子物件Sorting Layer = Foreground 建立場景 5
  • 6.  選取Prefabs/Props/swan.prefab  Sorting Layer = Background  選取Prefabs/Environment/Cab.prefab  Sorting Layer = Background  Wheels子物件  Sorting Layer = Background  選取Prefabs/Environment/Bus.prefab  Sorting Layer = Background  Wheels子物件  Sorting Layer = Background 設定背景動畫預製物件 6
  • 7.  在_Scripts目錄下新增BackgroundPropSpawner.cs程式腳本 using UnityEngine; using System.Collections; public class BackgroundPropSpawner : MonoBehaviour { public Rigidbody2D backgroundProp; //待生成的背景預製物件 public float leftSpawnPosX; //左側生成位置x座標 public float rightSpawnPosX; //右側生成位置x座標 public float minSpawnPosY; //生成位置y座標區間最小值 public float maxSpawnPosY; //生成位置y座標區間最大值 public float minTimeBetweenSpawns; //生成間隔時間最小值 public float maxTimeBetweenSpawns; //生成間隔時間最大值 public float minSpeed; //最小移動速度 public float maxSpeed; //移動速度最大值 void Start () { Random.InitState(System.DateTime.Today.Millisecond); StartCoroutine("Spawn"); //起始Spawn程序 } 隨機產生背景動畫物件 1/6 7
  • 8. IEnumerator Spawn () { float waitTime = Random.Range(minTimeBetweenSpawns, maxTimeBetweenSpawns); yield return new WaitForSeconds(waitTime); //隨機等待一段時間 bool facingLeft = Random.Range(0,2) == 0; //隨機設定預製物件方向 float posX = facingLeft ? rightSpawnPosX : leftSpawnPosX; float posY = Random.Range(minSpawnPosY, maxSpawnPosY); Vector3 spawnPos = new Vector3(posX, posY, transform.position.z); Rigidbody2D propInstance = Instantiate(backgroundProp, spawnPos, Quaternion.identity) as Rigidbody2D; if (!facingLeft) { Vector3 scale = propInstance.transform.localScale; scale.x *= -1; propInstance.transform.localScale = scale; } float speed = Random.Range(minSpeed, maxSpeed); //隨機設定速度 speed *= facingLeft ? -1f : 1f; propInstance.velocity = new Vector2(speed, 0); StartCoroutine(Spawn()); 隨機產生背景動畫物件 2/6 8
  • 9. while (propInstance != null) { if (facingLeft) { if (propInstance.transform.position.x < leftSpawnPosX - 0.5f) Destroy(propInstance.gameObject); } else { if (propInstance.transform.position.x > rightSpawnPosX + 0.5f) Destroy(propInstance.gameObject); } yield return null; } } } 隨機產生背景動畫物件 3/6 9
  • 10.  新增空物件,命名為swanCreator  重置Transform  加入BackgroundPropSpawner.cs程式腳本  拖曳Prefabs/Props/swan.prefab到Prop欄  Left Spawn Pos X = -24  Right Spawn Pos X = 24  Min Spawn Pos Y = 4  Max Spawn Pos Y = 8  Min Time Between Spawns = 2  Max Time Between Spawns = 8  Min Speed = 5  Max Speed = 8  測試遊戲,天空會隨機有天鵝飛過 隨機產生背景動畫物件 4/6 10
  • 11.  新增空物件,命名為busCreator  重置Transform  加入BackgroundPropSpawner.cs程式腳本  拖曳Prefabs/Environment/Bus.prefab到Prop欄  Left Spawn Pos X = -24  Right Spawn Pos X = 24  Min Spawn Pos Y = -5.5  Max Spawn Pos Y = -5.5  Min Time Between Spawns = 8  Max Time Between Spawns = 18  Min Speed = 5  Max Speed = 8  測試遊戲,街道會隨機有巴士通過 隨機產生背景動畫物件 5/6 11
  • 12.  新增空物件,命名為cabCreator  重置Transform  加入BackgroundPropSpawner.cs程式腳本  拖曳Prefabs/Environment/Cab.prefab到Prop欄  Left Spawn Pos X = -24  Right Spawn Pos X = 24  Min Spawn Pos Y = -6.4  Max Spawn Pos Y = -6.4  Min Time Between Spawns = 10  Max Time Between Spawns = 15  Min Speed = 5  Max Speed = 8  測試遊戲,街道會隨機有計程車通過 隨機產生背景動畫物件 6/6 12
  • 13.  新增空物件,命名為music  重置Transform  加入AudioSource元件  AudioClip = MainTheme  勾選Play On Awake  勾選Loop  Volume = 0.1  Reverb Zone Mix = 0  測試專案,遊戲執行時會持續撥放背景音樂 加入背景音樂 13
  • 14.  將Assets/Prefabs/Chractera/Mr.Bean.prefab加倒場景  設定所有子物件Sorting Layer = Character  Tag = Player  Layer = Player 建立玩家角色 14
  • 16.  在Mr.Bean物件加入PlayerControl.cs程式腳本 using UnityEngine; using System.Collections; public class PlayerControl : MonoBehaviour { [HideInInspector] public bool facingRight = true; //方向旗號,不顯示在屬性窗格,但可提其它程式腳本存取 [HideInInspector] public bool jump = false; //跳躍旗號,不顯示在屬性窗格,但可提其它程式腳本存取 public float moveForce = 365f; //行進力道 public float maxSpeed = 5f; //移動速度上限 public AudioClip[] jumpClips; //跳躍動作音效庫 public float jumpForce = 1000f; //跳躍力道 private Transform groundCheck; //用來檢查玩家角色是否站在地面 private bool grounded = false; //落地旗號 private Animator anim; //玩家角色動作控制器參照 玩家角色控制 2/5 16
  • 17. void Awake() { groundCheck = transform.Find("groundCheck"); anim = GetComponent<Animator>(); } void Update() { grounded = Physics2D.Linecast(transform.position, groundCheck.position, 1 << LayerMask.NameToLayer("Ground")); //玩家角色位於在地面,並按下Jump鍵 if (Input.GetButtonDown("Jump") && grounded) jump = true; //執行跳躍動作 } 玩家角色控制 3/5 17
  • 18. void FixedUpdate () { float h = Input.GetAxis("Horizontal"); anim.SetFloat("Speed", Mathf.Abs(h)); if (h * GetComponent<Rigidbody2D>().velocity.x < maxSpeed) GetComponent<Rigidbody2D>().AddForce(Vector2.right * h * moveForce); //加速 if (Mathf.Abs(GetComponent<Rigidbody2D>().velocity.x) > maxSpeed) GetComponent<Rigidbody2D>().velocity = new Vector2(Mathf.Sign(GetComponent<Rigidbody2D>().velocity.x) * maxSpeed, GetComponent<Rigidbody2D>().velocity.y); if (h > 0 && !facingRight) Flip(); //反轉方向 else if (h < 0 && facingRight) Flip(); //反轉方向 if (jump) { anim.SetTrigger("Jump"); int i = Random.Range(0, jumpClips.Length); //隨機撥放跳躍音效 AudioSource.PlayClipAtPoint(jumpClips[i], transform.position); GetComponent<Rigidbody2D>().AddForce(new Vector2(0f, jumpForce)); //跳躍 jump = false; //防止重複跳躍 } } 玩家角色控制 4/5 18
  • 19. void Flip () { facingRight = !facingRight; Vector3 theScale = transform.localScale; theScale.x *= -1; transform.localScale = theScale; } }  拖曳Audio/Player/Jumps/Player- jump[1-3].wav到Jump Clips欄  測試遊戲,可操控主角移動  適度調整移動速度及跳躍力道 玩家角色控制 5/5 19
  • 20.  新增空物件,命名為healthUI  Tag = HealthBar  Position(X, Y, Z) = (0, 100, 0)  在healthUI下新增Sprite物件,命名為HealthBar  Position(X, Y, Z) = (-0.83, 0, 0)  拖曳Assets/Sprites/_UI/Health.png到Sprite Renderer之Sprite欄  拖曳Assets/Materials/Health.mat到Sprite Renderer之Material欄  Sorting Layer = Foreground 建立玩家生命條 1/4 20
  • 21.  在healthUI下新增Sprite物件,命名為HealthOutline  Position(X, Y, Z) = (0, 0, 0)  拖曳Assets/Sprites/_UI/Health-bg.png到Sprite Renderer之 Sprite欄  拖曳Assets/Materials/DefaultPixelSnap.mat到Sprite Renderer 之Material欄  Sorting Layer = Foreground 建立玩家生命條 2/4 21
  • 22.  在healthUI加入FollowPlayer.cs程式腳本 using UnityEngine; using System.Collections; public class FollowPlayer : MonoBehaviour { public Vector3 offset; //顯示位置偏移值 private Transform player; void Awake () { player = GameObject.FindGameObjectWithTag("Player").transform; } void Update () { transform.position = player.position + offset; } } 建立玩家生命條 3/4 22
  • 23.  設定healthUI之FollowPlayer  Offset(X, Y, Z) = (0, 1.2, 0)  測試遊戲,玩家角色上方會顯示生命條並且跟隨移動 建立玩家生命條 4/4 23
  • 24.  在Mr.Bean物件加入PlayerHealth.cs程式腳本 using UnityEngine; using System.Collections; public class PlayerHealth : MonoBehaviour { public float health = 100f; //玩家生命值 public float repeatDamagePeriod = 1f; //玩家連續受傷之時時間隔 public AudioClip[] ouchClips; //玩家受傷音效 public float hurtForce = 100f; //玩家受傷時受到之推力 public float damageAmount = 10f; //每次受傷之損血值 private SpriteRenderer healthBar; //玩家生命條之sprite renderer參照 private float lastHitTime; //前次受傷時間戳記 private Vector3 healthScale; //生命條滿格時之大小 private PlayerControl playerControl; //玩家PlayerControl程式腳本參照 private Animator anim; //玩家動作控制器參照 玩家生命值管理 1/4 24
  • 25. void Awake () { playerControl = GetComponent<PlayerControl>(); //玩家PlayerControl參照 healthBar = GameObject.Find("HealthBar").GetComponent<SpriteRenderer>(); anim = GetComponent<Animator>(); //玩家動作控制器參照 healthScale = healthBar.transform.localScale; //記錄生命條滿格之大小 } void OnCollisionEnter2D (Collision2D col) { if (col.gameObject.tag == "Enemy") { //敵人撞到玩家 if (Time.time > lastHitTime + repeatDamagePeriod) { //已達連續受傷之時間間隔 if (health > 0f) { TakeDamage(col.transform); //使玩家損血 lastHitTime = Time.time; //記錄受傷時間 } else { //玩家死��,使玩家摔落河裡 Collider2D[] cols = GetComponents<Collider2D>(); foreach (Collider2D c in cols) { //將所有Collider2D調整為觸發器 c.isTrigger = true; } SpriteRenderer[] spr = GetComponentsInChildren<SpriteRenderer>(); 玩家生命值管理 2/4 25
  • 26. foreach(SpriteRenderer s in spr) { //將玩家角色移到UI圖層 s.sortingLayerName = "UI"; } GetComponent<PlayerControl>().enabled = false; //停止PlayerControl程式腳本 GetComponentInChildren<Gun>().enabled = false; //停止Gun程式腳本 anim.SetTrigger("Die"); //觸發Die動畫 } } } } void TakeDamage (Transform enemy) { playerControl.jump = false; //停止跳躍 Vector3 hurtVector = transform.position - enemy.position + Vector3.up * 5f; GetComponent<Rigidbody2D>().AddForce(hurtVector * hurtForce);//向玩家施加衝撞力 health -= damageAmount; //扣減玩家血量 UpdateHealthBar(); //更新顯示生命條 int i = Random.Range (0, ouchClips.Length); //隨機撥放玩家受傷音效 AudioSource.PlayClipAtPoint(ouchClips[i], transform.position); } 玩家生命值管理 3/4 26
  • 27. public void UpdateHealthBar () { //根據玩家的生命值,將生命條的顏色設置為綠色和紅色之間的比例 healthBar.material.color = Color.Lerp(Color.green, Color.red, 1 - health * 0.01f); //根據玩家的生命值,調整生命條的寬度 healthBar.transform.localScale = new Vector3(healthScale.x * health * 0.01f, 1, 1); } }  拖曳Assets/Audio/Player/Ouch/Player-ouch[1-4].wav到Ouch Clips欄 玩家生命值管理 4/4 27
  • 29.  在Main Camera加入CameraFollow.cs程式腳本 using UnityEngine; using System.Collections; public class CameraFollow : MonoBehaviour { public float xMargin = 1f; //攝影機啟動跟隨前允許玩家移動的水平位移 public float yMargin = 1f; //攝影機啟動跟隨前允許玩家移動的垂直位移 public float xSmooth = 8f; //使相機平穩地捕捉目標運動之X軸修正值 public float ySmooth = 8f; //使相機平穩地捕捉目標運動之Y軸修正值 public Vector2 maxXAndY; //攝影機位置X與Y座標最大值 public Vector2 minXAndY; //攝影機位置X與Y座標最小值 private Transform player; //玩家角色transform屬性 void Awake () { player = GameObject.FindGameObjectWithTag("Player").transform; } bool CheckXMargin() { return Mathf.Abs(transform.position.x - player.position.x) > xMargin; } 攝影機跟隨玩家移動 2/4 29
  • 30. bool CheckYMargin() { return Mathf.Abs(transform.position.y - player.position.y) > yMargin; } void FixedUpdate () { TrackPlayer(); } void TrackPlayer () { float targetX = transform.position.x; float targetY = transform.position.y; if (CheckXMargin()) targetX = Mathf.Lerp(transform.position.x, player.position.x, xSmooth * Time.deltaTime); if (CheckYMargin()) targetY = Mathf.Lerp(transform.position.y, player.position.y, ySmooth * Time.deltaTime); targetX = Mathf.Clamp(targetX, minXAndY.x, maxXAndY.x); targetY = Mathf.Clamp(targetY, minXAndY.y, maxXAndY.y); transform.position = new Vector3(targetX, targetY, transform.position.z); } } 攝影機跟隨玩家移動 3/4 30
  • 31.  設定Camera Follow參數  X Margin = 2,Y Margin = 2  X Smooth = 2,Y Smooth = 2  Max(X, Y) = (5, 5)  Min(X, Y) = (-5, -5)  測試遊戲,玩家水平或垂直位移超過2時,攝影機就會自動跟隨 攝影機跟隨玩家移動 4/4 31
  • 32.  選取Assets/Prefabs/Props/rocket.prefab  子物件Sorting Layer = Character  在Mr.Bean的Gun子物件加入Gun.cs腳本 using UnityEngine; using System.Collections; public class Gun : MonoBehaviour { public Rigidbody2D rocket; //rocket預製物件 public float speed = 25f; //rocket速度 private PlayerControl playerCtrl; //PlayerControl程式腳本參照 private Animator anim; //角色動畫控制器參照 void Awake() { anim = transform.root.gameObject.GetComponent<Animator>(); playerCtrl = transform.root.GetComponent<PlayerControl>(); } Mr.Bean射擊控制 1/3 32
  • 33. void Update () { if (Input.GetButtonDown("Fire1")) { //按下發射鍵 anim.SetTrigger("Shoot"); GetComponent<AudioSource>().Play(); if (playerCtrl.facingRight) { //向右發射 Rigidbody2D bulletInstance = Instantiate(rocket, transform.position, Quaternion.Euler(new Vector3(0, 0, 0))) as Rigidbody2D; bulletInstance.velocity = new Vector2(speed, 0); } else { //向左發射 Rigidbody2D bulletInstance = Instantiate(rocket, transform.position, Quaternion.Euler(new Vector3(0, 0, 180f))) as Rigidbody2D; bulletInstance.velocity = new Vector2(-speed, 0); } } } } Mr.Bean射擊控制 2/3 33
  • 34.  拖曳Assets/Prefabs/Props/rocket.prefab到Gun腳本的Rocket 資料欄  選取Assets/Prefabs/Props/rocketExplosion.prefab  Sorting Layer = Foreground  測試遊戲,點擊滑鼠左鍵可發射火箭彈 Mr.Bean射擊控制 3/3 34
  • 35.  選單命令GameObject> UI> Text,命名為Score  Anchor Presets = top, center  Pos(X, Y, Z) = (0, -10, 0)  Width = 500, Height = 100  Pivot(X, Y) = (0.5, 1)  Text = Score  Font = BradBunR  Font Size = 80  Alignment = Center  Color = white 加入計分板 1/5 35
  • 36.  在Score加入Score.cs程式腳本 using UnityEngine; using System.Collections; using UnityEngine.UI; public class Score : MonoBehaviour { public int score = 0; //玩家分數值 void Update () { GetComponent<Text>().text = "Score: " + score; //更新顯示分數 } } 加入計分板 2/5 36
  • 37.  選單命令GameObject> UI> Text,命名為Score-shadow  Anchor Presets = top, center  Pos(X, Y, Z) = (0, -14, 0)  Width = 500, Height = 100  Pivot(X, Y) = (0.5, 1)  Text = Score  Font = BradBunR  Font Size = 80  Alignment = Center  Color = black 加入計分板 3/5 37
  • 38.  在Score-shadow加入ScoreShadow.cs程式腳本 using UnityEngine; using System.Collections; using UnityEngine.UI; public class ScoreShadow : MonoBehaviour { public GameObject guiCopy; // Score物件參照 void Awake () { Vector3 behindPos = transform.position; behindPos = new Vector3(guiCopy.transform.position.x, guiCopy.transform.position.y - 4f, guiCopy.transform.position.z); transform.position = behindPos; } void Update () { GetComponent<Text>().text = guiCopy.GetComponent<Text>().text; //同步更新資料 } } 加入計分板 4/5 38
  • 39.  拖曳Score到Score-shadow之Gui Copy欄  調整Hierarhy窗格中物件順序,使Score位於Score-shadow下方  測試遊戲,場景中央上方會顯示陰影效果的得分值 加入計分板 5/5 39
  • 40.  選取Assets/Prefabs/Characters/enemy1.prefab  子物件Sorting Layer = Character  選取Assets/Prefabs/Characters/enemy2.prefab  子物件Sorting Layer = Character 隨機生成敵人 1/5 40
  • 41.  選單命令GameObject> Create Empty建立空物件,命名為 EnemySpawners  重置Transform  Position(X,Y,Z) = (0, 15, 0)  選單命令GameObject> Create Empty Child,在EnemySpawners 建立⼀個空子物件,命名為MidSpawner  重置Transform  Position(X,Y,Z) = (0.27, 0, 0)  在MidSpawner加入EnemySpawner.cs腳本 隨機生成敵人 2/5 41
  • 42. using System.Collections; using System.Collections.Generic; using UnityEngine; public class EnemySpawner : MonoBehaviour { public float spawnTime = 5f; //敵人生成間隔時間 public float spawnDelay = 3f; //延遲時間後才開始生成 public GameObject[] enemies; //敵人預製物件庫 void Start () { InvokeRepeating("Spawn", spawnDelay, spawnTime); } void Spawn () { int enemyIndex = Random.Range(0, enemies.Length); Instantiate(enemies[enemyIndex], transform.position, transform.rotation); } } 隨機生成敵人 3/5 42
  • 44.  複製2份MidSpawner,更名為LeftSpawner及RightSpawner  重置Transform  LeftSpawner Position(X,Y,Z) = (-13.8, 0, 0)  RightSpawner Position(X,Y,Z) = (14.5, 0, 0)  選取Assets/Prefabs/UI/ui_100points.prefab  子物件Sorting Layer = UI  測試遊戲  碰到敵人會損血,擊斃敵人會得分  玩家及敵人掉落河裡後,並不會被銷毀 隨機生成敵人 5/5 44
  • 45.  選單命令GameObject> Create Empty建立空物件,命名Destroyer  Position(X, Y, Z) = (0.54, -13.5, 0)  Scale(X, Y, Z) = (1.9, 1, 1)  加入Box Collider 2D  勾選Is Trigger  Size(X, Y) = (23, 1.9) 角色溺水作業 1/4 45
  • 46.  在Destroyer物件加入Remover.cs程式腳本 using UnityEngine; using UnityEngine.SceneManagement; using System.Collections; public class Remover : MonoBehaviour { public GameObject splash; //水花噴濺特效預製物件 void OnTriggerEnter2D(Collider2D col) { if (col.gameObject.tag == "Player") { //玩家掉落河裡 GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraFollow>() .enabled = false; GameObject.FindGameObjectWithTag("HealthBar").GetComponent<FollowPlayer>(). enabled = false; Instantiate(splash, col.transform.position, transform.rotation); Destroy (col.gameObject); StartCoroutine("ReloadGame"); //執行重新載入關卡程序 } 角色溺水作業 2/4 46
  • 47. else { //其它角色掉落河裡 Instantiate(splash, col.transform.position, transform.rotation); Destroy (col.gameObject); } } IEnumerator ReloadGame() { yield return new WaitForSeconds(2); //等待2秒 SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex, LoadSceneMode.Single); } } 角色溺水作業 3/4 47
  • 48.  選取Assets/Prefabs/FX/splash.prefab  Sorting Layer = Foreground  拖曳Assets/Prefabs/FX/splash.prefab到Splash欄  測試遊戲  敵人掉落河裡會濺出水花並消毀  玩家落河後會濺出水花並重新開始關卡 角色溺水作業 4/4 48
  • 49.  選取Assets/Prefabs/Props/bombCrate.prefab  子物件Sorting Layer = Foreground  選取Assets/Prefabs/Props/healthCrate.prefab  子物件Sorting Layer = Foreground  在Assets/Prefabs/Props/healthCrate.prefab之health子物件加 入HealthPickup.cs程式腳本 製作空投道具箱 1/5 49
  • 50. using UnityEngine; using System.Collections; public class HealthPickup : MonoBehaviour { public float healthBonus; //補充生命值 public AudioClip collect; //收集道具時之音效 private PickupSpawner pickupSpawner; //PickupSpawner程式腳本參照 private Animator anim; //動畫控制器參照 private bool landed; //道具箱落地旗號 void Awake () { pickupSpawner = GameObject.Find("pickupManager").GetComponent<PickupSpawner>(); anim = transform.root.GetComponent<Animator>(); } void OnTriggerEnter2D (Collider2D other) { if (other.tag == "Player") { //玩家碰到道具箱 PlayerHealth playerHealth = other.GetComponent<PlayerHealth>(); playerHealth.health += healthBonus; //補充玩家生命值 playerHealth.health = Mathf.Clamp(playerHealth.health, 0f, 100f); //上限值100 製作空投道具箱 2/5 50
  • 51. playerHealth.UpdateHealthBar(); //更新顯示玩家生命條 pickupSpawner.StartCoroutine(pickupSpawner.DeliverPickup()); //啟動下一波空投程序 AudioSource.PlayClipAtPoint(collect,transform.position); //撥放音效 Destroy(transform.root.gameObject); //銷毀道具箱 } else if(other.tag == "ground" && !landed) { //道具箱落地 anim.SetTrigger("Land"); //觸發道具箱落地動畫 transform.parent = null; gameObject.AddComponent<Rigidbody2D>(); landed = true; } } }  拖曳Assets/Audio/FX/healthPickup.ogg到 Collect欄 製作空投道具箱 3/5 51
  • 52.  在Assets/Prefabs/Props/bombCrate.prefab之crate子物件加入 BombPickup.cs程式腳本 using UnityEngine; using System.Collections; public class BombPickup : MonoBehaviour { public AudioClip pickupClip; //收集道具時之音效 private Animator anim; //動畫控制器參照 private bool landed = false; //道具箱落地旗號 void Awake() { anim = transform.root.GetComponent<Animator>(); } void OnTriggerEnter2D (Collider2D other) { if (other.tag == "Player") { //玩家碰到道具箱 AudioSource.PlayClipAtPoint(pickupClip, transform.position); other.GetComponent<LayBombs>().bombCount++; //增加玩家炸彈數量 Destroy(transform.root.gameObject); //銷毀道具箱 } 製作空投道具箱 4/5 52
  • 53. else if (other.tag == "ground" && !landed) { //道具箱落地 anim.SetTrigger("Land"); //觸發道具箱落地動畫 transform.parent = null; gameObject.AddComponent<Rigidbody2D>(); landed = true; } } }  拖曳Assets/Audio/Player/Taunts/Player-IDefyYou.wav到 Pickup Clip欄 製作空投道具箱 5/5 53
  • 54.  選單命令GameObject> Create Empty建立空物件,命名 pickupManager  重置Transform  加入PickupSpawner.cs程式腳本 空投道具 1/4 54
  • 55. using UnityEngine; using System.Collections; public class PickupSpawner : MonoBehaviour { public GameObject[] pickups; //道具預製物件陣列 public float pickupDeliveryTime = 5f; //傳送道具等待時間 public float dropRangeLeft; //道具空投範圍左邊界坐標 public float dropRangeRight; //道具空投範圍右邊界坐標 public float highHealthThreshold = 75f; //玩家生命值超過此設定值時,只空投炸彈道具 public float lowHealthThreshold = 25f; //玩家生命值低於此設定值時,只空投傷藥道具 private PlayerHealth playerHealth; //PlayerHealth程式腳本參照 void Awake () { playerHealth = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerHea lth>(); } 空投道具 2/4 55
  • 56. void Start () { StartCoroutine(DeliverPickup()); //啟動DeliverPickup程序 } public IEnumerator DeliverPickup() { yield return new WaitForSeconds(pickupDeliveryTime); //等待一段指定時間 float dropPosX = Random.Range(dropRangeLeft, dropRangeRight);//隨機空投X坐標 Vector3 dropPos = new Vector3(dropPosX, 15f, 1f); //空投位置 if (playerHealth.health >= highHealthThreshold) //空投炸彈 Instantiate(pickups[0], dropPos, Quaternion.identity); else if (playerHealth.health <= lowHealthThreshold) //空投傷藥 Instantiate(pickups[1], dropPos, Quaternion.identity); else { //隨機空投道具 int pickupIndex = Random.Range(0, pickups.Length); Instantiate(pickups[pickupIndex], dropPos, Quaternion.identity); } } } 空投道具 3/4 56
  • 57.  設定Pickups Spawner  拖曳Assets/Prefabs/Props/bombCrate.prefab到Pickups欄  拖曳Assets/Prefabs/Props/healthCrate.prefab到Pickups欄  Pickup Delivery Time = 5  Drop Range Left = -15  Drop Range Right = 15  High Health Threshold = 75  Low Health Threshold = 25  測試遊戲,會隨機飄下道具  玩家Player Health之Health值小於25時,⼀定飄下急救箱  玩家Player Health之Health值大於75時,⼀定飄下炸彈 空投道具 4/4 57
  • 58.  選單命令GameObject> UI> Raw Image,命名為ui_bombHUD  Anchor Presets = bottom, left  Pos(X, Y, Z) = (10, 10, 0)  Width = 84, Height = 70  Pivot(X, Y) = (0, 0)  拖曳Assets/Sprites/_Props/ prop_crate_ammo.png到Texture欄 製作炸彈圖示 58
  • 59.  將Assets/Prefabs/Props/explosionParticle.prefab加到場景  選取Assets/Prefabs/Props/bomb.prefab  Sorting Layer = Character  在Mr.Bean加入LayBombs.cs程式腳本 using UnityEngine; using System.Collections; using UnityEngine.UI; public class LayBombs : MonoBehaviour { [HideInInspector] public bool bombLaid = false; //玩家是否已放置炸彈 public int bombCount = 0; //玩家擁有的炸彈道具個數 public AudioClip bombsAway; //玩家放置炸彈時的語音 public GameObject bomb; //炸彈預製物件 private RawImage bombHUD; //炸彈道具圖示參照,當玩家擁有後就會開啟圖示 玩家放置炸彈 1/3 59
  • 60. void Awake () { bombHUD = GameObject.Find("ui_bombHUD").GetComponent<RawImage>(); } void Update () { if (Input.GetButtonDown("Fire2") && !bombLaid && bombCount > 0) { //放置炸彈 bombCount--; bombLaid = true; AudioSource.PlayClipAtPoint(bombsAway,transform.position); Instantiate(bomb, transform.position, transform.rotation); } bombHUD.enabled = bombCount > 0; //更新炸彈圖示狀態 } } 玩家放置炸彈 2/3 60
  • 61.  拖曳Assets/Prefabs/Props/bomb.prefab到Bomb欄  拖曳Assets/Audio/Player/Taunts/Player-BombsAway.ogg到 Bombs Away欄  執行遊戲  Bomb Count大於0時,會顯示炸彈道具圖示  拿到炸彈道具後,使用滑鼠右鍵放置炸彈 玩家放置炸彈 3/3 61
  • 62.  選單命令GameObject> Create Empty建立空物件,命名GameManager  在GameManager加入Pauser.cs程式腳本 using UnityEngine; using System.Collections; public class Pauser : MonoBehaviour { private bool paused = false; void Update () { if (Input.GetKeyUp(KeyCode.P)) { //按了P鍵 paused = !paused; //切換暫停狀態 } if (paused) Time.timeScale = 0; else Time.timeScale = 1; } }  測試遊戲,按P鍵可暫停遊戲 遊戲控制 1/2 62
  • 63.  編輯GameManager.cs程式腳本,按Esc鍵可結束遊戲 using UnityEngine; using System.Collections; public class Pauser : MonoBehaviour { private bool paused = false; void Update () { if (Input.GetKey (KeyCode.Escape)) { //按了Esc鍵 Application.Quit (); //結束遊戲 } if (Input.GetKeyUp(KeyCode.P)) { //按了P鍵 ... } } 註:需建立執行檔測試 遊戲控制 2/2 63
  • 64.  選單命令Files>Build Settings…  設定遊戲場景、選擇遊戲平台  點擊Build按鈕 建立執行檔 64