有限状态机(Finite State Machine, FSM),又称有限状态自动机,简称状态机,是指在有限个状态之间按照一定规律转换的逻辑状态。
状态机有 3 个组成部分:状态、事件、动作。
状态:所有可能存在的状态。包括当前状态和条件满足后要迁移的状态。
事件:也称为转移条件,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是* 必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
FSM有限状态机在游戏中的作用主要是做场景的流程管理,进入场景状态后 加载资源初始化,更新状态时执行更新逻辑,离开场景状态时销毁场景资源,数据清理、角色动作状态切换,进入时播放动作,离开时播放下一个当作等。
设计的时候考虑了可能同时存在多个状态机,所以把状态机单独的分成了一个类;状态机里面有具体的状态,负责具体状态的生命周期;状态机多了之后就需要有一个管理者去管理状态机的整个生命周期:创建、删除、释放内存;
主要分成了4个类文件:
包含状态机所需要的通用数据,所有的基类其实都是设计模式中的模板方法模式,因为基类里面的所有方法对于子类来说,都是一样的,所以叫做模板方法。
FsmBase.cs 代码
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
-
- namespace Myh
- {
- ///
- /// 状态机基类
- ///
- public abstract class FsmBase
- {
- //状态机编号
- public int FsmId { private set; get; }
-
- //当前状态的类型
- public sbyte CurrStateType;
-
- public FsmBase(int fsmId)
- {
- FsmId = fsmId;
- }
-
- //关闭状态机
- public abstract void ShutDown();
-
- }
- }
状态机的具体类,继承了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
m_CurrState; -
- //状态字典
- private Dictionary<sbyte, FsmState
> m_dicState; -
- //参数字典(??切换状态的时候还传参数??)
- private Dictionary<string, VariableBase> m_dicParam;
-
-
-
- /*
- * 构造函数 需要以下参数
- * fsmId:状态机的id
- * owner:拥有者
- * states:状态机里的所有状态
- */
- public Fsm(int fsmId, T owner, FsmState
[] states ) : base(fsmId) - {
- m_dicState = new Dictionary<sbyte, FsmState
>(); - m_dicParam = new Dictionary<string, VariableBase>();
-
- Owner = owner;
-
- //把状态放入字典
- int len = states.Length;
- for (int i = 0; i < len; ++i)
- {
- FsmState
state = states[i]; - if (null != state)
- {
- state.CurrFsm = this;
- }
- m_dicState[(sbyte)i] = state;
- }
-
- CurrStateType = -1;
- }
-
- //获取状态 by 状态类型
- public FsmState
GetState(sbyte stateType) - {
- FsmState
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
item = itemBase as Variable; - item.Value = value;
- //其实这里不重新写入也行,itemBase本来就是个class,拿到的本来就是引用
- //m_dicParam[key] = item;
- }
- else
- {
- //参数原来不存在,插入
- Variable
item = new Variable(); - item.Value = value;
- m_dicParam[key] = item;
- }
- }
-
- //获取参数值
- public TData GetData<TData>(string key)
- {
- VariableBase itemBase = null;
- if (m_dicParam.TryGetValue(key, out itemBase))
- {
- Variable
item = itemBase as Variable; - 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
> kvPair in m_dicState) - {
- if (null != kvPair.Value)
- {
- kvPair.Value.OnDestroy();
- }
- }
-
- m_dicState.Clear();
- m_dicParam.Clear();
- }
- }
- }
状态抽象类不可以直接使用,在实际的开发中使用一个具体的状态来继承 状态抽象类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
CurrFsm; -
- //进入状态
- public virtual void OnEnter() { }
-
- //更新状态
- public virtual void OnUpdate() { }
-
- //离开状态
- public virtual void OnLeave() { }
-
- //状态机销毁时调用(销毁状态)
- public virtual void OnDestroy() { }
-
- }
- }
状态机管理器内缓存下来来了所有的,状态机;主要负责创建、销毁,有点像是一个模型类,大部分的职责都是增删状态机。
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
[] states ) where T : class - {
- Fsm
fsm = new Fsm(fsmId, owner, states); - m_dicFsm[fsmId] = fsm;
- return fsm;
- }
-
- public Fsm<T> Create<T>(T owner, FsmState
[] 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
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
m_fsm=null; -
- public void OnTestStart()
- {
- FsmState
[] arrStates = new FsmState[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);
- }
-
- }
- }