天天看點

Unity狀态機FSMUnity狀态機FSM

Unity狀态機FSM

一:狀态機介紹

有限狀态機,也稱為 FSM(Finite State Machine) ,這些狀态是有限的、不重疊的,其在任意時刻都處于有限狀态集合中的某一狀态。當其獲得特定輸入時,将從目前狀态轉換到另一個狀态 ,或者仍然保持在目前狀态。

狀态機的應用領域

--- 玩家動作控制:比如一個玩家動作較多,我們可以使用狀态機進行管理

--- UI界面的切換與管理

--- 怪物AI的設計

二:狀态機設計執行個體

我們以案例為基礎,設計一個人物有三種狀态,在三種狀态之間自由切換。

我先把裡面的幾個類描述一下

StateBase:所有狀态基類

StateMachine:狀态機類,負責管理所有的狀态以及切換

PlayerCtrl:玩家控制類,包含一個狀态機以及自身控制邏輯

IdleState,RunState,AttackState三種狀态

StateTemplate<T>:泛型類,為了解決人物和怪物都存在狀态機的時候,可以指定對應的所有者

2.1 設計狀态機基類 

一個狀态機裡面,有很多種狀态,每一種狀态都有很多相似的特征,這裡我們需要一個狀态基類。基類裡面包含所有狀态的基本資訊。

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public abstract class StateBase

{

    /// <summary> 

    /// 每個狀态對應不同的ID号

    /// </summary> 

    public int ID { get; private set; }

    /// <summary> 

    /// 狀态機

    /// </summary> 

    public StateMachine machine;

    /// <summary> 

    /// Construtor

    /// </summary> 

    /// <param name="id">狀态的id号(id号有對應的枚舉)</param> 

    public StateBase(int id)

    {

        ID = id;

}

//進入狀态

public virtual void OnEnter(params object[] args) { }

//狀态停留

public virtual void OnStay(params object[] args) { }

//狀态退出

public virtual void OnExit(params object[] args) { }

    //檢查狀态

 public virtual void OnCheck(params object[] args) { }

}

public class StateTemplate<T> : StateBase

{

    /// <summary> 

    /// 狀态的擁有者

    /// </summary> 

    public T m_owner;

    /// <summary> 

    /// Constructor

    /// </summary> 

    /// <param name="id">狀态的id号</param> 

    /// <param name="owner">狀态的擁有者</param> 

    public StateTemplate(int id, T owner) : base(id)

    {

        m_owner = owner;

    }

}

2.2 狀态機類

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

/// 狀态機

/// </summary>

public class StateMachine

{

    /// <summary>

    /// 狀态緩存

    /// </summary>

    public Dictionary<int, StateBase> m_StateCache;

    /// <summary>

    /// 目前狀态

    /// </summary>

    public StateBase m_CurrentState;

    /// <summary>

    /// 目前狀态的前一個狀态

    /// </summary>

    public StateBase m_PreviousState;

    #region StateMachine  Constructor

    /// <summary>

    /// Constructor

    /// </summary>

    /// <param name="beginState">開始狀态</param>

    public StateMachine(StateBase beginState)

    {

        m_PreviousState = null;

        m_CurrentState = beginState;

        m_StateCache = new Dictionary<int, StateBase>();

        //注冊狀态

        RegisterState(beginState);

        m_CurrentState.OnEnter();

    }

    #endregion

    #region FSMUpdate  狀态機監測狀态

    /// <summary>

    /// 狀态機監測狀态變化

    /// </summary>

    public void FSMUpdate()

    {

       if (m_CurrentState != null)

        {

            m_CurrentState.OnStay();

            m_CurrentState.OnCheck();

        }

    }

    #endregion

    #region TranslateToState 狀态切換

    /// <summary>

    /// 狀态切換

    /// </summary>

    /// <param name="id">目标狀态的id号</param>

    /// <param name="args">可變參數</param>

    public void TranslateToState(int id, params object[] args)

    {

        int key_id = id;

        if (!m_StateCache.ContainsKey(key_id))

        {

            Debug.LogError("The key is not Exist");

            return;

        }

        //目前狀态退出

        m_CurrentState.OnExit();

        //儲存目前狀态為下一個新狀态的前一個狀态

        m_PreviousState = m_CurrentState;

        //目前狀态更新到下一個新狀态

        m_CurrentState = m_StateCache[key_id];

        //新的狀态開始進入

        m_CurrentState.OnEnter(args);

    }

    #endregion

    #region RegisterState   注冊一個新的狀态到緩存中

    /// <summary>

    /// 注冊一個新的狀态到緩存中

    /// </summary>

    /// <param name="aState">新狀态</param>

    public void RegisterState(StateBase aState)

    {

        int id = aState.ID;

        //狀态是否緩存了

        if (m_StateCache.ContainsKey(id))

        {

            Debug.LogError("The State has been added the Cache");

            return;

        }

        //緩存aState狀态

        m_StateCache.Add(id, aState);

        //設定aState狀态的狀态機對象

        aState.machine = this;

    }

    #endregion

}

2.3 每種狀态設計

AttackState代碼如下:

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class AttackState : StateTemplate<PlayerCtrl>

{

    public AttackState(int id, PlayerCtrl owner) : base(id, owner) {

    }

    public override void OnEnter(params object[] args) {

        base.OnEnter(args);

        Debug.Log("AttackState Enter");

        m_owner.GetComponent<MeshRenderer>().material.color = Color.red;

    }

    public override void OnStay(params object[] args) {

        base.OnExit(args);

        machine.TranslateToState(3);

    }

    public override void OnExit(params object[] args) {

        base.OnExit(args);

        Debug.Log("Attack Exit");

    }

    public override void OnCheck(params object[] args)

    {

        base.OnCheck(args);

    }

}

IdleState狀态代碼如下:

using UnityEngine;

using System.Collections;

public class IdleState : StateTemplate<PlayerCtrl>

{

    public IdleState(int id, PlayerCtrl owner) : base(id, owner)

    { }

    public override void OnEnter(params object[] args)

    {

        base.OnEnter(args);

        Debug.Log("IdleState Enter");

        m_owner.GetComponent<MeshRenderer>().material.color = Color.blue;

    }

    public override void OnStay(params object[] args)

    {

        base.OnStay(args);

    }

    public override void OnExit(params object[] args)

    {

        base.OnExit(args);

        Debug.Log("IdleState OnExit");

    }

    public override void OnCheck(params object[] args)

    {

    }

}

RunState代碼如下:

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class RunState : StateTemplate<PlayerCtrl>

{

    public RunState(int id, PlayerCtrl owner) : base(id, owner)

    { }

    public override void OnEnter(params object[] args)

    {

        base.OnEnter(args);

        Debug.Log("RunState Enter");

        m_owner.GetComponent<MeshRenderer>().material.color = Color.green;

    }

    public override void OnStay(params object[] args)

    {

        base.OnStay(args);

    }

    public override void OnExit(params object[] args)

    {

        base.OnExit(args);

        Debug.Log("RunState OnExit");

    }

    public override void OnCheck(params object[] args)

    {

    }

}

2.4 玩家控制類實作

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class PlayerCtrl : MonoBehaviour

{

    StateMachine m_stateMachine;

    // Start is called before the first frame update

    void Start()

    {

        //初始化狀态機

        m_stateMachine = new StateMachine(new IdleState(1,this));

        //注冊人物的所有狀态

        InitState();

    }

    void InitState() {

        m_stateMachine.RegisterState(new RunState(2,this));

        m_stateMachine.RegisterState(new AttackState(3, this));

    }

    void LateUpdate()

{

        m_stateMachine.FSMUpdate();

    }

    // Update is called once per frame

    void Update()

    {

        if (Input.GetKeyDown(KeyCode.A))

        {

            m_stateMachine.TranslateToState(2);

        }

        if (Input.GetKeyDown(KeyCode.B))

        {

            m_stateMachine.TranslateToState(3);

        }

        if (Input.GetKeyDown(KeyCode.C))

        {

            m_stateMachine.TranslateToState(1);

        }

    }

}

将該腳本挂載在立方體身上進行測試,按鍵盤上的ABC可以實作紅綠藍三種狀态之間切換。

2.5 總結變化

(1)當把顔色變化,切換為Animator變化。其實本質上是一樣的,在進入的時候播放對應的動畫即可。在OnCheck的時候檢查動畫是否播放完畢,進行對應的狀态切換。記住:在狀态内部也是可以調用TranslateToState的,因為本身每個狀态裡面包含了對應的狀态機

(2)如果是滿足每個狀态2s時間,進行切換怎麼辦?其實也是類似的,在OnCheck函數内部不停檢測時間即可,根據自己具體的邏輯實作即可。

(3)不管狀态機寫法怎麼變化,本質思想是一樣的。

(4)将狀态的ID從int類型修改為枚舉更容易表達對應的含義。

繼續閱讀