层次状态机HFSM
背景
层次状态机 即 HFSM。在开发过程中,角色和系统的行为复杂性不断增加。传统状态机在处理复杂逻辑时,可能导致状态间的逻辑层次混乱。尤其是当一个状态需要包含多个子状态时,维护这些状态变得非常困难。引入层次状态机HFSM可以很好地解决这些问题。
层次状态机的核心思想是,将一个状态机也作为状态,这样就能够在状态机内部嵌套多个子状态机,实现更复杂的状态切换逻辑,特别是互斥状态间的隔离,与包含状态间的共存。例如,角色可以有一个 移动状态 ,但这个状态下还可以细分为 行走 和 奔跑 子状态,明显的移动状态与行走和奔跑状态属于包含关系。角色还可以有一个 闲置状态 ,明显的闲置状态与移动状态属于互斥关系。
层次状态机可以帮助我们清晰地管理这种复杂逻辑,使代码的组织结构更加合理,满足更多更复杂的需求。
使用
私有HFSM
例子说明:
角色Player有行为状态机 fsm ,行为状态机下有 移动状态 Move 、 闲置状态 Idle 。移动状态下细分为 行走状态 Walk 、 奔跑状态 Run 。所以我们将移动状态定义为层次状态机 moveHfsm 。
结构如下:
1 2 3 4 5 6 7 8
| fsm{ Idle, Move{ Walk, Run, } }
|
创建状态定义枚举
避免成环详见下文注意事项
1 2 3 4 5 6 7 8 9
| public enum Def { Idle, Move, Walk, Run, }
|
声明层次状态机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public class MoveHfsm : HfsmComponent<Player, Def> { public MoveHfsm(Player owner) : base(owner) { } public override void OnEnter(Player owner, params object[] objs) { base.OnEnter(owner,objs); } public override void OnUpdate(Player owner, params object[] objs) { base.OnUpdate(owner,objs); } public override void OnExit(Player owner) { base.OnExit(owner); } }
|
声明状态类
同状态机模板的状态声明部分。状态机模板FSM
1 2 3 4 5 6 7 8 9 10 11 12
| public class IdleState : IFsmComponentState<Player> { } public class WalkState : IFsmComponentState<Player> { } public class RunState : IFsmComponentState<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
| public class Player { public FsmComponent<Player, Def> fsm; public MoveHfsm moveHfsm; moveHfsm = new MoveHfsm(this); moveHfsm.SetFsm(new Dictionary<Def, IFsmComponentState<Player>>() { {Def.Walk, new WalkState()}, {Def.Run, new RunState()}, }); fsm = new FsmComponent<Player, Def>(this); fsm.SetFsm(new Dictionary<Def, IFsmComponentState<Player>>() { {Def.Idle, new IdleState()}, {Def.Move, moveHfsm}, }); }
|
实现私有状态机的生命周期函数
1 2 3 4 5 6 7 8
| public class Player { private void Update() { fsm.OnUpdate(); } }
|
使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
fsm.ChangeFsmState(Def.Idle);
fsm.ChangeFsmState(Def.Move);
moveFsm.ChangeFsmState(Def.Run);
if(fsm.IsState(Def.Run)) if(fsm.IsState(Def.Move)) if(fsm.IsState(Def.Idle)) if(fsm.IsState(Def.Walk))
|
公共层次状态机
使用方法与私有HFSM基本相同
声明层次状态机所继承的类名有区别:
1 2
| public class MyHFSM : BaseHfsm
|
注意事项
- 设置了fsm或hfsm的状态字典后,fsm或hfsm的当前状态为空。与私有状态机的规则保持一致。
- 初始化状态机时需要由下至上进行初始化,因为顶层fsm或hfsm,需要引用到 下层的fsm或hfsm实例对象,进行赋值和数据交换,交换初始化顺序会出现报空。
- 初始化状态字典时,需要留意下层状态不能包含上层状态,否则会成环。(在IsState()中不做环检测和跳环算法,应该在源头避免成环,即初始化状态字典时避免成环)。可以不同层次的状态,使用不同的状态定义类型,这样即可在初始化状态字典时进行语法检测。
- 切换状态时,应该一层再一层按顺序进行切换,否则跳层切换状态,会找不到hfsm所持有的状态。
- 判断当前状态时,如果状态不相符,会进入到下一层hfsm的状态判断,最坏情况下查找的时间复杂度为O(n)。(鉴于层次状态机总体上为树形结构,该时间复杂度明显超出预期,后续需要进行查找操作的优化)
内部实现
- 层次状态机机制的核心思想,是将状态机也视为一种状态,状态机持有状态机,即可完成状态机的嵌套,即达到状态间分层次的目的。所以层次状态机首先应该是一个状态机,应该继承FsmComponent类,同时,也被视为状态,应该继承状态的接口:
1
| public class HfsmComponent<Owner, Key> : FsmComponent<Owner, Key>, IFsmComponentState<Owner>
|