【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);
    }
}

範例程式碼下載

後記

  • 終於完成了,做遊戲真是是寫程式的大全,什麼細節都要先考慮到,難怪薪水比較高?個人還在慢慢的學習中,希望能出個小品遊戲上架。