學習筆記(二) 簡單的狀態模式&FSM有限狀態機框架的實現

首先在我們講我們的主角之前,先來看一張圖。
在這裏插入圖片描述
你的第一反應是什麼?哇,好大一張蜘蛛網!
而事實上它連蜘蛛網都不是。

在製作遊戲的過程中,動畫是必不可少的東西,Unity的新版動畫系統已經很好的幫我們解決了動畫管理難的問題。
但是它雖然方便了我們,但同樣不可避免的出現了某種意外,也就是上面所示的“蜘蛛網”的狀態。
當然,雖然出現了這種情況,也並不是沒有解決方案的,也就是我們接下來所說的FSM有限狀態機。

FSM有限狀態機(Finite-state machine)
它是專門用於解決這種多狀態切換導致的問題的。
使用動畫狀態機中的 Any State(任意狀態),可以使得蜘蛛網瞬間變得清晰:
在這裏插入圖片描述
看,多麼清晰明瞭。
那麼接下來,我們需要做的就非常簡單了,寫一個狀態機的管理!
首先來分析,我們什麼時候需要狀態,或者說,我們的有限狀態機能做什麼?
接下來給大家簡單的分析一下代碼實現。
首先創建一個腳本StateBase,是所有狀態的父類。
這個腳本中一共有3個方法:進入、持續、退出;和一個管理者一個調用者。

/// <summary>
/// 轉換的條件
/// </summary>
public enum Transition
{
    //空狀態
    NullTrans = 0,
    //按下空格
    ClickSp,
    //血量清空
    HpClear
}

/// <summary>
/// 狀態ID
/// </summary>
public enum State
{
    NullState = 0,
    //idle
    Idle,
    //跳躍中
    Jump,
    //死亡
    Death
}

public abstract class StateBase {
    /// <summary>
    /// 狀態字典
    /// </summary>
    protected Dictionary<Transition, State> mStateDic = new Dictionary<Transition, State>();	//定義一個字典,鍵爲轉換條件,值爲狀態ID
    
    /// <summary>
    /// 狀態控制器    他們要知道是誰在控制它
    /// </summary>
    public FSMStateCtrl StateCtrl;
    
    /// <summary>
    /// 當前類對應的狀態
    /// </summary>
    public State CurrentState;

    public Rigidbody rig;

    /// <summary>
    /// 初始化
    /// </summary>
    public StateBase(State state ,FSMStateCtrl StateCtrl,Rigidbody rig)
    {
        CurrentState = state;
        this.StateCtrl = StateCtrl;
        this.rig = rig;
    }

    /// <summary>
    /// 添加狀態
    /// </summary>
    public void AddState(Transition trans,State state)	//添加一個狀態進入字典中
    {
        //這裏一系列防誤操作判斷
        if (mStateDic.ContainsKey(trans))
        {
            Debug.LogError("FSM狀態機錯誤:【"+trans+"】狀態已添加,不可重複添加!");return;
        }
        mStateDic[trans] = state;
    }
    
    /// <summary>
    /// 刪除
    /// </summary>
    public void DeleteState(State state)		//移除一個狀態
    {
        foreach (Transition tran in mStateDic.Keys)
        {
            if (mStateDic[tran].Equals(state))
            {
                mStateDic.Remove(tran); return;
            }
        }
    }

    /// <summary>
    /// 從他身上拿到要轉換的狀態
    /// </summary>
    public State GetState(Transition trans)		//由於分解耦合性的思想,這裏將轉換條件的獲取由條件自己而來,就是當前狀態滿足了這個條件後,它自身有一個對應的跳轉狀態。
    {										//這樣可以將每個狀態都區分開。
        if (mStateDic.ContainsKey(trans))
        {
            return mStateDic[trans];
        }
        return State.NullState;
    }
    //定義虛方法
    //每幀調用  用於檢測是否需要轉換
    public abstract void Act(Transform player);
    //每幀調用  用於執行事件
    public abstract void Continue(Transform player);
    //進入時調用
    public virtual void OnEnter() { }
    //退出時調用
    public virtual void OnExit() { }
}

然後在創建一個腳本StateManager,狀態機的管理類。
它持有一個狀態的集合,和一個當前狀態。其中有幾個方法,分別是往集合中添加狀態、移除狀態的方法,還有更換當前狀態的方法。

//用於控制狀態機
public class FSMStateManager : MonoBehaviour
{
    /// <summary>
    /// 狀態的集合,所有狀態在裏面存着
    List<StateBase> mStateList;

    /// <summary>
    /// 當前狀態
    /// </summary>
    private StateBase CuttouenState;

    private void Awake()
    {
        //初始化
        mStateList = new List<StateBase>();
    }

    private void Update()
    {
        //如果當前狀態不爲空
        if (CuttouenState != null)
        {
            CuttouenState.Act(transform);		//執行每幀調用的方法
            CuttouenState.Continue(transform);
        }
    }

    /// <summary>
    /// 添加一個狀態
    /// </summary>
    /// <param name="stateb"></param>
    public void AddState(StateBase stateb)
    {
        //這裏不能重複添加,所以一系列防誤觸
        if (mStateList.Contains(stateb))
        {
            Debug.LogError("FSM錯誤:【"+stateb+"】已存在,添加失敗!");
        }
        //將第一個進入的狀態設置爲初始狀態
        if (mStateList.Count == 0)
        {
            CuttouenState = stateb;
        }
        mStateList.Add(stateb);
    }

    /// <summary>
    /// 切換狀態,通過條件,狀態身上拿到它要轉換到的狀態ID
    /// </summary>
    public void CutState(Transition trans)
    {
        State s = CuttouenState.GetState(trans);
        if (s == State.NullState)
        {
            Debug.Log("轉到狀態爲空!"); return;
        }
        for (int i = 0; i < mStateList.Count; i++)
        {
            if (s == mStateList[i].CurrentState)
            {
                CuttouenState.OnExit();
                CuttouenState = mStateList[i];
                CuttouenState.OnEnter();
            }
        }
    }
}

之後,當我們需要添加一個狀態的時候,就可以繼承StateBase狀態基類,實現它身上的方法:

public class IdleState : StateBase
{
    //實現父類構造方法
    public IdleState(State CurrentState, FSMStateCtrl StateCtrl,Rigidbody rig) :base(CurrentState, StateCtrl, rig)
    {

    }
    //重寫判斷條件(Update中每幀調用的)
    public override void Act(Transform player)
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            StateCtrl.CutState(Transition.ClickSp);	//這裏枚舉是轉換條件:當按下空格
                 //對應的事件是:進入跳躍狀態	
        }
    }
    public override void Continue(Transform player)	//每幀調用:執行事件
    {
        Debug.Log("我是Idle");
    }
}

接下來只需要一個啓動狀態機的類,就可以完成啦:

/// <summary>
/// 用於啓動狀態機
/// </summary>
public class StateCtrl: MonoBehaviour {

    	FSMStateCtrl mFsmStateCtrl;

	void Start () {
        //初始化   得到他身上的FSM控制器
        mFsmStateCtrl = GetComponent<FSMStateCtrl>();
         
        //創建幾個狀態出來,並且給他們添加上可以轉換到的狀態和轉換條件。
        IdleState idle = new IdleState(State.Idle, mFsmStateCtrl,transform.GetComponent<Rigidbody>());
        idle.AddState(Transition.DoubleClickSp, State.Death);
        idle.AddState(Transition.HpClear, State.Death);

        DeathState death = new DeathState(State.Death, mFsmStateCtrl, transform.GetComponent<Rigidbody>());
        death.AddState(Transition.IsDown, State.Idle);

        //把這幾個狀態添加進狀態機中
        mFsmStateCtrl.AddState(idle);
        mFsmStateCtrl.AddState(death);
    }
}

這樣,我們只需要定義自己的狀態,然後通過轉換,就可以實現所需要的需求啦,是不是很方便。

下面是筆者簡單的總結出來這套狀態框架的使用方法:
1、給需要使用狀態的物體添加上 StateManager(狀態管理器)
2、創建出自己的StateCtrl(狀態控制器)並掛在在遊戲物體上 - 命名格式:物體名+StateCtrl;
3、在狀態枚舉類中(EnumBase)按分類添加對應的轉換條件和狀態ID,命名格式要加上物體名。
4、創建出實際狀態類,繼承StateBase,並且實現方法與構造方法(這裏說一下,如果你的執行事件需要其他的某些條件,直接就在這個狀態類中獲取,它身上有進入方法,可以進行初始化賦值)
5、在StateCtrl中獲取到StateManager(GetComponent方法獲取)
6、在StateCtrl中創建狀態對象(在start中用new)
7、使用狀態的Add方法添加轉換條件和對應狀態
8、將這些狀態用創建好的StateManager對象.Add方法添加入管理器中
9、至此完成步驟。

好了,今天的分享就到這裏結束啦,感謝大家的觀看。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章