GameFramework 解析:有限状态机(FSM)
什么是有限状态机
有限状态机的概念相信很多同学都清楚了,不清楚的可以参考一下书籍《游戏编程模式》中状态模式一节,里面讲得十分清楚。FSM 在游戏中常用于玩家控制、怪物 AI、UI 状态、游戏流程控制等。
有限状态机的实现
结构
有限状态机的实现我们可以把他分成 3 部分,上图中从上到下每一行就是一部分,分别是状态部分(FsmState),状态机部分(FsmBase、IFsm、Fsm)以及状态机管理器部分(IFsmManager、FsmManager)。
状态类 FsmState
- FsmState 为有限状态机状态基类,所有用于有限状态机的状态都需要继承自此类,泛型参数 T 需要传入状态持有者类型。
- OnInit、OnEnter、OnUpdate、OnLeave、OnDestroy 为状态的生命周期方法,其中 OnInit 和 OnDestroy 分别在状态创建和销毁时调用,只会调用一次,而 OnEnter、OnLeave 分别在进入状态和离开状态时调用,可能会调用多次,而 OnUpdate 则是在进入该状态后每帧调用。
- ChangeState 用于切换到下一状态。ChangeState 实际是用该方法传入的 FSM 对象调用 FSM 类里的 ChangeState 方法,正式执行状态切换逻辑。
状态机类 Fsm
- Fsm 对象通过 Create 方法创建,需要传入状态机拥有者类型、状态机名字、状态列表 3 个参数,Create 方法为静态方法,由 FsmManager 调用。参数状态列表将会保存在字段 m_States 中,并调用所有状态的 OnInit 方法。
- 状态机通过 Start 方法启动,传入初始状态类型作为参数,方法内部会调用该状态的 OnEnter。
- Update 方法会每帧调用当前状态的 Update 方法,且会计算当前状态机进行了的累计时间,可通过 CurrentStateTime 获取。
- GetAllState 和 GetState 方法可以获取注册进这个状态机的状态对象。
- 状态机内通常不同状态之间是需要有数据交互的,GetData,SetData,HasData,RemoveData 这四个接口则提供了不同状态间数据交互的功能,分别对应获取数据、设置数据、是否有数据、移除数据,数据以 key-value 形式存在于字典 m_Datas 中。
- Shutdown 方法会回收 FSM 对象,此方法由 FsmManager 的 DestroyFsm 方法调用。
状态机管理器 FsmManager
- 外部创建新的状态机统一通过 FsmManager 的 CreateFsm 接口创建,参数同 FSM 类中的静态方法 Create,此方法会调用 Fsm 类的 Create 创建 Fsm 对象,然后以 key-value 的形式储存在字段 m_Fsms 中,注意 m_Fsms 是 Dictionary 类型,以 TypeNamePair 为 Key,TypeNamePair 对象是结合状态机持有者类型和状态机名字字符串类型参数组成,为了保证 Key 的唯一性,对于同样类型的而不同实例的持有者,应该传入不同的状态机名字。
- GetFsm、GetAllFsm、HasFsm,向外部提供某个状态机的查询、获取,需要传入持有者类型和状态机名字两个参数。
- DestroyFsm 可销毁特定状态机,会调用对应 Fsm 对象的 Shutdown 方法,并在 FsmManager 的 m_Fsms 字段中移除该状态机。
- Update 方法中会调用 m_Fsms 中的所有状态机的 Update 方法,值得注意的是这里并没有直接对 m_Fsms 进行 foreach,而是添加到一个临时的列表中再进行循环调用,这样可以防止在迭代过程中,外部销毁某个状态机而从 m_Fsms 移除状态机对象时,造成迭代器失效。
示例
假设我们现在需要用状态来实现玩家的控制,其中包括空闲和移动状态,处于空闲状态下的玩家当检测到方向键按下时,会切换到移动状态,且根据方向键向某个方向进行移动,移动过程持续一秒。
我们需要 3 个类去实现这一需求,其中 IdleState、MoveState 两个类分别对应空闲状态、移动状态,Player 则为状态机的持有者,也是状态机要控制的主体。
空闲状态类
移动状态类
玩家类
Inspector 面板
FSM 组件的 Inspector 面板可以实时看到所有正在运行的状态机,以及这些状态机当前处于的状态、运行时间。
最后
GameFramework 解析 系列目录:GameFramework 解析:开篇
个人原创,未经授权,谢绝转载!