RPG遊戲《黑暗之光》流程介紹與代碼分析之(十二):怪物系統的實現

第十二章:怪物系統

怪物功能是交互的重要部分,涉及到任務系統、人物狀態系統等等,設計起來也較爲複雜。
首先將Model中的小狼拖入場景,命名爲WolfBaby,並添加動畫信息

12.1 小狼的狀態切換和移動

爲其添加一個腳本WolfBaby以控制其行爲
using UnityEngine;
using System.Collections;

public enum WolfBabyState{    //幾種枚舉狀態,對應怪物的不同形態
    Idle,
    Walk,
    Attack,
    Death
}

public class WolfBaby : MonoBehaviour {
    

    public WolfBabyState state = WolfBabyState.Idle;    //默認動畫爲站立
    public string aniName_death;    //對應不同的動畫
    public string aniName_walk;
    public string aniName_idle;
    public string currentAniName;    //當前播放的動畫
    public float changeTime = 3;    //動畫改變的時間間隔
    public float timer = 0;    //定時器
    void Awake()
    {
        currentAniName = aniName_idle;
    }

    void Update()
    {
        if (state == WolfBabyState.Death)    //根據state判斷當前的狀態,並做出相應操作
        {
            animation.CrossFade(aniName_death);
        }
        else if(state == WolfBabyState.Attack)    //爲攻擊狀態
        {
                //todo,對應下文的AutoAttack
        }
        else    //巡邏狀態
        {
            animation.CrossFade(currentAniName);    
            timer += Time.deltaTime;    定時器開始工作
            if(timer >= changeTime)    //當定時器大於3秒時
            {
                timer = 0;
                RandomState();    //隨機生成一種動畫
                animation.CrossFade(currentAniName);
            }
        }
    }

    void RandomState()
    {
        int value = Random.Range (0, 2);
        if (value == 0)
        {
            currentAniName = aniName_idle;
                
        } else
        {
            currentAniName = aniName_walk;    
        }
    }

}
可以看到小狼有兩種不同的動畫,每隔3秒改變一次,但在Walk狀態下的小狼無法移動,需要改進
因此添加一個Character Controller,用以控制小狼的移動,並在state == WolfBabyState.Walk的時候調用SimpleMove()控制移動。
    private CharacterController cc;
    public float speed = 0.5f;
    void Awake()
    {
        cc = this.GetComponent<CharacterController> ();
    }

            if(currentAniName == aniName_walk)
            {
                cc.SimpleMove(transform.forward * speed);
            }
此時的小狼只會朝一個方向進行移動,因此我們修改RandomState()中的設置,在切換到Walk狀態時給小狼一個隨機的方向。
    void RandomState()
    {
        int value = Random.Range (0, 2);
        if (value == 0)
        {
            currentAniName = aniName_idle;
                
        } else
        {
            if(currentAniName != aniName_walk)    //當value爲1時,即下一個播放狀態爲Walk,但當前狀態仍爲Idle的時候,改變小狼朝向的角度
            {
                transform.Rotate(transform.up * Random.Range(0,361));    //隨機一個角度,改變朝向
            }
            currentAniName = aniName_walk;    
        }
    }

12.2 小狼遭受攻擊

處理完移動行爲後,接下來還有自動攻擊,被攻擊等功能,先處理被攻擊功能。爲小狼創建一個血量。
    public int hp = 100;    //怪物血量
    public float missRate = 0.2;    //怪物閃避率
    public void BeDamaged(int attackValue)
    {
        int value = Random.Range (0f, 1f);    //生成一個0~1之間的隨機數,與missRate比較,若小於,則產生miss
        if (value > this.missRate)
        {
            this.hp -= attackValue;    //扣除血量
            if(hp <= 0)
            {
                state = WolfBabyState.Death;    //播放死亡動畫,2秒後銷燬
                Destroy(this.gameObject,2);
            }
        }
    }
這樣達到了扣血的目的,但被攻擊時的效果需要直觀地顯示出來,以更好地提示用戶,因此我們通過Skinned Mesh Renderer組建控制怪物顏色的改變,在WolfBaby中定義兩種顏色,對應普通狀態和受擊時的顏色。
我們定義一般狀態的顏色normalColor,並通過協程控制顏色的改變,起到傷害的顏色效果
    private Color normalColor;    //存儲原始的顏色,當被擊效果結束後可以返回原樣
    private GameObject wolfBody;
    void Awake()
    {
        wolfBody = transform.Find ("Wolf_Baby").gameObject;    //訪問到控制顏色的子物體Wolf_Baby
        normalColor = wolfBody.renderer.material.color;
    }
    IEnumerator ShowWolfRed()    //通過協程代替計時器,更加簡單,協程的概念見https://blog.csdn.net/jasonwang18/article/details/55519165
    {
        wolfBody.renderer.material.color = Color.red;
        yield return new WaitForSeconds (1f);
        wolfBody.renderer.material.color = normalColor;
    }
並在受到傷害時調用StartCoroutine(ShowWolfRed())即可

12.3 MISS效果

攻擊怪物時加入一個閃避效果可以提高遊戲體驗,因此我們爲Miss效果添加一個AudioClip,實現提示效果,在if (value > this.missRate)時,添加
AudioSource.PlayClipAtPoint(missSound,transform.position);
導入HUD Text創建Miss效果,將HUD Text放到UI root下,

它包含的UIFollow Target用以跟隨主角或怪物,文本HUD Text用以顯示Miss或扣血效果。在UI root下創建一個Invisible Widget,命名爲HUDTextParent,併爲其創建一個腳本HUDTextParent,並設置爲單例模式。在每個物體創建時新增一個HUD Text
之後在WolfBaby中添加一個Empty物體,用以存放HUD Text,命名爲WolfHUDtext,並將WolfBaby做成一個Prefab

    private GameObject wolfHUDTextGO;    //WolfBaby下的HUD Text
    private GameObject HUDTextGO;    //UI root下的HUDTextParent下的HUD Text
    public GameObject HUDTextPrefab;    //HUD Text的prefab,直接導入即可
    private HUDText showText;    //HUDTextGO下的Text信息,控制顯示
    private UIFollowTarget followTarget;    //HUDTextGO下的位置信息,控制位置
    void Awake()
    {
        wolfHUDTextGO = transform.Find ("WolfHUDText").gameObject;
    }

    void Start()
    {
        HUDTextGO = GameObject.Instantiate (HUDTextPrefab, Vector3.zero, Quaternion.identity) as GameObject;    //HUDTextParent下的HUD Text由Prefab得到
        HUDTextGO.transform.parent = HUDTextParent._instance.gameObject.transform;    //並將這一Prefab作爲HUDTextParent的子類
        showText = HUDTextGO.GetComponent<HUDText> ();    //取得文本和位置信息
        followTarget = HUDTextGO.GetComponent<UIFollowTarget> ();
        followTarget.target = wolfHUDTextGO.transform;    //followTarget中的位置跟隨小狼的移動
        followTarget.gameCamera = Camera.main;    //followTarget中的Camera爲main Camera
        followTarget.uiCamera = UICamera.current.GetComponent<Camera> ();    //followTarget中的UICamera爲current Camera
    }
初始化完成後,在Miss時進行測試
    public void BeDamaged(int attackValue)
    {
        if (value > this.missRate)
        {   
        }
        else
        {
            AudioSource.PlayClipAtPoint(missSound,transform.position);
            showText.Add("Miss",Color.gray,1);    //添加顯示的文本、顏色和時間
        }
    }
我們添加一個方法,作爲模擬攻擊測試。在Update()中,通過按下“A”鍵起到模擬的作用
        if (Input.GetKeyDown (KeyCode.A))
        {
            BeDamaged(1);    
        }


被攻擊時,小狼會變爲紅色,但miss文字的顯示有問題(上圖灰色部分所示,字體過於巨大)
問題出現在
        HUDTextGO = GameObject.Instantiate (HUDTextPrefab, Vector3.zero, Quaternion.identity) as GameObject;    
        HUDTextGO.transform.parent = HUDTextParent._instance.gameObject.transform;    
即創建HUDText物體時的問題,我們用NGUITool創建可以避免這一情況。
HUDTextGO = NGUITools.AddChild (HUDTextParent._instance.gameObject, HUDTextPrefab);
結果如下。

在怪物被殺死時,我們要銷燬WolfBaby,並且銷燬HUDText,在WolfBaby腳本中添加
      if (hp <= 0)
            {
                state = WolfBabyState.Death;
                Destroy (this.gameObject, 2);
                GameObject.Destroy(HUDTextGO);
                        
            }

12.4 敵人的自動攻擊部分

自動攻擊的設計關係到AI的智商,這裏只涉及基本的AI操作,一些高端的“拉怪”操作不在考慮範圍內。。。

12.4.1 自動攻擊邏輯

當state == WolfBabyState.Attack時,我們需要讓小狼自動攻擊。攻擊包括下面幾個屬性
    public int attackValue;    //攻擊傷害
    public string aniName_normalAttack;    //正常攻擊
    public string aniName_crazyAttack;    //瘋狂攻擊,提升attackRate,即攻擊速率
    public string aniName_nowAttack;    //當前攻擊的種類
    public float normalAttackTime;    //普通攻擊消耗時間
    public float crazyAttackTime;    //瘋狂攻擊消耗時間
    public float crazyAttackRate;    //瘋狂攻擊觸發的概率
    public int attackRate = 1;    //攻擊速率,默認爲1秒1次
    public float attackTimer = 0;    //攻擊的計時器,決定attackRate
    public Transform target;    //攻擊目標,當觸發BeDamage函數時獲取目標
屬性如下

自動攻擊的邏輯爲
  • 當人物與小狼的距離小於可攻擊距離時,進行攻擊    (distance < acceptAttackDistance ,攻擊)
  • 當人物與小狼的距離大於可攻擊距離並且小於最大攻擊距離時,移動到最小距離之內,再攻擊    (acceptAttackDistance < distance < maxAcceptAttackDistance,,移動再攻擊)
  • 當人物與小狼距離大於最大攻擊距離時,返回巡邏狀態 (distance > maxAcceptAttackDistance ,取消攻擊狀態)
因此對函數AutoAttack()地設置如下
    public float minAttackDistance = 2f;
    public float maxAttackDistance = 5f;

    void AutoAttack()
    {
        if (target != null)    //取得目標
        {
            float distance = Vector3.Distance(target.position,transform.position);    //計算距離
            if(distance > maxAttackDistance)    //大於最大攻擊距離時,切換到巡邏狀態
            {
                target = null;
                state = WolfBabyState.Idle;    
            }
            else if(distance <= minAttackDistance)    //小於最小攻擊距離時,攻擊
            {

            }
            else    //介於最小與最大攻擊距離時,移動到攻擊距離內再攻擊
            {
                transform.LookAt(target);
                cc.SimpleMove(transform.forward * speed);
                animation.CrossFade(aniName_walk);
            }
        }
        else
        {
            state = WolfBabyState.Idle;    
        }
    }

12.4.2 攻擊行爲的切換與播放

攻擊行爲可以拆分成兩部分:攻擊和距離下一次攻擊開始的休息時間。因此我們將攻擊狀態分爲3種:普通攻擊、瘋狂攻擊以及攻擊的休息間隔

先考慮distance <= minAttackDistance的情況
else if(distance <= minAttackDistance)
            {
                attackTimer += Time.deltaTime;    //計時器開啓
                animation.CrossFade(aniName_nowAttack);    //先播放當前攻擊動畫
                if(aniName_nowAttack == aniName_normalAttack)    //判斷當前攻擊動畫的種類
                {
                    if(attackTimer >= normalAttackTime)    //大於播放時間後,造成傷害
                    {    
                        //todo,造成傷害
                        animation.CrossFade(aniName_idle);
                    }
                }
                else if(aniName_nowAttack == aniName_crazyAttack)
                {
                    if(attackTimer >= crazyAttackTime)
                    {
                        //todo
                        animation.CrossFade(aniName_idle);
                    }
                }
                if(attackTimer > (1f/attackRate))    //如果大於攻擊休息間隔時,隨機一種攻擊動畫並重置計時器,實現一個攻擊的循環
                {
                    RandomAttack();   
                    attackTimer = 0;
                }
            }

    void RandomAttack()
    {
        float value = Random.Range (0f, 1f);
        if (value > crazyAttackRate)
        {
            aniName_nowAttack = aniName_normalAttack;        
        } else
        {
            aniName_nowAttack = aniName_crazyAttack;    
        }
    }
這樣就實現了攻擊動畫和攻擊行爲的循環,但暫時沒有取得target目標,target要在角色對小狼造成傷害後將主角信息傳遞給小狼,之後進行補充。

ps:(5月3日補充)中型狼和Boss狼的創建。

中型狼和大型BOSS狼
中型狼和大型狼的素材都在RPG——>Model——>Model Enemy之中,拖入場景之中

與小狼類似,我們爲其添加Animation動畫和角色控制器,我們使用WolfBaby的腳本,稍作修改即可。
主要涉及修改部分:Animation動畫、自身gameObject的指定、屬性值以及攻擊距離(大狼體積較大,攻擊距離過小會導致無法正常攻擊)


發佈了96 篇原創文章 · 獲贊 140 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章