在之前的文章里介紹了一個(gè)基礎(chǔ)U3D狀態(tài)機(jī)框架(Unity3D游戲開發(fā)之狀態(tài)流框架)即大Switch的枚舉狀態(tài)控制。這種方法雖然容易理解,編程方法也相對(duì)簡(jiǎn)單,但是弊端是當(dāng)狀態(tài)變得復(fù)雜之后,或需要添加一種新的狀態(tài)時(shí),會(huì)顯得非?;靵y并且難以下手。故我們需要引進(jìn)一種更高級(jí)的狀態(tài)機(jī)技術(shù)來避免這些問題。網(wǎng)上有一些講述U3D-FSM狀態(tài)機(jī)的文章,但都不針對(duì)基礎(chǔ)講解,而且大多帶有冗余的與狀態(tài)機(jī)不相關(guān)的代碼,基礎(chǔ)不好的讀者容易看不清FSM狀態(tài)機(jī)的核心所在。這里針對(duì)網(wǎng)上的一些文章和代碼做了一個(gè)整理,意圖使之簡(jiǎn)單易懂。 這里關(guān)于FSM有限狀態(tài)機(jī)這類名詞的解釋這里就不再說明了,感興趣的朋友可以自己去百度下(度娘鏈接),本文只說重點(diǎn)。 首先是狀態(tài)機(jī)基類State.cs /** * 狀態(tài)基類 */ public class State[entity_type> { public entity_type Target; //Enter state public virtual void Enter (entity_type entityType) { } //Execute state public virtual void Execute (entity_type entityType) { } //Exit state public virtual void Exit (entity_type entityType) { } } 基類之所以設(shè)計(jì)成含有3個(gè)小的狀態(tài)方法是因?yàn)椋ǔT谟螒蛑杏行┬袨槎贾皇窃谶M(jìn)入或退出某個(gè)狀態(tài)時(shí)出現(xiàn)的,并不會(huì)發(fā)生在通常的更新步驟中。這樣設(shè)計(jì)就可以有效的將持續(xù)性調(diào)用語句和一次性調(diào)用語句有效的區(qū)分開來。(舉例:發(fā)送技能時(shí)的特效,有些是持續(xù)性而有些又是一次性的) 接下來我們編寫狀態(tài)機(jī)代碼,來使直接的這個(gè)基類的各個(gè)方法運(yùn)作起來: using UnityEngine; using System.Collections; public class StateMachine[entity_type> { private entity_type m_pOwner; private State[entity_type> m_pCurrentState;//當(dāng)前狀態(tài) private State[entity_type> m_pPreviousState;//上一個(gè)狀態(tài) private State[entity_type> m_pGlobalState;//全局狀態(tài) /*狀態(tài)機(jī)構(gòu)造函數(shù)*/ public StateMachine (entity_type owner) { m_pOwner = owner; m_pCurrentState = null; m_pPreviousState = null; m_pGlobalState = null; } /*進(jìn)入全局狀態(tài)*/ public void GlobalStateEnter() { m_pGlobalState.Enter(m_pOwner); } /*設(shè)置全局狀態(tài)*/ public void SetGlobalStateState(State[entity_type> GlobalState) { m_pGlobalState = GlobalState; m_pGlobalState.Target = m_pOwner; m_pGlobalState.Enter(m_pOwner); } /*設(shè)置當(dāng)前狀態(tài)*/ public void SetCurrentState(State[entity_type> CurrentState) { m_pCurrentState = CurrentState; m_pCurrentState.Target = m_pOwner; m_pCurrentState.Enter(m_pOwner); } /*Update*/ public void SMUpdate () { if (m_pGlobalState != null) m_pGlobalState.Execute (m_pOwner); if (m_pCurrentState != null) m_pCurrentState.Execute (m_pOwner); } /*狀態(tài)改變*/ public void ChangeState (State[entity_type> pNewState) { if (pNewState == null) { Debug.LogError ("can't find this state"); } //觸發(fā)退出狀態(tài)調(diào)用Exit方法 m_pCurrentState.Exit(m_pOwner); //保存上一個(gè)狀態(tài) m_pPreviousState = m_pCurrentState; //設(shè)置新狀態(tài)為當(dāng)前狀態(tài) m_pCurrentState = pNewState; m_pCurrentState.Target = m_pOwner; //進(jìn)入當(dāng)前狀態(tài)調(diào)用Enter方法 m_pCurrentState.Enter (m_pOwner); } public void RevertToPreviousState () { //切換到前一個(gè)狀態(tài) ChangeState (m_pPreviousState); } public State[entity_type> CurrentState () { //返回當(dāng)前狀態(tài) return m_pCurrentState; } public State[entity_type> GlobalState () { //返回全局狀態(tài) return m_pGlobalState; } public State[entity_type> PreviousState () { //返回前一個(gè)狀態(tài) return m_pPreviousState; } } 這個(gè)狀態(tài)機(jī)其實(shí)還不是最簡(jiǎn)的,全局和上一個(gè)狀態(tài)的相關(guān)部分都可以去掉,但同時(shí)功能上就會(huì)被削減,故這里將其保留。 現(xiàn)在狀態(tài)基類和狀態(tài)機(jī)類都有了,我們可以開始編寫游戲?qū)ο蟮莫?dú)立狀態(tài)類,先編寫游戲的總流程狀態(tài)類,這里命名為MainState.cs /** * 全局狀態(tài) */ public class MainState : State[Main> { public static MainState instance; /*構(gòu)造函數(shù)單例化*/ public static MainState Instance() { if (instance == null) instance = new MainState(); return instance; } public override void Enter(Main Entity) { //這里添加進(jìn)入此狀態(tài)時(shí)執(zhí)行的代碼 } public override void Execute(Main Entity) { //這里添加持續(xù)此狀態(tài)刷新代碼 } public override void Exit(Main Entity) { //這里添加離開此狀態(tài)時(shí)執(zhí)行代碼 } } /** * Ready狀態(tài) */ public class MainState_Ready : State[Main> { public static MainState_Ready instance; /*構(gòu)造函數(shù)單例化*/ public static MainState_Ready Instance() { if (instance == null) instance = new MainState_Ready(); return instance; } public override void Enter(Main Entity) { //這里添加進(jìn)入此狀態(tài)時(shí)執(zhí)行的代碼 } public override void Execute(Main Entity) { //這里添加持續(xù)此狀態(tài)刷新代碼 //這里是重點(diǎn) 當(dāng)滿足某條件后 我們可以進(jìn)行狀態(tài)切換 執(zhí)行如下代碼 切換到 Run狀態(tài) Entity.GetFSM().ChangeState(MainState_Run.Instance()); } public override void Exit(Main Entity) { //這里添加離開此狀態(tài)時(shí)執(zhí)行代碼 } } /** * Run狀態(tài) */ public class MainState_Run : State[Main> { public static MainState_Run instance; /*構(gòu)造函數(shù)單例化*/ public static MainState_Run Instance() { if (instance == null) instance = new MainState_Run(); return instance; } public override void Enter(Main Entity) { //這里添加進(jìn)入此狀態(tài)時(shí)執(zhí)行的代碼 } public override void Execute(Main Entity) { //這里添加持續(xù)此狀態(tài)刷新代碼 //當(dāng)滿足某條件后 我們可以繼續(xù)進(jìn)行狀態(tài)切換 執(zhí)行如下代碼 切換到 Over狀態(tài) Entity.GetFSM().ChangeState(MainState_Over.Instance()); } public override void Exit(Main Entity) { //這里添加離開此狀態(tài)時(shí)執(zhí)行代碼 } } /** * Over狀態(tài) */ public class MainState_Over : State[Main> { public static MainState_Over instance; /*構(gòu)造函數(shù)單例化*/ public static MainState_Over Instance() { if (instance == null) instance = new MainState_Over(); return instance; } public override void Enter(Main Entity) { //這里添加進(jìn)入此狀態(tài)時(shí)執(zhí)行的代碼 } public override void Execute(Main Entity) { //這里添加持續(xù)此狀態(tài)刷新代碼 //如之前兩個(gè)狀態(tài)類一樣 同理 當(dāng)滿足一定狀態(tài)后 可以切換回Ready狀態(tài) Entity.GetFSM().ChangeState(MainState_Ready.Instance()); } public override void Exit(Main Entity) { //這里添加離開此狀態(tài)時(shí)執(zhí)行代碼 } } 代碼有點(diǎn)長(zhǎng),主要是為了讓大家能夠看清楚如何進(jìn)行一個(gè)狀態(tài)的編寫,其實(shí)基類都是一樣的,都是重復(fù)內(nèi)容。 這里我們看到,除了定義一個(gè)全局的狀態(tài)類之外,我們還添加了Ready、Run、Over三個(gè)狀態(tài)。重點(diǎn)注意一下Execute函數(shù),這里是狀態(tài)切換的關(guān)鍵,當(dāng)帶此狀態(tài)綁定的對(duì)象Update時(shí)就在不停的執(zhí)行Execute里的代碼段,當(dāng)滿足一定條件后,即達(dá)成狀態(tài)的切換。 這里我們看一下之前的狀態(tài)機(jī)代碼里的ChangeState方法,就知道整個(gè)狀態(tài)切換是如何工作的了: /*狀態(tài)改變*/ public void ChangeState (State[entity_type> pNewState) { if (pNewState == null) { Debug.LogError ("can't find this state"); } //觸發(fā)退出狀態(tài)調(diào)用Exit方法 m_pCurrentState.Exit(m_pOwner); //保存上一個(gè)狀態(tài) m_pPreviousState = m_pCurrentState; //設(shè)置新狀態(tài)為當(dāng)前狀態(tài) m_pCurrentState = pNewState; m_pCurrentState.Target = m_pOwner; //進(jìn)入當(dāng)前狀態(tài)調(diào)用Enter方法 m_pCurrentState.Enter (m_pOwner); } 可以看到當(dāng)狀態(tài)切換時(shí),會(huì)自動(dòng)觸發(fā)當(dāng)前狀態(tài)的Exit方法和目標(biāo)狀態(tài)的Enter方法。這樣就完成了一整個(gè)狀態(tài)的切換過程。 到這里整個(gè)有限狀態(tài)機(jī)體系基本就算完工了,剩下的是如何在Main里進(jìn)行MainState類的創(chuàng)建及使用,Main.cs代碼如下: using UnityEngine; using System.Collections; public class Main : MonoBehaviour{ StateMachine[Main> m_pStateMachine;//定義一個(gè)狀態(tài)機(jī) void Start () { m_pStateMachine = new StateMachine[Main>(this);//初始化狀態(tài)機(jī) m_pStateMachine.SetCurrentState(MainState_Ready.Instance()); //設(shè)置一個(gè)當(dāng)前狀態(tài) m_pStateMachine.SetGlobalStateState(MainState.Instance());//設(shè)置全局狀態(tài) } void Update () { m_pStateMachine.SMUpdate(); } /*返回狀態(tài)機(jī)*/ public StateMachine[Main> GetFSM () { return m_pStateMachine; } } 寫到這里我們整個(gè)狀態(tài)機(jī)的框架及使用流程就基本結(jié)束了,這里要注意幾個(gè)問題: ①不要在SetCurrentState()方法調(diào)用前,調(diào)用ChangeState()方法,否則會(huì)出現(xiàn)null對(duì)象錯(cuò)誤,具體原因很簡(jiǎn)單,看一下ChangeState()里的代碼調(diào)用了哪些變量就知道了。 ②狀態(tài)間的通信,這個(gè)狀態(tài)機(jī)其實(shí)還是有未完善的地方的,目前狀態(tài)間的通知是通過直接調(diào)用其他狀態(tài)機(jī)的ChangeState()方法實(shí)現(xiàn)的,這樣勢(shì)必要先獲取該對(duì)象的腳本,這個(gè)功能待完善吧。 ③在U3D里每個(gè)游戲?qū)ο蟪跏蓟⒄{(diào)用Start()方法的時(shí)機(jī)是不一樣的,所以要注意,開始游戲時(shí)不要直接進(jìn)入開始狀態(tài),而是要有一個(gè)等待態(tài)來讓所有的游戲?qū)ο笸瓿蒘tart()方法后再調(diào)用這些對(duì)象的狀態(tài)機(jī)。 另外,多個(gè)狀態(tài)機(jī)間的通信,就像上文②中所述那樣,僅僅是通過調(diào)用ChangeState()方法來實(shí)現(xiàn),并不是非常完善,所以暫時(shí)不做講解,以免誤導(dǎo)大家,待日后有較好解決方案再另行開篇。 此FSM狀態(tài)機(jī)僅為一個(gè)雛形,還有很多功能及優(yōu)化要做,但對(duì)于入門FSM有限狀態(tài)機(jī)來說,已經(jīng)實(shí)現(xiàn)了其最主要的功能。不足之處歡迎大家提出討論,并幫助加以完善。 謝謝關(guān)注。 |
|