基于ET框架的FSM状态机设计
前言
    FSM状态机可以说是RPG、ACT等类型游戏的必备架构,而因为ET框架中并不带有基于ECS组件式思想所设计的状态机,因此萌生了自己设计的想法。而在参考了花花的GameFramwork解析:有限状态机(FSM)后想根据GF的思路来进行设计。
    但在设计的过程中也踩了不少坑,首先GF的状态机采取了传统OOP思路,由FSM(状态机管理器,其继承自FsmBase并实现IFSM接口)管理其中的所有FSMState(状态部分),在其生命周期中调用状态中对应的函数(OnEnter、OnUpdate、OnLeave等)。因此在根据GF第一次设计出FSMComponent组件时发现还是离不开OOP的思想,甚至变成了OOP+ECS这种奇怪的组合,ET中Component存储数据与System做逻辑。
 后续考虑后根据ECS的思想采用了和ET中UIEvent分发较为相似的思路。采用Attribute标记状态生命周期函数(OnInit、OnEnter、OnUpdate等)+全局FsmMgrComponent进行事件派发的方法。
基础结构:
- FsmComponent中存储当前状态机名fsmName以及状态名stateName作为标识,同时也可记录下前一个状态名lastStateName,且保留有GF中用于在FSM中传递的数据流data。 
- 全局具有FsmComponent,其在进入战斗场景阶段就加入到zoneScene中,在其组件的Awake函数中会搜索所有带有FsmAttribute的类,并将其对应的AFsmState加入到存储字典中,用于后续派发,避免了一个FsmComponent存储一个Fsm状态机且需实例化多个FsmState的情况,实现了FsmState的共用。(此时FsmState中仅存有对应的派发函数,不能在其类中设置变量,会造成多个同fsmName的FsmComponent的混用) - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23- public override void Awake(FsmMgrComponent self) 
 {
 //将dll中具有Fsm标签的class加入到对应fsm的字典中
 FsmMgrComponent.Instance = self;
 self.allFsm = new Dictionary<string, Fsm>();
 foreach (Type type in Game.EventSystem.GetTypes(typeof(FsmAttribute)))
 {
 object[] attrs = type.GetCustomAttributes(typeof (FsmAttribute), false);
 if(attrs.Length==0) continue;
 FsmAttribute fsmAttribute = attrs[0] as FsmAttribute;
 AFsmState aFsmState=Activator.CreateInstance(type) as AFsmState;
 if (!self.allFsm.ContainsKey(fsmAttribute.fsmType))
 {
 self.allFsm.Add(fsmAttribute.fsmType,new Fsm());
 }
 
 self.allFsm[fsmAttribute.fsmType].allEvents.Add(type.Name.Split('_')[1],aFsmState);
 }
 
 }
- AFsmState抽象类中具有OnEnter、Onupdate、OnExit、Ondestroy的对应状态派发函数,其传入值为当前调用的FsmComponent,同时使用data数据流进行不同生命周期函数间的交互。 
- 保留了ECS思想中Component的通用性,也维护了GF的状态机中类似于FSMState<Player>的泛型标记,转而用stateName来代替(ET中不存在泛型组件)。 
△后续进行流程图补充

具体使用:
以一个角色Unit作为例子:
- 在AfterOfflineUnitCreate的事件中加入FsmComponent,并设定其fsm类型名为FsmType.PlayerFSM,此时只会分发对应类型的FsmState - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13- public class AfterOfflineUnitCreate_CreateUnitView:AEvent<EventType.AfterOfflineUnitCreate> 
 {
 protected override async ETTask Run(AfterOfflineUnitCreate args)
 {
 ......
 
 //每个Unit中加入FsmComponent用于管理状态机
 FsmComponent fsm=args.unit.AddComponent<FsmComponent,string>(FsmType.PlayerFSM);
 
 ......
 await ETTask.CompletedTask;
 }
 }
- 书写好对应的IdleState、WalkState 
 PlayerFSM_IdleState:- 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- using System; 
 using UnityEngine;
 namespace ET
 {
 [Fsm(FsmType.PlayerFSM)]
 public class PlayerFSM_IdleState:AFsmState
 {
 public override void OnEnter(FsmComponent fsmComponent)
 {
 Debug.Log("Enter idle");
 
 AnimatorComponent animatorComponent = fsmComponent.Parent.GetComponent<AnimatorComponent>();
 if (animatorComponent != null)
 {
 animatorComponent.MotionType = MotionType.Idle;
 }
 }
 public override void OnUpdate(FsmComponent fsmComponent)
 {
 Debug.Log("Update idle");
 }
 
 public override void OnExit(FsmComponent fsmComponent)
 {
 Debug.Log("Exit idle");
 AnimatorComponent animatorComponent = fsmComponent.Parent.GetComponent<AnimatorComponent>();
 if (animatorComponent != null)
 {
 animatorComponent.ResetMotionType();
 }
 }
 
 public override void OnDestroy()
 {
 
 }
 }
 }- PlayerFSM_WalkState: - 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- using System; 
 using UnityEngine;
 namespace ET
 {
 [Fsm(FsmType.PlayerFSM)]
 public class PlayerFSM_WalkState:AFsmState
 {
 public override void OnEnter(FsmComponent uiComponent)
 {
 Debug.Log("Enter Walk");
 AnimatorComponent animatorComponent = uiComponent.Parent.GetComponent<AnimatorComponent>();
 if (animatorComponent != null)
 {
 animatorComponent.MotionType = MotionType.Run;
 }
 
 }
 public override void OnUpdate(FsmComponent uiComponent)
 {
 Debug.Log("Update Walk");
 }
 public override void OnExit(FsmComponent uiComponent)
 {
 Debug.Log("Leave Walk");
 AnimatorComponent animatorComponent = uiComponent.Parent.GetComponent<AnimatorComponent>();
 if (animatorComponent != null)
 {
 animatorComponent.ResetMotionType();
 }
 }
 
 public override void OnDestroy()
 {
 
 }
 }
 }
- 在对应的Command命令的Execute函数中获取Unit的FsmComponent组件并切换状态: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21- namespace ET.Demo.Command 
 {
 [Command(CommandType.MoveCommand)]
 public class MoveCommand:ACommand
 {
 public override void OnInit(CommandComponent commandComponent)
 {
 
 }
 public override void Execute(CommandComponent commandComponent, TypeEntityPair entityPair,params object[] param)
 {
 Entity entity = entityPair.entity;
 FsmComponent fsm = entity.GetComponent<FsmComponent>();
 if(fsm==null) return;
 
 if (!fsm.isRunning) fsm.Start("WalkState");
 else fsm.ChangeState("WalkState");
 }
 }
 }
后期拓展:
增加类似baseState状态的复用机制:
 在实际使用的过程发现无法实现状态机通用状态的复用,比如某个Enemy的攻击state具有特殊定义,也必须重写其他states并分离出一个新的FSM类型,因此对当前的ET状态机的部分逻辑进行更改。
 以下是新版的FsmComponent,主要是加入了allStates来存储当前Fsm状态机中的所有状态,无需再将FsmType作为一个类型导入,而是仅仅作为分发具体声明周期的查询参数。并在Awake时赋值对其进行赋值
 这里仅放上修改部分的流程图,其他和之前基本无变化

- fsmName仅作为向FsmMgrComponent查询并分发对应生命周期函数的依据
- allStates中存储状态机中所有可供转换的状态名
- FsmComponent中的fsmName和allStates在AwakeSystem的Awake(…)中进行赋值



