状态机模板FSM

背景

  为什么需要状态机,试想一下,在肉鸽玩法中,玩家角色如果被冻结的话,应该阻止其移动,可以加个是否冻结中的bool字段 _isFroze,然后在移动逻辑中if判断。如果中了混乱的buff,左右移动方向交换,我们可以再加个字段再if判断,但是随着需求的增加,字段变多,管理起来就会变得混乱,如果再遇上需求变更,返工,屎山就这样堆起来了。
  但是每次使用状态机都得手动实现一次状态机,不说每个人的实现方式不一样,就使用方法也不一样,这已经使多人协作开发很痛苦了。为了统一状态机机制,也为了提高创建和使用状态机的便利性,提高代码复用性,框架提出了状态机模板,封装了状态机的使用方法的内部实现逻辑,达到即装即用的高效便利目的。
  状态机模板用于对象内部,所以也称为私有状态机。外部不需要也不关心内部状态的切换,可以认为,该状态机仅作为持有者的一个组件。所以持有者需要自行完成状态机的内置生命周期函数的转发。

使用

定义状态枚举

1
2
3
4
5
6
7
// 状态定义类型
// 可以直接用int或者值类型定义状态,类型仅要求用==值判断
public enum Def
{
Idle, // 闲置中
Walking, // 走动中
}

创建和初始化状态机

(以角色的移动状态机举例)

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
public class Holder // 持有者
{
// 创建状态机fsm,FsmComponent<持有者, 状态定义类型>
private FsmComponent<Holder, Def> _fsm = new FsmComponent<Holder, Def>(this);

/// <summary>
/// 初始化状态机
/// </summary>
private void InitFsm()
{
// 设置状态机的状态列表,状态机.SetFsm(dict 状态字典)
// dict状态字典 -> <状态定义类型,IFsmComponentState<持有者>>
_fsm.SetFsm(new Dictionary<Def, IFsmComponentState<Holder>>()
{
{Def.Idle, new IdleState()},
{Def.Walking, new WalkingState()}
});
}

private void Update()
{
// 完成状态机生命周期函数的实现
_fsmMovement.OnUpdate();
}

// 可以在状态类中实现状态对应逻辑,也可以写在己类中,对应状态类去调用
public void OnExitIdleState()
{
// 闲置状态退出时的逻辑
}
}

创建各状态对应的状态类

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
public class IdleState : IFsmComponentState<Holder> // 实现接口函数
{

// 进入状态时的逻辑
public void OnEnter(Holder owner, params object[] objs)
{
owner.animator.Play(闲置时的循环动画); // 在状态类中实现状态对应逻辑
}

// 处于该状态时的update()逻辑
public void OnUpdate(Holder owner, params object[] objs)
{
}

// 退出状态时的逻辑
public void OnExit(Holder owner)
{
owner.OnExitIdleState(); // 声明在owner类中,调用即可
}
}

public class WalkingState : IFsmComponentState<Holder>
{
// ……同上
}

使用状态机

1
2
3
4
5
6
7
8
9
// 切换状态,自动切换状态对应的逻辑
// 状态机.ChangeFsmState(状态定义)
_fsm.ChangeFsmState(Def.Idle);

// 判断当前状态
// 状态机.IsState(状态定义)
if(_fsm.IsState(Def.Idle))
{
}

注意事项

  1. 为避免出现不可预测的问题,状态机初始化时,设置了状态字典 SetFsm() 后,并不会设置初始状态,此时处于空状态,需要手动设置初始状态 ChangeFsmState()。