【Unity 3D 2019】躲貓貓?貓貓躲?如何製作重複使用的元件?
在前面幾篇的介紹之後,現在我們要來做一個簡單的小遊戲 - 貓貓躲,一些IDE使用上的細節就不多說了,在此帶入Prefab的使用,用來預製(Prefabricated)來複製大量重複使用的模板,另外還加上「鍵盤 / 按鈕」的使用,讓我們繼續看下去吧。
做一個長這樣子的Game吧
遊戲製作
放入素材
- 新增一個「2D專案」後,將遊戲中所需要的圖片拉到專案中,相對的位置調整好,在這裡比較要注意的是,物件是有「先後順序」關係的,所以有時候在畫面上看不到,可以去調Order in layer的大小,數字越大的越前面(越晚畫)。
使用鍵盤移動Player
- 在這裡新增一個PlayController.cs的Script,利用鍵盤上的「左 / 右鍵」來讓Player左右移動,此時箭頭還是不會動的。
using UnityEngine;
public class PlayController : MonoBehaviour {
private static readonly float moveSpeed = 3.0f;
void Update() { MovePlayerWithKey(); }
// 利用鍵盤左右鍵來移動Player
private void MovePlayerWithKey() {
if (Input.GetKeyDown(KeyCode.LeftArrow)) { MovePlayerWithSpeed(-moveSpeed); }
if (Input.GetKeyDown(KeyCode.RightArrow)) { MovePlayerWithSpeed(moveSpeed); }
}
// 移動Player (速度)
private void MovePlayerWithSpeed(float speed) {
transform.Translate(speed, 0, 0);
}
}
箭頭落下
- 在這裡新增一個ArrowController.cs的Script,讓箭頭能落下,然後超出了畫面就消失 - Destroy()。
using UnityEngine;
public class ArrowController : MonoBehaviour {
private static readonly float dropSpeed = -0.1f;
private static readonly float boundPositionY = -5.0f;
void Update() { DropArrow(); }
// 讓箭頭往下掉落 (超過畫面就消失)
private void DropArrow() {
transform.Translate(0, dropSpeed, 0);
if (transform.position.y < boundPositionY) { ArrowDestory(); }
}
// 箭頭消失
private void ArrowDestory() {
Destroy(gameObject);
}
}
Prefab
- 到這裡就是今天的重頭戲啦 - Prefab,其實做法很簡法,只要將做好的元件拉到專案內就可以了,然後就可以看到一個跟原來長的一模一樣,但是是灰色背景的元件,然後把原來的Delete掉,使用Instantiate()產生元件,讓它定時的被Clone。
using UnityEngine;
public class ArrowGenerator : MonoBehaviour {
public GameObject arrowPrefab;
private static readonly int minRandomX = -6;
private static readonly int maxRandomX = 7;
private static readonly float height = 7.0f;
private static readonly float spanTime = 1.0f;
private float deltaTime;
void Update() { ArrowMaker(); }
// 產生箭頭 (定時產生)
private void ArrowMaker() {
deltaTime += Time.deltaTime;
if (deltaTime > spanTime) {
deltaTime = 0;
GameObject arrowObject = Instantiate(arrowPrefab) as GameObject;
arrowObject.transform.position = RandomVector3(minRandomX, maxRandomX);
}
}
// 產生不同的位置
private Vector3 RandomVector3(int min, int max) {
int randomX = Random.Range(min, max);
return new Vector3(randomX, height, 0);
}
}
物體碰撞
- 在這裡要先找到「誰跟誰」碰撞?怎麼樣才算「碰撞」?先簡單利用兩個物件的距離來當成碰撞與否的判斷依據。
using UnityEngine;
public class ArrowController : MonoBehaviour {
private static readonly float playerRadius = 1.0f;
private static readonly float arrowRadius = 0.5f;
private static readonly string playIdentity = "player";
GameObject playerObject;
void Start() { playerObject = FindGameObjectWithIdentity(playIdentity); }
void Update() { ConflictTesting(); }
// 利用物件的名字找到該物件
private GameObject FindGameObjectWithIdentity(string identity) {
return GameObject.Find(identity);
}
// 測試箭頭跟玩家撞到時的反應 (兩者的距離 < 兩者的半徑和 => 箭頭消失 / 損血)
private void ConflictTesting() {
Vector2 arrowPosition = transform.position;
Vector2 playerPosition = playerObject.transform.position;
Vector2 directionVector = arrowPosition - playerPosition;
float direction = directionVector.magnitude;
float conflictRadius = arrowRadius + playerRadius;
if (direction < conflictRadius) {
ArrowDestory();
}
}
}
加入血量環
- 這裡我們幫助主角加入血量的計算,首先增加一個「UI -> Image」,然後把血量的圖示加進去。比較要注意的是,要將「錨點 - Anchor」改在右上角,如此一來畫面縮小的話,血量圖也不會跑出到畫面之外。再來就是要將「Image Type」調整成「Filled / Radial 360 / Top」,然後再改變「Fill Amount」試試看,血量圖是不是會變化了呢?
讓血量環動起來
- 接下來我們要做損血的動作了,不然不會損血就不好玩了呀。我們新增一個「導演 - GameDirector」,遊戲中的動作都要由它來處理,讓Code的職權分得更清楚。重點是「DecreaseHp()」這個函數要公開給大家使用 (有點像一般在寫Util公用函式的感覺),去調整「fillAmount」的值。
using UnityEngine;
using UnityEngine.UI;
public class GameDirector : MonoBehaviour {
private static readonly string hpGaugeIdentity = "hpGauge";
private static readonly float decreaseHpRate = 0.1f;
GameObject hpGauge;
void Start() { hpGauge = FindGameObjectWithIdentity(hpGaugeIdentity); }
// 利用物件的名字找到該物件
private GameObject FindGameObjectWithIdentity(string identity) {
return GameObject.Find(identity);
}
// 損血的處理 (公開的函式)
public void DecreaseHp() {
hpGauge.GetComponent<Image>().fillAmount -= decreaseHpRate;
}
}
using UnityEngine;
public class ArrowController : MonoBehaviour {
// 測試箭頭跟玩家撞到時的反應 (兩者的距離 < 兩者的半徑和 => 箭頭消失 / 損血)
private void ConflictTesting() {
...
if (direction < conflictRadius) {
ArrowDestory();
NoticeDecreaseHp();
}
}
// 通知GameDirector要損血了
private void NoticeDecreaseHp() {
GameObject gameDirector = FindGameObjectWithIdentity(gameDirectorIdentity);
gameDirector.GetComponent<GameDirector>().DecreaseHp();
}
}
產生左右按鈕
- 因為在手機上並沒有「左右鍵」,所以在這裡做個「UI -> Button」,讓手機也能玩。
讓左右按鈕產生動作
using UnityEngine;
public class PlayController : MonoBehaviour {
private static readonly float moveSpeed = 3.0f;
// 利用右按鍵來移動Player (公開的函式)
public void MovePlayerWithRightButton() {
MovePlayerWithSpeed(-moveSpeed);
}
// 利用左按鍵來移動Player (公開的函式)
public void MovePlayerWithLeftButton() {
MovePlayerWithSpeed(moveSpeed);
}
// 移動Player (速度)
private void MovePlayerWithSpeed(float speed) {
transform.Translate(speed, 0, 0);
}
}