可配置的有限狀態機

本文主要介紹如何開發一套可配置的有限狀態機,把AI行為的配置工作交給策劃來做。

有限狀態機 Finite-state machine(後文簡稱FSM)在很長一段時間裡是解決遊戲AI問題的最流行方法。後來行為樹出現,逐漸取代了FSM而成為比較複雜的AI解決方案。

在處理簡單AI行為的時候,我更傾向於使用FSM,因為比較簡單。

Unity Asset Store里也有很多FSM的插件。其中最具代表性的應該是playmaker。在我自己的項目中,我感覺playmaker的功能太過簡單。在處理AI的時候,往往需要運行一些優先順序最高的全局狀態,全局狀態可以阻塞整個狀態機,這個用playmaker做起來不太方便,所以還是決定自己寫一套AI狀態機。

我感覺playmaker最好的地方就是它的圖形化界面,每個狀態和條件都可以自由配置。自己寫狀態機,如果要做出這種圖形化界面,還是要在Editor腳本上花很多時間的。所以我想到了一個替代的方法,就是把每個狀態貼到一個單獨的GameObject上,把離開條件和下一個狀態都放在狀態腳本的編輯窗口中配置。這樣,就可以把整個AI的配置交給策劃來做,減少程序的工作量

狀態的基類AIState

/// <summary>/// AI狀態基礎類/// </summary>public class AIState : MonoBehaviour { AIFSM _fsm; public AIFSM fsm { get { return _fsm; } set { _fsm = value; } } //跳出條件 public StateConditionPair[] nextStates; [HideInInspector] public bool isStateFinish = false; private void Awake() { this.enabled = false; BaseAwake(); } protected virtual void BaseAwake() { } public void BaseEnter() { enabled = true; beginTime = Time.time; isStateFinish = false; Enter(); } public void BaseExecute() { if (IsGotoNextState()) { return; } Execute(); } public void BaseExit() { enabled = false; Exit(); } protected virtual bool IsGotoNextState() { foreach (StateConditionPair scp in nextStates) { if (scp.IsReachCondition(fsm)) { fsm.GotoState(scp.state); return true; } } return false; } protected virtual void Execute() { } protected virtual void Enter() { } protected virtual void Exit() { } /// <summary> /// 全局狀態的執行,如果返回true則阻塞整個狀態機 /// </summary> /// <returns></returns> public virtual bool GlobalExecute() { return false; } }

這就像一個經典的狀態介面,包含了Enter、Execute、Exit方法,不同的是這並不是一個介面,而是個是可以貼在GameObject上的腳本。另外,這裡加入了一個全局狀態的執行方法GlobalExecute(),如果這個狀態是作為全局狀態的,最後返回true就可以阻塞整個狀態機。在沒有進入一個狀態的時候,這個狀態對應的腳本是關閉的。

狀態裡面包含了一個StateConditionPair數組,保存的是要離開這個狀態的條件和要到達的下一個狀態。條件和下一個狀態都可以在腳本的編輯窗口裡直接編輯,而不是寫死到代碼中。

AIFSM為狀態機控制類,裡面保存了所有的狀態、公共數據和函數。

StateConditionPair類,用於保存離開條件和下一個狀態:

[System.Serializable]public class StateConditionPair { private bool avaliable = true;//是否啟用 public AIState state;//下一個狀態 public StateCondition[] conditions;//並列條件,必須全部完成//是否達到離開的條件 public bool IsReachCondition(AIFSM fsm) { if (!avaliable) { return false; } if (conditions.Length == 0) { return false; } foreach (StateCondition condition in conditions) { if (!condition.IsReachCondition(fsm)) { return false; } } return true; }}

StateCondition類,為條件判斷的具體實現。判斷一般會用到狀態機里的數據,所以把AIFSM對象傳進來。

public enum AIConditionType{ IsDistanceGreaterThan, IsDistanceLessThan, IsHpGreaterThan, IsHpLessThan, IsTimeGreaterThan, IsReachDestination, IsStateFinish}[System.Serializable]public class StateCondition { public AIConditionType type; public float value; public GameObject valueObj; public bool IsReachCondition(AIFSM fsm) { //bool isReach = false; switch (type) { case AIConditionType.IsDistanceGreaterThan: { //TODO 判斷如果距離大於value就返回true } break; case AIConditionType.IsDistanceLessThan: { //TODO 判斷如果距離小於value就返回true } break; case AIConditionType.IsHpGreaterThan: { //TODO 判斷如果血量大於value就返回true } break; case AIConditionType.IsHpLessThan: { //TODO 判斷如果血量小於value就返回true } break; case AIConditionType.IsTimeGreaterThan: { //TODO 判斷如果運行時間超過value就返回true } break; case AIConditionType.IsReachDestination: { //TODO 判斷如果到達目標valueObj返回true } break; case AIConditionType.IsStateFinish: { //TODO 判斷如果狀態結束完成就返回true } break; } return false; }}

注意條件類是可序列化的,這樣就可以直接在狀態腳本中設置條件。其中value和valueObj是一些用來進行比較的參數。

AIFSM類,裡面就是狀態機的實現邏輯。

public AIState currentState; //全局狀態 public AIState[] globalStates; //進入第一個狀態的條件 public StateConditionPair[] nextStates; //非全局狀態,用於初始化 public List<AIState> allStates = new List<AIState>(); public void GotoState(AIState state) { if (currentState != null) { currentState.BaseExit(); } if (state == null) { currentState = null; return; } currentState = state; currentState.fsm = this; currentState.BaseEnter(); } //運行所有全局狀態,如果返回false說明沒有全局狀態阻塞狀態機 bool ExecuteGlobalState(out AIState executeState) { foreach (AIState state in globalStates) { if (state == null) { continue; } state.fsm = this; if (state.GlobalExecute()) { state.enabled = true; executeState = state; return true; } else { state.enabled = false; } } executeState = null; return false; }//自動進入第一個狀態 public bool IsGotoNextState() { foreach (StateConditionPair scp in nextStates) { if (scp.IsReachCondition(this)) { GotoState(scp.state); return true; } } return false; } void Update() { AIState executeState = null; //運行全局狀態,看是否有全局狀態阻塞狀態機,如果沒有就再繼續運行後面的狀態。 if (ExecuteGlobalState(out executeState)) { currentState = null; return; } //如果當前狀態為空,自動找一個狀態進入 if (currentState == null) { IsGotoNextState(); } //當前狀態不為空,就執行 if (currentState) { currentState.BaseExecute(); } }

裡面定義了全局狀態,普通狀態,狀態的切換邏輯等。另外還定義了一些公共方法,供狀態使用。只要在AIFSM的編輯窗口把物體下面的全局狀態和普通狀態拖進去,配置好每個狀態的離開條件和下一個狀態就可以了。

AIFSM的運行流程:

最後,只需要根據策劃的需求,把具體的狀態類寫出來,把所有的離開狀態的判斷條件寫出來就可以了,其他的交給策劃搞定。
推薦閱讀:

GameMaker: Studio 中文教程 #8: 惡魔行者與AI
GDC2017: 《Lone Echo》中的VR動畫
如何開發一款老陰逼射擊遊戲?
【Unity】UGUI系列教程————UGUI基礎!界面拼接!
爐石製作人主講 - 以《爐石戰記》為例,探討跨平台遊戲的製作思維與設計

TAG:Unity游戏引擎 | 游戏开发 |