筆記42 FSM有限狀態機

傳統編碼

拖放模型。設定動畫。

在這裏插入圖片描述
在這裏插入圖片描述

代碼:Player1Controller+出現的小bug

 //拿到動畫控制器
    private Animator ani;

    void Start()
    {
        ani = GetComponent<Animator>();
    }

    void Update()
    {
        //拿到軸
        float vertical = Input.GetAxis("Vertical");
        float horizontal = Input.GetAxis("Horizontal");
        //做成向量
        Vector3 dir = new Vector3(horizontal,0, vertical);
        //移動
        if (dir != Vector3.zero)
        {
            //旋轉
            transform.rotation = Quaternion.LookRotation(dir);
            //前進
            transform.Translate(Vector3.forward * 3 * Time.deltaTime);
            //播放跑步動畫
            ani.SetBool("isRun", true);
        }
        else
        {
            ani.SetBool("isRun", false);
        }

        //攻擊
        if (Input.GetKeyDown(KeyCode.Space))
        {
            //SetTrigger觸發
            ani.SetTrigger("attack");
        }
        //再有類似的,就繼續添加if(){}
    }

在這裏插入圖片描述

缺點多於優點

再有類似的,就繼續添加if(){}。
整個Update裏寫了一堆if。
一個if就代表一個狀態。
如果寫了二十多個if,一是,修改起來麻煩;
二是,它是按照順序來的,有時修改了中間的代碼,下面的代碼可能出現不知解的bug。
三是,用這種方式寫連擊,很麻煩。
四是,比如攻擊動畫沒做完,按跳躍是可以跳躍的,這是不允許的。
有個優點,就是與玩家相關的代碼全寫在這一個文件裏了。

有限狀態機有兩種簡單方式:方法、類

法一:方法+代碼:Player2Controller

在這裏插入圖片描述

//枚舉。玩家的狀態
public enum PlayerState
{
    idle,
    run,
    attack
}

public class Player2Controller : MonoBehaviour
{
    //動畫
    private Animator ani;
    //當前的狀態。默認是站立
    private PlayerState state = PlayerState.idle;

    void Start()
    {
        //獲取ani
        ani = GetComponent<Animator>();
    }

    void Update()
    {
        //switch:切換小括號裏內容的取值。點擊“補齊代碼”。
        switch (state)
        {
            case PlayerState.idle:
                //補充:如果是站立,就調用站立的方法。
                idle();
                break;
            case PlayerState.run:
                run();
                break;
            case PlayerState.attack:
                attack();
                break;
        }
    }

    //有幾個狀態,就寫幾個方法。此處有三個。
    //每個裏面,寫每個要做的事。
    void idle()
    {
        //站立動畫
        ani.SetBool("isRun", false);
        //得到軸,形成向量
        float vertical = Input.GetAxis("Vertical");
        float horizontal = Input.GetAxis("Horizontal");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        //判斷向量不爲零,說明按了AWSD某個按鍵了,跑步。
        if (dir != Vector3.zero)
        {
            //切換到跑步狀態
            state = PlayerState.run;
        }
        //攻擊
        if (Input.GetKeyDown(KeyCode.Space))
        {
            //切換到攻擊狀態
            state = PlayerState.attack;
        }
    }
    void run()
    {
        //跑步動畫
        ani.SetBool("isRun", true);
        //得到軸,形成向量
        float vertical = Input.GetAxis("Vertical");
        float horizontal = Input.GetAxis("Horizontal");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        //判斷向量不爲零,說明按了AWSD某個按鍵了,跑步。
        if (dir != Vector3.zero)
        {
            //朝向向量
            transform.rotation = Quaternion.LookRotation(dir);
            //前進
            transform.Translate(Vector3.forward * 3 * Time.deltaTime);
        }
        else
        {
            //切換動畫狀態
            state = PlayerState.idle;
        }
    }
    void attack()
    {
        //觸發攻擊
        ani.SetTrigger("attack");
        //如果當前狀態名稱(方塊上的名稱)不是攻擊,說明攻擊的動畫播放完了。
        //if如果 (!不是ani動畫.GetCurrentAnimatorStateInfo狀態(0).IsName名稱("attack1"攻擊))
        if (!ani.GetCurrentAnimatorStateInfo(0).IsName("attack1"))
        {
            //切換站立狀態
            state = PlayerState.idle;
        }
    }
}

法二:類+代碼

需要寫幾個類

在這裏插入圖片描述
在這裏插入圖片描述

代碼:FSMState

//abstract抽象類(不能被實例化。必須有子類繼承,對應關鍵詞override)
public abstract class FSMState 
{
    //當前狀態ID。它是int類型,因爲玩家的狀態我們是寫在枚舉裏的。
    public int stateID;
    //狀態擁有者。需要把角色控制類傳過來
    public MonoBehaviour mono;
    //狀態所屬管理器
    public FSMManager fsmManager;

    //初始化方法
    public FSMState(int stateID,MonoBehaviour mono,FSMManager manager)
    {
        //賦值
        this.stateID = stateID;
        this.mono = mono;
        this.fsmManager = manager;
    }

    //抽象方法(子類必須實現的方法)只能包含在抽象類當中。
    //用虛方法(允許子類重寫方法)也可以,類就不用寫成抽象類了。
    public abstract void OnEnter();
    public abstract void OnUpdate();
}

代碼:FSMManager

public class FSMManager 
{
    //狀態列表
    public List<FSMState> stateList = new List<FSMState>();
    //當前的狀態(索引)。-1表示沒給狀態。
    public int currentIndex = -1;
    
    //改變狀態
    public void ChangeState(int StateID)
    {
        //當前狀態=傳進來的狀態。
        currentIndex = StateID;
        //stateList[currentIndex]從數組裏取出對應的狀態
        //.OnEnter()調用一次OnEnter方法,確保切換到新狀態的第一時間執行OnEnter方法
        stateList[currentIndex].OnEnter();       
    }

    //更新方法。這個Update是不會調用的,需要玩家裏的Update協助調用。
    public void Update()
    {
        //如果當前有狀態
        if (currentIndex != -1)
        {
            //stateList[currentIndex]從列表裏拿到當前狀態
            //.OnUpdate()調這個狀態裏的OnUpdate方法,確保OnUpdate會被一直調用。
            stateList[currentIndex].OnUpdate();
        }
    }
}

代碼:IdleState : FSMState

/繼承於抽象類FSMState。點擊“生成構造函數”“實現抽象類”
public class IdleState : FSMState
{
    //必須實現構造方法。爲了new的時候更方便。 new IdleState(小括號裏提示寫三個參數)
    public IdleState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager)
    {        
    }
    //狀態的初始化方法,只調用一次
    public override void OnEnter()
    {
        //mono狀態擁有者,其實就是PlayerController
        mono.GetComponent<Animator>().SetBool("isRun", false);
    }
    //狀態的刷新方法,循環調用
    public override void OnUpdate()
    {
        //監聽跑步狀態,切換。
        //得到軸,組成向量,判斷
        float vertical = Input.GetAxis("Vertical");
        float horizontal = Input.GetAxis("Horizontal");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        if (dir != Vector3.zero)
        {
            //fsmManager狀態所屬於的管理類。(int)PlayerState.run把枚舉轉成int類型
            fsmManager.ChangeState((int)PlayerState.run);
        }

        //監聽攻擊狀態,切換。
        if (Input.GetKeyDown(KeyCode.Space))
        {
            fsmManager.ChangeState((int)PlayerState.attack);
        }
    }
}

代碼:RunState : FSMState

public class RunState : FSMState
{
    public RunState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager)
    {           
    }

    public override void OnEnter()
    {
        //跑步動畫狀態
        mono.GetComponent<Animator>().SetBool("isRun", true);
    }

    public override void OnUpdate()
    {
        //得到軸,形成向量,判斷,前進
        float vertical = Input.GetAxis("Vertical");
        float horizontal = Input.GetAxis("Horizontal");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        if (dir != Vector3.zero)
        {
            //朝向向量
            mono.transform.rotation = Quaternion.LookRotation(dir);
            //前進
            mono.transform.Translate(Vector3.forward * 3 * Time.deltaTime);
        }
        else
        {
            //切換到站立狀態
            fsmManager.ChangeState((int)PlayerState.idle);
        }
    }
}

代碼:AttackState : FSMState

public class AttackState : FSMState
{
    public AttackState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager)
    {
    }

    public override void OnEnter()
    {
        mono.GetComponent<Animator>().SetTrigger("attack");
    }

    public override void OnUpdate()
    {
        //判斷當前狀態如果不是攻擊1,就切換到站立
        if (!mono.GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).IsName("attack1"))
        {
            fsmManager.ChangeState((int)PlayerState.idle);
        }
    }
}

代碼:PlayerState3

//枚舉。玩家的狀態。(因爲寫第二個恐龍時,寫了枚舉PlayerState,所以此處改名爲PlayerState3)
//這個枚舉不妨在這的話,在下面的那行代碼無用。
public enum PlayerState3
{
    idle,
    run,
    attack
}

public class player3Controller : MonoBehaviour
{
    //狀態管理類
    private FSMManager fsmManager;

    void Start()
    {
        //new一個
        fsmManager = new FSMManager();
        //實例化各種狀態。(0索引與枚舉裏的一致, this傳進來的所屬管理類, fsmManager屬於哪個管理器類,剛剛new的)
        IdleState idle = new IdleState(0, this, fsmManager);
        RunState run = new RunState(1, this, fsmManager);
        AttackState attack = new AttackState(2, this, fsmManager);
        //把三種狀態添加到管理類列表
        fsmManager.stateList.Add(idle);
        fsmManager.stateList.Add(run);
        fsmManager.stateList.Add(attack);
        //管理類默認切換成站立狀態
        fsmManager.ChangeState((int)PlayerState.idle);
    }

    void Update()
    {
        //調用管理類裏的Update
        //因爲FSMManager裏的Update方法不會自己運行,此處,幫助它運行。
        //進而,它就會調用當前狀態裏的OnUpdate。
        fsmManager.Update();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章