【Unity 3D 2019】讓喵喵不再是阿飄?物理屬性與動畫

前面幾篇的介紹之後,這裡要做一個比較完整的DEMO,利用Unity的Physics屬性來產生碰撞跟移動的效果,其實在上一篇中使用程式去改變物體坐標位置也是可以的,不過WYSIWYG - 所見即所得的操作還是比較親民一些,動作也比較流暢。另外也會使用Animation來做簡單的2D動畫(連續換圖?),現在我們就開始吧。

遊戲製作

放入素材

  • 新增一個「2D專案」後,將遊戲中所需要的圖片拉到專案中,相對的位置調整好,在這裡比較要注意的是,物件是有「先後順序」關係的,所以有時候在畫面上看不到,可以去調Order in layer的大小,數字越大的越前面(越晚畫)。

加上鉛塊?

加上碰撞範圍

讓喵喵能跑能跳

using UnityEngine;

public class PlayerController : MonoBehaviour {

    private readonly float jumpForce = 300.0f;
    private readonly float walkForce = 10.0f;

    private new Rigidbody2D rigidbody2D;

    void Start() { InitSetting(); }

    void Update() {
        PlayerMove(walkForce);
        PlayerJump(jumpForce);
    }

    // 讓主角左右移動 (往左右推)
    private void PlayerMove(float force) {

        int direction = 0;

        if (Input.GetKey(KeyCode.RightArrow)) { direction = 1; }
        if (Input.GetKey(KeyCode.LeftArrow)) { direction = -1; }

        rigidbody2D.AddForce(transform.right * direction * force);
    }

    // 讓主角向上跳 (往上推)
    private void PlayerJump(float force) {

        if (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0)) {
            rigidbody2D.AddForce(transform.up * force);
        }
    }

    // 初始的一些設定 (先找出相關物件的位置)
    private void InitSetting() {
        rigidbody2D = GetComponent<Rigidbody2D>();
    }
}

改變碰撞體的外型

  • 在這裡可以發現主角跟雲不是那麼的貼合,所以我們要改變Collider的範圍,把雲變成的小一點的方形,而主角是半膠囊形的 (半圓形 + 方形)。

限制最高速 / 防止兩段跳

  • 因為使用外力「推的」會讓主角的速度越來越快,所以要限制最高速;另外,在跳的方面,也要防止兩段跳,等到主角的向上的速度為0時,才能再跳一次,此外,修改主角身上的Rigidbody 2D -> Gravity Scale的值,也能讓主角有從地球到月球一般,越跳越高。最後是Debug.Log()的使用,它可簡易的加上CSS來顯示喲。
using UnityEngine;

public class PlayerController : MonoBehaviour {

    private readonly float jumpForce = 300.0f;
    private readonly float walkForce = 10.0f;
    private readonly float maxWalkForce = 2.0f;

    private new Rigidbody2D rigidbody2D;

    void Start() { InitSetting(); }

    void Update() {
        PlayerMove(walkForce);
        PlayerJump(jumpForce);
    }

    // 讓主角左右移動 (往左右推 / 限制最高速)
    private void PlayerMove(float force) {

        int direction = 0;
        float speedX = Mathf.Abs(rigidbody2D.velocity.x);

        if (Input.GetKey(KeyCode.RightArrow)) { direction = 1; }
        if (Input.GetKey(KeyCode.LeftArrow)) { direction = -1; }

        rigidbody2D.AddForce(transform.right * direction * force);

        if (speedX < maxWalkForce) {
            rigidbody2D.AddForce(transform.right * direction * force);
        }

        Debug.Log("<color=red>[speedX] = " + speedX + "</color>");
    }

    // 讓主角向上跳 (往上推 / 防止兩段跳)
    private void PlayerJump(float force) {

        if (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0)) {
            if (IsVelocityZero(rigidbody2D.velocity.y)) { rigidbody2D.AddForce(transform.up * force); }
        }
    }

    // 初始的一些設定 (先找出相關物件的位置)
    private void InitSetting() {
        rigidbody2D = GetComponent<Rigidbody2D>();
    }

    // 測試速度是否為0?
    private bool IsVelocityZero(float velocity) {
        return Mathf.Approximately(velocity, 0);
    }
}

臉朝哪一邊?

using UnityEngine;

public class PlayerController : MonoBehaviour {

    private readonly float jumpForce = 300.0f;
    private readonly float walkForce = 10.0f;
    private readonly float maxWalkForce = 2.0f;

    private new Rigidbody2D rigidbody2D;

    void Start() { InitSetting(); }

    void Update() {
        PlayerMove(walkForce);
        PlayerJump(jumpForce);
    }

    // 讓主角左右移動 (往左右推 / 限制最高速)
    private void PlayerMove(float force) {

        int direction = 0;
        float speedX = Mathf.Abs(rigidbody2D.velocity.x);

        if (Input.GetKey(KeyCode.RightArrow)) { direction = 1; }
        if (Input.GetKey(KeyCode.LeftArrow)) { direction = -1; }

        rigidbody2D.AddForce(transform.right * direction * force);

        if (speedX < maxWalkForce) {
            rigidbody2D.AddForce(transform.right * direction * force);
        }

        PlayerMoveDirection(direction);
    }

    // 讓主角向上跳 (往上推 / 防止兩段跳)
    private void PlayerJump(float force) {

        if (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0)) {
            if (IsVelocityZero(rigidbody2D.velocity.y)) { rigidbody2D.AddForce(transform.up * force); }
        }
    }

    // 初始的一些設定 (先找出相關物件的位置)
    private void InitSetting() {
        rigidbody2D = GetComponent<Rigidbody2D>();
    }

    // 測試速度是否為0?
    private bool IsVelocityZero(float velocity) {
        return Mathf.Approximately(velocity, 0);
    }

    // 主角移動的圖形位置 (圖片翻轉)
    private void PlayerMoveDirection(int direction) {

        if (direction != 0) {
            transform.localScale = new Vector3(direction, 1, 1);
        }
    }
}

建立動畫

建立移動動畫

using UnityEngine;

public class PlayerController : MonoBehaviour {

    private Animator animator;

    // 讓主角左右移動 (往左右推 / 限制最高速)
    private void PlayerMove(float force) {

        int direction = 0;
        float speedX = Mathf.Abs(rigidbody2D.velocity.x);

        if (Input.GetKey(KeyCode.RightArrow)) { direction = 1; }
        if (Input.GetKey(KeyCode.LeftArrow)) { direction = -1; }

        if (speedX < maxWalkForce) {
            rigidbody2D.AddForce(transform.right * direction * force);
            WalkAnimatorSpeed(speedX);
        }

        PlayerMoveDirection(direction);
    }

    // 走路的動畫速度
    private void WalkAnimatorSpeed(float speed) {
        animator.speed = speed * 0.5f;
    }
}

建立彈跳動畫

  • 建立彈跳動畫跟建立移動動畫的動作大同小異,大家可以自己看看。

動畫的初換

  • 這裡主要是在「Animator」中,利用「Trigger」去做切換兩者間的連線,因為是在設定的方面比較多,麻煩請各位看圖說故事囉。
using UnityEngine;

public class PlayerController : MonoBehaviour {

    private readonly float jumpForce = 300.0f;
    private readonly float walkForce = 10.0f;
    private readonly float maxWalkForce = 2.0f;

    private readonly string trigger = "JumpTrigger";

    // 讓主角左右移動 (往左右推 / 限制最高速)
    private void PlayerMove(float force) {

        int direction = 0;
        float speedX = Mathf.Abs(rigidbody2D.velocity.x);

        if (Input.GetKey(KeyCode.RightArrow)) { direction = 1; }
        if (Input.GetKey(KeyCode.LeftArrow)) { direction = -1; }

        if (speedX < maxWalkForce) {
            rigidbody2D.AddForce(transform.right * direction * force);
        }

        PlayerMoveDirection(direction);
        WalkAnimatorSpeed(speedX);
    }

    // 讓主角向上跳 (往上推 / 防止兩段跳 / 切換動畫Trigger)
    private void PlayerJump(float force) {

        if (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0)) {
            if (IsVelocityZero(rigidbody2D.velocity.y)) {
                animator.SetTrigger(trigger);
                rigidbody2D.AddForce(transform.up * force);
            }
        }
    }

    // 測試速度是否為0?
    private bool IsVelocityZero(float velocity) {
        return Mathf.Approximately(velocity, 0);
    }

    // 走路 / 彈跳的動畫速度
    private void WalkAnimatorSpeed(float speed) {

        if (IsVelocityZero(rigidbody2D.velocity.y)) {
            animator.speed = speed * 0.5f;
        } else {
            animator.speed = 1.0f;
        }
    }
}

完成遊戲

產生重複物件

攝影機的處理

  • 我們可以看到往上跳的時候畫面並沒有跟著往上,這時候就要讓攝影機跟著角色一起移動。
using UnityEngine;

public class CameraController : MonoBehaviour {

    private readonly string playerObjectName = "cat";

    private GameObject player;

    void Start() {
        InitPlayer();
    }

    void Update() {
        CameraPostion();
    }

    /// 找尋主角物件
    private void InitPlayer() {
        player = GameObject.Find(playerObjectName);
    }

    /// 攝影機的位置 (跟著主角移動)
    private void CameraPostion() {
        Vector3 playerPosition = player.transform.position;
        transform.position = new Vector3(transform.position.x, playerPosition.y, transform.position.z);
    }
}

碰到旗子了?

public class PlayerController : MonoBehaviour {
    // @override => 碰到旗子的話,就回到首頁
    private void OnTriggerEnter2D(Collider2D collision) {
        Debug.Log("碰到旗子了");
    }
}

切換開始 / 結束場景

  • 首先建立一個新的場景叫「ClearScene」,放上背景圖之後,再建立一個空白的物件,利用UnityEngine.SceneManagementLoadScene()點擊畫面來切換場景。但是在執行的時候會發現無法切換,這是因為場景沒有「註冊」的緣故,請選擇「File -> Build Setting -> Add Open Secens」加入開啟中的場景,注意有前後次序之分。最後再將過關與掉下去的情況都回到首頁即可。
using UnityEngine;
using UnityEngine.SceneManagement;

public class ClearDircetor : MonoBehaviour {

    private readonly string sceneName = "GameScene";

    void Update() { changeScene(); }

    // 切換場景
    private void changeScene() {
        if (Input.GetMouseButtonDown(0)) { SceneManager.LoadScene(sceneName); }
    }
}
using UnityEngine;
using UnityEngine.SceneManagement;

public class PlayerController : MonoBehaviour {

    private readonly string sceneName = "ClearScene";

    // @override => 碰到旗子的話,就回到首頁
    private void OnTriggerEnter2D(Collider2D collision) {
        GotoClearScene();
    }

    // 讓主角向上跳 (往上推 / 防止兩段跳 / 切換動畫Trigger / 掉下去回到首頁)
    private void PlayerJump(float force) {

        if (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0)) {
            if (IsVelocityZero(rigidbody2D.velocity.y)) {
                animator.SetTrigger(trigger);
                rigidbody2D.AddForce(transform.up * force);
            }
        }

        if (transform.position.y < -10) {
            GotoClearScene();
        }
    }

    // 回到首頁
    private void GotoClearScene() {
        SceneManager.LoadScene(sceneName);
    }
}

範例程式碼下載

後記

  • 終於完成了,做遊戲要考慮到的東西真的好多,沒有想像中的簡單,這篇文章寫得好久呀,希望下次學學3D的製作,自己再加加油吧,希望能出個小品遊戲上架。