首先在我們講我們的主角之前,先來看一張圖。
你的第一反應是什麼?哇,好大一張蜘蛛網!
而事實上它連蜘蛛網都不是。
在製作遊戲的過程中,動畫是必不可少的東西,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、至此完成步驟。
好了,今天的分享就到這裏結束啦,感謝大家的觀看。