綜合練習小案例
玩家控制
基本流程
-
設定移速(全域性,以便在unity介面中直接修改)(如
public float speed = 5;
) -
將移動單獨封裝成方法
-
在移動方法中完成獲取輸入、設定移動動畫、設定移動時朝向以及移動角色
private void Move() { //獲取輸入 int inputX = (int)UnityEngine.Input.GetAxisRaw("Horizontal"); int inputY = (int)UnityEngine.Input.GetAxisRaw("Vertical"); //設定動畫 bool isMoving = (inputX!= 0 || inputY!= 0); animator.SetBool("Move", isMoving); //設定角色的朝向 if (inputX > 0) transform.localScale = new Vector3(5, 5, 1); else if (inputX < 0) transform.localScale = new Vector3(-5, 5, 1); //移動角色 Vector3 moveDirection = new Vector3(inputX, inputY, 0); transform.Translate(moveDirection * moveSpace * Time.deltaTime); }
要點:
- 最好給玩家同時新增碰撞體(人形的話橢圓形最好)和剛體,並設定為觸發器,以在節約資源的情況下完成與其他物體的互動(物理碰撞,被攻擊死亡之類的)
- 當獲取X,Y軸輸入時,
GetAxis()
函式傳遞的值型別為float
,移動效果為有慣性;
GetAxisRaw()
函式傳遞的值型別為int
,移動效果為無慣性。注意區分並轉換型別
發射子彈
基本流程
-
在角色上建立一個空物體作為子彈發生點
-
需要給操作角色封裝一個攻擊方法的同時,給子彈一個執行指令碼
-
攻擊方法:
例項化子彈(gemPrefab
需要在unity中選中) -> 子彈發射(移動)public GameObject gemPrefab; public Transform gemShootPoint; public float shootCD = 1; private float shootCDTimer; private void Attack() { //子彈冷卻 if (shootCDTimer > 0) { shootCDTimer -= Time.deltaTime; //如果子彈冷卻時間未到,則不發射子彈 return; } //按下空格發射子彈 if (UnityEngine.Input.GetKeyDown(KeyCode.Space)) { //設定子彈冷卻時間 shootCDTimer = shootCD; //例項化一個Gem子彈 GameObject gem = GameObject.Instantiate(gemPrefab); //設定子彈的位置 gem.transform.position = gemShootPoint.position; //設定子彈的方向 bool isRight = transform.localScale.x == 5; Vector3 moveDir = isRight? Vector3.right * 5 : Vector3.left * 5; gem.GetComponent<Gem>().Init(moveDir); } }
-
子彈指令碼:
設定子彈基本屬性(速度、銷燬時間)public class Gem : MonoBehaviour { public float moveSpeed = 5f; //子彈移動方向 private Vector3 moveDir; public float destroyTime = 2; private float destroyTimer; //初始化 public void Init(Vector3 dir) { moveDir = dir; destroyTimer = destroyTime; } void Update() { //gem子彈移動 transform.Translate(moveDir * moveSpeed * Time.deltaTime, Space.World); //gem子彈銷燬 if (destroyTimer <= 0) { Debug.Log("Gem Destroyed"); Destroy(gameObject); } //gem子彈銷燬計時器 else { destroyTimer -= Time.deltaTime; } } }
怪物生成
基本流程
- 確定好怪物生成的座標點位
- 確定好怪物生成的時間間隔(CD)
public class MonsterManager : MonoBehaviour
{
//序列化(將資料視覺化到unity的Inspector皮膚中)
[System.Serializable]
//怪物生成點
public class MonsterSpawnPoint
{
//生成點
public Transform spawnPoint;
//怪物預製體
public GameObject monsterPrefab;
//生成間隔
public float spawnInterval;
//計時器(需要非序列化)
private float spawnTimer;
public void Update(float deltaTime)
{
//更新計時器
spawnTimer -= Time.deltaTime;
if (spawnTimer <= 0)
{
spawnTimer = spawnInterval;
//生成怪物
GameObject monster = GameObject.Instantiate(monsterPrefab);
//將不同點生成的怪物歸類到對應點下
monster.transform.SetParent(spawnPoint);
//設定怪物位置
monster.transform.position = spawnPoint.position;
}
}
}
//怪物生成點陣列
public MonsterSpawnPoint[] spawnPoints;
private void Update()
{
//更新所有生成點
for (int i = 0; i < spawnPoints.Length; i++)
{
spawnPoints[i].Update(Time.deltaTime);
}
}
}
建立MonsterManager
後可以再在其下設定好預設的點位Points
怪物邏輯
移動邏輯
基本流程
-
確定怪物移動邏輯是當玩家存在時,向玩家移動
將Player
設定為單例模式//單例模式:透過將當前Player物件賦值給靜態變數instance,確保遊戲中只有一個Player例項,並且可以透過Player.instance全域性訪問。 public static Player instance; //當遊戲開始時,將當前Player物件設定為靜態變數instance。 //優點:無論在遊戲的任何地方,我們都可以透過Player.instance來訪問這個唯一的Player例項,從而更方便地進行全域性訪問和控制 //初始化:在遊戲開始前進行必要的初始化操作,確保Player物件在遊戲中的唯一性和可訪問性。 private void Awake() { instance = this; }
Monster
檢查玩家是否存在,不存在的話就不執行移動邏輯if (Player.instance == null) return;
-
怪物執行向玩家移動的邏輯
//利用向量知識,將怪物的方向始終對準玩家方向 Vector3 moveDir = (Player.instance.transform.position - transform.position).normalized; //怪物移動 transform.Translate(Time.deltaTime * moveSpeed * moveDir, Space.World); //判斷怪物移動時的左右方向 bool isRight = transform.position.x > Player.instance.transform.position.x; //根據移動時單獨左右方向旋轉貼圖 if (isRight) transform.localScale = new Vector3(-5, 5, 1); else transform.localScale = new Vector3(5, 5, 1);
受擊死亡邏輯
基本流程
-
為怪物新增HP變數,並設定好觸發器事件函式並新增死亡動畫事件
public int HP; //動畫控制器 public Animator animator; private void OnTriggerEnter2D(Collider2D collision) { if (HP == 0) return; //接觸子彈 if (collision.gameObject.CompareTag("Bullet")) { //子彈碰撞後,怪物生命值減一,子彈消失 Destroy(collision.gameObject); HP-=1; //HP歸零時觸發 if (HP == 0) { animator.SetTrigger("Death"); } } } #region 動畫事件 //死亡動畫結束 private void OnDeathAnimationEnd() { //物件消亡 Destroy(gameObject); } #endregion
-
為怪物設定好死亡動畫,並在動畫最後一陣新增動畫事件(選定指令碼里寫好的)
設定動畫狀態,並把條件設定為觸發器(無後搖)
-
將怪物新增上碰撞體元件,並勾選觸發器選項
-
將子彈新增剛體和碰撞體,鎖定三個方向的軸並加上
“Bullet”
標籤
滑鼠指向為攻擊方向
基本流程
-
透過攝像機(
Camera
獲取滑鼠的座標),再將角色朝向翻轉邏輯和攻擊方向邏輯改寫//使用new關鍵字明確地表示camera欄位是當前類特有的,與基類中的任何同名欄位無關。 public new Camera camera; void Update() { //獲取滑鼠位置 Vector3 mousePos = camera.ScreenToWorldPoint(Input.mousePosition); mousePos.z = 0; Move(mousePos); Attack(mousePos); } private void Move(Vector3 mousePos) { //獲取輸入 int inputX = (int)UnityEngine.Input.GetAxisRaw("Horizontal"); int inputY = (int)UnityEngine.Input.GetAxisRaw("Vertical"); //設定動畫 bool isMoving = (inputX!= 0 || inputY!= 0); animator.SetBool("Move", isMoving); //設定角色的朝向 if (mousePos.x > transform.position.x) transform.localScale = new Vector3(5, 5, 1); else if (mousePos.x < transform.position.x) transform.localScale = new Vector3(-5, 5, 1); //移動角色 Vector3 moveDirection = new Vector3(inputX, inputY, 0); transform.Translate(moveDirection * moveSpace * Time.deltaTime); } private void Attack(Vector3 mousePos) { //子彈冷卻 if (shootCDTimer > 0) { shootCDTimer -= Time.deltaTime; //如果子彈冷卻時間未到,則不發射子彈 return; } //滑鼠左鍵按下發射子彈,持續按下可以持續發射子彈 if (Input.GetMouseButton(0)) { //設定子彈冷卻時間 shootCDTimer = shootCD; //例項化一個Gem子彈 GameObject gem = GameObject.Instantiate(gemPrefab); //設定子彈的位置 gem.transform.position = gemShootPoint.position; //設定子彈的方向 Vector3 moveDir = (mousePos - transform.position).normalized; gem.GetComponent<Gem>().Init(moveDir); } }
玩家生命值以及狀態(模擬UI)
基本流程
-
為解決人物移動時,
Camera
會帶著UI一起翻轉的問題,需要將Camare
單獨列出並使用指令碼的方法讓Camera
跟隨玩家視角
-
為保證能穩定獲取玩家最後一幀的位置,所以使用
LateUpdate
public Transform target; private void LateUpdate() { Vector3 pos = target.position; //避免攝像機與其他素材重疊 pos.z = -10; transform.position = pos; }
可新建一個
CameraPoint
在玩家身上,用於掛起target
-
給玩家新建一個HP欄位並定義屬性,後繼續設定一個HP精靈管理器以及HP狀態精靈陣列
//宣告一個HpSpriteRenderer欄位,用於渲染角色的血條 public SpriteRenderer HpSpriteRenderer; //宣告一個HpSprites欄位,用於儲存角色的血條圖片 public Sprite[] HpSprites; //[SerializeField]:這是一個屬性(Attribute),用於指示Unity在Inspector視窗中顯示私有欄位hp,即使它是私有的。 //private int hp:宣告一個私有的整型變數hp,用於儲存角色的生命值。 [SerializeField] private int hp; //宣告一個公共屬性HP,用於外部訪問和修改私有欄位hp。 public int HP { //返回私有欄位hp的值。 get => hp; //用於設定私有欄位hp的值,並在設定值時更新HpSpriteRenderer的精靈影像。 set { //將傳入的值value賦給私有欄位hp hp = value; HpSpriteRenderer.sprite = HpSprites[hp]; } } private void Awake() { instance = this; //血量初始化 HP = HpSprites.Length - 1; }
-
在unity中拖入對應元件
玩家受傷及死亡
基本流程
-
在怪物的指令碼下設定好玩家觸發到怪物後的事件
private void OnTriggerEnter2D(Collider2D collision) { //接觸玩家 //如果發生碰撞的物體Tag為“Player”的話,呼叫對應元件的Hurt()方法 if (collision.gameObject.CompareTag("Player")) { collision.gameObject.GetComponent<Player>().Hurt(); } }
-
在玩家指令碼下寫好受傷方法,死亡方法以及死亡動畫事件
[SerializeField] private int hp; public int HP { get => hp; set { hp = value; HpSpriteRenderer.sprite = HpSprites[hp]; //將和hp歸零判斷放在欄位中更方便 if (hp == 0) { Death(); } } } public void Hurt() { if (HP <= 0) return; HP-=1; } private void Death() { //觸發死亡動畫條件 animator.SetTrigger("Death"); } #region 動畫事件 //死亡動畫結束 private void OnDeathAnimationEnd() { //隱藏玩家元件 gameObject.SetActive(false); } #endregion
編輯好死亡動畫後,在動畫最後一幀設定動畫事件,選定
OnDeathAnimationEnd()