什么是有限状态机
有限状态机的概念相信很多同学都清楚了,不清楚的可以参考一下书籍《游戏编程模式》中状态模式 一节,里面讲得十分清楚。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, FsmBase>类型,以TypeNamePair为Key,TypeNamePair对象是结合状态机持有者类型和状态机名字字符串类型参数组成,为了保证Key的唯一性,对于同样类型的而不同实例的持有者,应该传入不同的状态机名字。
GetFsm、GetAllFsm、HasFsm,向外部提供某个状态机的查询、获取,需要传入持有者类型和状态机名字两个参数。
DestroyFsm可销毁特定状态机,会调用对应Fsm对象的Shutdown方法,并在FsmManager的m_Fsms字段中移除该状态机。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 internal override void Update (float elapseSeconds, float realElapseSeconds ){ m_TempFsms.Clear(); if (m_Fsms.Count <= 0 ) { return ; } foreach (KeyValuePair<TypeNamePair, FsmBase> fsm in m_Fsms) { m_TempFsms.Add(fsm.Value); } foreach (FsmBase fsm in m_TempFsms) { if (fsm.IsDestroyed) { continue ; } fsm.Update(elapseSeconds, realElapseSeconds); } }
Update方法中会调用m_Fsms中的所有状态机的Update方法,值得注意的是这里并没有直接对m_Fsms进行foreach,而是添加到一个临时的列表中再进行循环调用,这样可以防止在迭代过程中,外部销毁某个状态机而从m_Fsms移除状态机对象时,造成迭代器失效。
示例
假设我们现在需要用状态来实现玩家的控制,其中包括空闲和移动状态,处于空闲状态下的玩家当检测到方向键按下时,会切换到移动状态,且根据方向键向某个方向进行移动,移动过程持续一秒。
我们需要3个类去实现这一需求,其中IdleState、MoveState两个类分别对应空闲状态、移动状态,Player则为状态机的持有者,也是状态机要控制的主体。
空闲状态类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 using UnityEngine;using GameFramework.Fsm;using ProcedureOwner = GameFramework.Fsm.IFsm<Player>;using UnityGameFramework.Runtime;public class IdleState : FsmState <Player >{ private static KeyCode[] MOVE_COMMANDS = { KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.UpArrow, KeyCode.DownArrow }; protected override void OnInit (ProcedureOwner fsm ) { base .OnInit(fsm); } protected override void OnEnter (ProcedureOwner fsm ) { base .OnEnter(fsm); } protected override void OnUpdate (ProcedureOwner fsm, float elapseSeconds, float realElapseSeconds ) { base .OnUpdate(fsm, elapseSeconds, realElapseSeconds); foreach (var command in MOVE_COMMANDS) { if (Input.GetKeyDown(command)) { fsm.SetData<VarInt32>("MoveCommand" , (int )command); ChangeState<MoveState>(fsm); } } } protected override void OnLeave (ProcedureOwner fsm, bool isShutdown ) { base .OnLeave(fsm, isShutdown); } protected override void OnDestroy (ProcedureOwner fsm ) { base .OnDestroy(fsm); } }
移动状态类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 using UnityEngine;using GameFramework.Fsm;using ProcedureOwner = GameFramework.Fsm.IFsm<Player>;using UnityGameFramework.Runtime;public class MoveState : FsmState <Player >{ private static readonly float EXIT_TIME = 1f ; private float exitTimer; private KeyCode moveCommand; protected override void OnInit (ProcedureOwner fsm ) { base .OnInit(fsm); } protected override void OnEnter (ProcedureOwner fsm ) { base .OnEnter(fsm); moveCommand = (KeyCode)(int )fsm.GetData<VarInt32>("MoveCommand" ); } protected override void OnUpdate (ProcedureOwner fsm, float elapseSeconds, float realElapseSeconds ) { base .OnUpdate(fsm, elapseSeconds, realElapseSeconds); exitTimer += elapseSeconds; if (exitTimer > EXIT_TIME) { ChangeState<IdleState>(fsm); } } protected override void OnLeave (ProcedureOwner fsm, bool isShutdown ) { base .OnLeave(fsm, isShutdown); exitTimer = 0 ; moveCommand = KeyCode.None; fsm.RemoveData("MoveCommand" ); } protected override void OnDestroy (ProcedureOwner fsm ) { base .OnDestroy(fsm); } }
玩家类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 using System.Collections.Generic;using UnityEngine;using GameFramework.Fsm;using StarForce;public class Player : MonoBehaviour { private static int SERIAL_ID = 0 ; private IFsm<Player> fsm; void Start ( ) { List<FsmState<Player>> stateList = new List<FsmState<Player>>() { new IdleState(), new MoveState() }; fsm = GameEntry.Fsm.CreateFsm<Player>((SERIAL_ID++).ToString(), this , stateList); fsm.Start<IdleState>(); } void Update ( ) { } private void OnDestroy ( ) { GameEntry.Fsm.DestroyFsm(fsm); } }
Inspector面板
FSM组件的Inspector面板可以实时看到所有正在运行的状态机,以及这些状态机当前处于的状态、运行时间。
最后
GameFramework解析 系列目录:GameFramework解析:开篇
个人原创,未经授权,谢绝转载!
GameFramework解析:有限状态机(FSM)