最近在做一些怪物AI,發現之前寫的狀態機維護性不是很優秀,自己稍微改動了一下
所謂有限狀態機就是決定我們游戲對象的當前狀態和狀態間的切換,狀態機最終只能指向一個結果,由這個結果指向這個狀態的行為,也就是執行的函數
之前的狀態機將所有的狀態邏輯寫到了狀態類中,但是如果新添加邏輯需要回到狀態中寫,或者創建新的狀態類,在代碼的可讀行上有點欠缺
而這次做出的改動的主要思想就是將狀態中執行的邏輯和狀態類進行一個分離,將狀態中的邏輯單獨封裝成類,這個類寫在對應的游戲對象所掛的腳本的文件中,這樣就能在保正維護性的同時提高可讀性
具體的寫法如下
public class FsmCore //這個類是狀態機的核心,是改變狀態和調用狀態對應函數的類 { public StateBase curState;//當前狀態此類是一個抽象類 public StateBase prevState;//上一個狀態 public StateDataBase curdata;//當前狀態執行時需要的數據,此數據是一個抽象類 //更改狀態方法 public void ChangeState(StateBase state,StateDataBase data)//這里兩個參數分別是一個新狀態和執行新狀態需要的數據 { if (curState != null && curState.ID == state.ID)//判斷當前狀態和傳入的狀態是否是同一個狀態,如果是同一個狀態則不做處理,如果當前狀態是空或者不是同一個狀態則開始切換狀態 { return; } if (prevState != null)//判斷是否記錄了上一個狀態,如果有則退出上一個狀態 { prevState.Leave(data);//這里data沒有賦值,所以data中存儲的仍然是上一個狀態的數據 } curdata = data;//將新數據賦值 curState = state;//更改當前狀態 prevState = curState;//更改上一個狀態,這里加以說明,上一個狀態和當前狀態其實是同一個·狀態,這樣寫的好處就是當前一個狀態為空時可以少進行一次無意義的賦值 curState.Enter(data);//調用新狀態的進入方法 } public void Excute()//這個方法的作用是執行當前狀態所對應的函數(行為) { if (curState != null)//對當前狀態做一個非空判斷 { curState.Excute(curdata); } } } //狀態數據類的基類,這個類中存儲了所有要使用的數據以及一個抽象的執行方法,當有一個新的游戲對象的行為在這個狀態不同時,就可以創建一個類繼承他,重寫Excute方法,通過運行時多態最終指向重寫過的方法 public abstract class StateDataBase { public Animator anim; public NavMeshAgent nav; public GameObject obj; public GameObject target; public FsmCore fsm; protected StateDataBase(Animator anim, NavMeshAgent nav, GameObject obj, GameObject target, FsmCore fsm) { this.anim = anim; this.nav = nav; this.obj = obj; this.target = target; this.fsm = fsm; } public abstract void Excute(); } ///狀態類的基類 所有的狀態都繼承自這個類,重寫這三個方法,其中的邏輯根據處理的不同而不同,這里就可以在Excute方法中直接調用StateDataBase中的Excute方法,因為穿過來的子類重寫過此方法,因此在這里不用再次判斷游戲對象的類型,直接調用Excute即可相比於之前的狀態機在Excute中進行判斷要方便一些而ID是用來區分狀態的,因為每次切換狀態都會創建一個新對象,所以不能使用對象直接比較 public abstract class StateBase { public abstract int ID { get; } public abstract void Enter(StateDataBase data); public abstract void Excute(StateDataBase data); public abstract void Leave(StateDataBase data); }