【Unity 3D 2019】讓喵喵不再是阿飄?物理屬性與動畫
在前面幾篇的介紹之後,這裡要做一個比較完整的DEMO,利用Unity的Physics屬性來產生碰撞跟移動的效果,其實在上一篇中使用程式去改變物體坐標位置也是可以的,不過WYSIWYG - 所見即所得的操作還是比較親民一些,動作也比較流暢。另外也會使用Animation來做簡單的2D動畫(連續換圖?),現在我們就開始吧。
遊戲製作
放入素材
- 新增一個「2D專案」後,將遊戲中所需要的圖片拉到專案中,相對的位置調整好,在這裡比較要注意的是,物件是有「先後順序」關係的,所以有時候在畫面上看不到,可以去調Order in layer的大小,數字越大的越前面(越晚畫)。
加上鉛塊?
- 加上Rigidbody 2D - 剛體之後物體便有了重量 (綁鉛塊?),就會往下掉;這裡比較要注意的是,為了不要讓雲朵也往下掉,要去設定它的Rigidbody 2d -> Body Type -> Kinematic。
加上碰撞範圍
- 加上Collider 2D - 剛體之後物體可以互相碰到,不會穿過去 (阿飄?),當然,它的形狀也有很多種,有Circle Collider 2D - 圓的、有Box Collider 2D - 方的…。
讓喵喵能跑能跳
- 在程式方面,主要是利用Rigidbody 2D的AddForce()來讓物體移動,跟使用上一篇不同的是,這裡這用「推的」方式去處理。這裡要注意的是主角要設定Rigidbody 2D -> Constraints -> Freeze Rotation z,不然主角走一走就會旋轉,滿好笑的。
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);
}
}
}
建立動畫
建立移動動畫
- 在這裡我們要利用Unity強大的Mecanim動作系統來處理動畫。首先選擇主角,然後選Window -> Animation -> Animation開啟動畫視窗,然後建立一個Walk.anim的動畫物件,再來新增Add Property -> Sprite Renderer -> Sprite,最後把走路的圖放上去就可以了。完成後,按下播放鍵就可以看到成果。
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;
}
}
建立彈跳動畫
- 建立彈跳動畫跟建立移動動畫的動作大同小異,大家可以自己看看。
動畫的初換
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);
}
}
碰到旗子了?
- 接下來我們在旗子上加上Box Collider 2D元件,然後將is Trigger功能打開,然後當主角碰到旗子時,就會觸發OnTriggerEnter2D()。
public class PlayerController : MonoBehaviour {
// @override => 碰到旗子的話,就回到首頁
private void OnTriggerEnter2D(Collider2D collision) {
Debug.Log("碰到旗子了");
}
}
切換開始 / 結束場景
- 首先建立一個新的場景叫「ClearScene」,放上背景圖之後,再建立一個空白的物件,利用UnityEngine.SceneManagement的LoadScene()點擊畫面來切換場景。但是在執行的時候會發現無法切換,這是因為場景沒有「註冊」的緣故,請選擇「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的製作,自己再加加油吧,希望能出個小品遊戲上架。