unity學習筆記(三)

StaDark發表於2024-09-01

綜合練習小案例

玩家控制

基本流程

  • 設定移速(全域性,以便在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()

相關文章