一、有限状态机介绍
有限状态机(Finite State Machine, FSM),又称有限状态自动机,简称状态机,是指在有限个状态之间按照一定规律转换的逻辑状态。
状态机有 3 个组成部分:状态、事件、动作。
状态:所有可能存在的状态。包括当前状态和条件满足后要迁移的状态。
事件:也称为转移条件,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是* 必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
FSM有限状态机在游戏中的作用主要是做场景的流程管理,进入场景状态后 加载资源初始化,更新状态时执行更新逻辑,离开场景状态时销毁场景资源,数据清理、角色动作状态切换,进入时播放动作,离开时播放下一个当作等。
二、有限状态机的设计
设计的时候考虑了可能同时存在多个状态机,所以把状态机单独的分成了一个类;状态机里面有具体的状态,负责具体状态的生命周期;状态机多了之后就需要有一个管理者去管理状态机的整个生命周期:创建、删除、释放内存;
主要分成了4个类文件:
1.状态机基类 FsmBase.cs
包含状态机所需要的通用数据,所有的基类其实都是设计模式中的模板方法模式,因为基类里面的所有方法对于子类来说,都是一样的,所以叫做模板方法。
FsmBase.cs 代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Myh { /// <summary> /// 状态机基类 /// </summary> public abstract class FsmBase { //状态机编号 public int FsmId { private set; get; } //当前状态的类型 public sbyte CurrStateType; public FsmBase(int fsmId) { FsmId = fsmId; } //关闭状态机 public abstract void ShutDown(); } }
2.状态机类 Fsm.cs
状态机的具体类,继承了FsmBase类;内部实现了所有具体状态的管理、添加、更新、切换等。
Fsm.cs实现代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Myh { //拥有者,拥有这个状态的类型 public class Fsm<T> : FsmBase where T : class { //拥有者 public T Owner { private set; get; } //当前状态 private FsmState<T> m_CurrState; //状态字典 private Dictionary<sbyte, FsmState<T>> m_dicState; //参数字典(??切换状态的时候还传参数??) private Dictionary<string, VariableBase> m_dicParam; /* * 构造函数 需要以下参数 * fsmId:状态机的id * owner:拥有者 * states:状态机里的所有状态 */ public Fsm(int fsmId, T owner, FsmState<T>[] states) : base(fsmId) { m_dicState = new Dictionary<sbyte, FsmState<T>>(); m_dicParam = new Dictionary<string, VariableBase>(); Owner = owner; //把状态放入字典 int len = states.Length; for (int i = 0; i < len; ++i) { FsmState<T> state = states[i]; if (null != state) { state.CurrFsm = this; } m_dicState[(sbyte)i] = state; } CurrStateType = -1; } //获取状态 by 状态类型 public FsmState<T> GetState(sbyte stateType) { FsmState<T> state = null; m_dicState.TryGetValue(stateType,out state); return state; } //状态机更新 public void OnUpdate() { if (null != m_CurrState) { m_CurrState.OnUpdate(); } } //状态机切换 by newStateType public void ChangeState(sbyte newState) { //如果两个状态一致,不再进入 if (CurrStateType == newState) return; //先让当前状态执行OnLeave流程,执行上一个状态离开的逻辑 if (null != m_CurrState) m_CurrState.OnLeave(); CurrStateType = newState; m_CurrState = m_dicState[CurrStateType]; //然后再让新状态执行OnEnter流程,执行新状态的进入逻辑 m_CurrState.OnEnter(); } //设置参数值 public void SetData<TData>(string key, TData value) { VariableBase itemBase = null; if (m_dicParam.TryGetValue(key, out itemBase)) { //参数原来存在,更新值 Variable<TData> item = itemBase as Variable<TData>; item.Value = value; //其实这里不重新写入也行,itemBase本来就是个class,拿到的本来就是引用 //m_dicParam[key] = item; } else { //参数原来不存在,插入 Variable<TData> item = new Variable<TData>(); item.Value = value; m_dicParam[key] = item; } } //获取参数值 public TData GetData<TData>(string key) { VariableBase itemBase = null; if (m_dicParam.TryGetValue(key, out itemBase)) { Variable<TData> item = itemBase as Variable<TData>; if (null == item) return default(TData); return item.Value; } //返回该结构的实例? return default(TData); } public override void ShutDown() { if (null != m_CurrState) m_CurrState.OnLeave(); //循环遍历,执行所有状态的OnDestroy函数,执行销毁逻辑,销毁所有的状态 foreach (KeyValuePair<sbyte, FsmState<T>> kvPair in m_dicState) { if (null != kvPair.Value) { kvPair.Value.OnDestroy(); } } m_dicState.Clear(); m_dicParam.Clear(); } } }
3.状态机 状态抽象类 FsmState.cs
状态抽象类不可以直接使用,在实际的开发中使用一个具体的状态来继承 状态抽象类FsmState ,具体的例子可以看测试代码中的实现。abstract改成接口也行,但是后来感觉没啥必要,有些派生类未必一定需要实现所有虚函数,所以就写成了抽象类,4个事件都写成了虚函数。派生类实现不实现都可。
FsmState.cs 代码实现
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Myh { //状态机的状态 public abstract class FsmState<T> where T : class { //状态对应的状态机 public Fsm<T> CurrFsm; //进入状态 public virtual void OnEnter() { } //更新状态 public virtual void OnUpdate() { } //离开状态 public virtual void OnLeave() { } //状态机销毁时调用(销毁状态) public virtual void OnDestroy() { } } }
4.状态机管理器 FsmManager.cs
状态机管理器内缓存下来来了所有的,状态机;主要负责创建、销毁,有点像是一个模型类,大部分的职责都是增删状态机。
FsmManager.cs代码实现
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Myh { //状态机管理器 public class FsmManager : ManagerBase, IDisposable { //状态机字典 private Dictionary<int, FsmBase> m_dicFsm; //状态机的临时编号 private int m_TemFsmId = 0; public FsmManager() { m_dicFsm = new Dictionary<int, FsmBase>(); } //初始化 public override void Init() { } #region Create /* * 创建状态机实例 * T:拥有者类型 * fsmId:状态机Id * owner:拥有者实例 * states:状态数组 */ public Fsm<T> Create<T>(int fsmId, T owner, FsmState<T>[] states) where T : class { Fsm<T> fsm = new Fsm<T>(fsmId, owner, states); m_dicFsm[fsmId] = fsm; return fsm; } public Fsm<T> Create<T>(T owner, FsmState<T>[] states) where T : class { return Create(++m_TemFsmId, owner, states); } #endregion #region 销毁状态机 //销毁状态机 public void DestroyFsm(int fsmId) { FsmBase fsm = null; if (m_dicFsm.TryGetValue(fsmId, out fsm)) { fsm.ShutDown(); m_dicFsm.Remove(fsmId); } } #endregion public void Dispose() { IEnumerator<KeyValuePair<int, FsmBase>> iter= m_dicFsm.GetEnumerator(); for (; iter.MoveNext();) { iter.Current.Value.ShutDown(); } m_dicFsm.Clear(); } } }
三、测试与总结
因为刚开始写,还没办法在实际的工程中去做测试,所以主要通过按下按键就切换状态,测了一下状态切换是否符合预期,结果很好,符合预期。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Myh; using YouYou; using UnityEngine; public class FmsTest : ITest { static private void PrintStateLog(int id, string strEvent) { Debug.Log("状态机:" + id + " 触发了:" + strEvent + " 事件"); } #region 测试状态类 enum EnumTestClassType { eTest1, eTest2, eTest3, eTest4, } class FsmTestState1 : FsmState<FmsTest> { public override void OnEnter() { base.OnEnter(); PrintStateLog(1, "OnEnter"); } public override void OnUpdate() { base.OnUpdate(); PrintStateLog(1, "OnUpdate"); } public override void OnLeave() { base.OnLeave(); PrintStateLog(1, "OnLeave"); } public override void OnDestroy() { base.OnDestroy(); PrintStateLog(1, "OnDestroy"); } } class FsmTestState2 : FsmState<FmsTest> { public override void OnEnter() { base.OnEnter(); PrintStateLog(2, "OnEnter"); } public override void OnUpdate() { base.OnUpdate(); PrintStateLog(2, "OnUpdate"); } public override void OnLeave() { base.OnLeave(); PrintStateLog(2, "OnLeave"); } public override void OnDestroy() { base.OnDestroy(); PrintStateLog(2, "OnDestroy"); } } class FsmTestState3 : FsmState<FmsTest> { public override void OnEnter() { base.OnEnter(); PrintStateLog(3, "OnEnter"); } public override void OnUpdate() { base.OnUpdate(); PrintStateLog(3, "OnUpdate"); } public override void OnLeave() { base.OnLeave(); PrintStateLog(3, "OnLeave"); } public override void OnDestroy() { base.OnDestroy(); PrintStateLog(3, "OnDestroy"); } } class FsmTestState4 : FsmState<FmsTest> { public override void OnEnter() { base.OnEnter(); PrintStateLog(4, "OnEnter"); } public override void OnUpdate() { base.OnUpdate(); PrintStateLog(4, "OnUpdate"); } public override void OnLeave() { base.OnLeave(); PrintStateLog(4, "OnLeave"); } public override void OnDestroy() { base.OnDestroy(); PrintStateLog(4, "OnDestroy"); } } #endregion private Fsm<FmsTest> m_fsm=null; public void OnTestStart() { FsmState<FmsTest>[] arrStates = new FsmState<FmsTest>[4]; arrStates[(int)EnumTestClassType.eTest1] = new FsmTestState1(); arrStates[(int)EnumTestClassType.eTest2] = new FsmTestState2(); arrStates[(int)EnumTestClassType.eTest3] = new FsmTestState3(); arrStates[(int)EnumTestClassType.eTest4] = new FsmTestState4(); m_fsm = GameEntry.FSM.Create(this, arrStates); } public void OnTestUpdate() { if (null == m_fsm) return; //m_fsm.OnUpdate(); if (Input.GetKeyDown(KeyCode.Keypad1)) { m_fsm.ChangeState((int)EnumTestClassType.eTest1); } else if (Input.GetKeyDown(KeyCode.Keypad2)) { m_fsm.ChangeState((int)EnumTestClassType.eTest2); } else if (Input.GetKeyDown(KeyCode.Keypad3)) { m_fsm.ChangeState((int)EnumTestClassType.eTest3); } else if (Input.GetKeyDown(KeyCode.Keypad4)) { m_fsm.ChangeState((int)EnumTestClassType.eTest4); } } }