FSM狀態機改
一.前言
之前寫過一版有限狀態機,後來發現很多問題;
前一個版本是記錄了當前的狀態,切換狀態時,要等下一幀狀態機Update的時候才會調動上個狀態的退出,總會有一幀的延遲;
除了導致動作延遲外,狀態很多的情況報錯也無法追述,斷點只能回到狀態機中;
因此做了如下修改;
1.狀態機不再繼承MonoBehaviour,只需要是單例,儲存所有狀態基類;
2.狀態機提供切換狀態的方法SwitchAction,傳參下個狀態ID;
3.切換狀態時呼叫上一個狀態的退出週期,再呼叫當前狀態的開始週期;
4.同時將當前狀態的引用重新賦值為傳入的狀態;
5.狀態機提供Run方法給角色控制器呼叫,角色控制器Update只執行當前狀態的Run;
效果展示:
二.修改
修改後FSM,除增刪查外新增切換狀態函式SwitchState;
提供FSM狀態機的生命週期;FSMInit,FSMRun,FSMEnd;
public class FSM<T>
{
private Dictionary<int, StateBase<T>> FSMActDic;
private StateBase<T> curState;
//切換狀態時呼叫
public void SwitchState(int nextID)
{
curState.OnExit();
curState = FSMActDic[nextID];
curState.OnEnter();
}
public int GetCurState()
{
foreach (var kv in FSMActDic)
{
if (kv.Value == curState)
return kv.Key;
}
return -1;
}
public FSM()
{
FSMActDic = new Dictionary<int, StateBase<T>>();
}
//增
public void AddState(int id, StateBase<T> state)
{
if(FSMActDic.ContainsKey(id))
return;
FSMActDic.Add(id, state);
}
//刪
public void RemoveSatate(int id)
{
if (FSMActDic.ContainsKey(id))
FSMActDic.Remove(id);
}
//獲取
public StateBase<T> GetState(int id)
{
if (!FSMActDic.ContainsKey(id))
return null;
return FSMActDic[id];
}
//狀態機初始化呼叫,給curState賦值並呼叫其OnStay
public void FSMInit(int id)
{
curState = FSMActDic[id];
curState.OnStay();
}
//每幀執行
public void FSMRun()
{
curState.OnStay();
}
//退出狀態機執行
public void FSMEnd()
{
curState.OnExit();
}
}
三.測試程式碼
使用狀態先初始化,同時設定初始狀態;
角色控制類負責初始化和執行FSM狀態機;
public class PlayerControl : MonoBehaviour
{
public enum PlayerState
{
none = 0,
idle,
move,
jump,
}
public FSM<PlayerControl> mPlayerFSM;
public PlayerState mState;
public Animator mAnimator;
public float mSpeed;
public Vector3 moveDir;
private void InitFSM()
{
mPlayerFSM.AddState((int) PlayerState.idle, new ActIdle((int) PlayerState.idle, this));
mPlayerFSM.AddState((int) PlayerState.move, new ActMove((int) PlayerState.move, this));
mPlayerFSM.AddState((int) PlayerState.jump, new ActAttack((int) PlayerState.jump, this));
mPlayerFSM.FSMInit((int)PlayerState.idle);
}
void Start()
{
mAnimator = GetComponentInChildren<Animator>();
mSpeed = 10;
mPlayerFSM = new FSM<PlayerControl>();
InitFSM();
mState = PlayerState.idle;
}
void Update()
{
//單純為了在inspector皮膚中看到當前狀態
mState = (PlayerState)mPlayerFSM.GetCurState();
mPlayerFSM.FSMRun();
}
}
在不同的行為類中,監聽輸入按鍵通過owner呼叫fsm的switch方法,切換狀態;
舉例移動行為類,監聽兩個軸的輸入,切換idle,同時監聽攻擊按鍵切換攻擊狀態;
public class ActMove : StateBase<PlayerControl>
{
public ActMove(int id, PlayerControl t) : base(id, t)
{
}
//給子類提供方法
public override void OnEnter(params object[] args)
{
owner.mAnimator.Play("Run");
}
public override void OnStay(params object[] args)
{
owner.transform.position += owner.moveDir * Time.deltaTime * owner.mSpeed;
if (Input.GetAxis("Horizontal") > 0 && Input.GetAxis("Vertical") == 0)
{
owner.moveDir = owner.transform.right;
}
else if (Input.GetAxis("Horizontal") < 0 && Input.GetAxis("Vertical") == 0)
{
owner.moveDir = -owner.transform.right;
}
else if (Input.GetAxis("Horizontal") > 0 && Input.GetAxis("Vertical") < 0)
{
owner.moveDir = owner.transform.right - owner.transform.forward;
}
else if (Input.GetAxis("Horizontal") < 0 && Input.GetAxis("Vertical") < 0)
{
owner.moveDir = -owner.transform.right - owner.transform.forward;
}
else if (Input.GetAxis("Horizontal") > 0 && Input.GetAxis("Vertical") > 0)
{
owner.moveDir = owner.transform.right + owner.transform.forward;
}
else if (Input.GetAxis("Horizontal") < 0 && Input.GetAxis("Vertical") > 0)
{
owner.moveDir = -owner.transform.right + owner.transform.forward;
}
else if (Input.GetAxis("Horizontal") == 0 && Input.GetAxis("Vertical") < 0)
{
owner.moveDir = -owner.transform.forward;
}
else if (Input.GetAxis("Horizontal") == 0 && Input.GetAxis("Vertical") > 0)
{
owner.moveDir = owner.transform.forward;
}
if (Mathf.Abs(Input.GetAxis("Horizontal")) < 0.1f && Mathf.Abs(Input.GetAxis("Vertical")) < 0.1f)
owner.mPlayerFSM.SwitchState((int)PlayerControl.PlayerState.idle);
if (Input.GetAxis("Jump") != 0)
owner.mPlayerFSM.SwitchState((int)PlayerControl.PlayerState.jump);
}
public override void OnExit(params object[] args)
{
}
}
自從出了行為樹之後,有限狀態機就沒太大的用武之地了,後面有機會介紹官方的BehaviourTree外掛吧;