[参考于Bob Nystrom的游戏编程模式一书](Table of Contents · Game Programming Patterns)

引子

一个玩家有一个空中劈砍的动作,输入为J键

但有如下限定条件

  • 必须在空中
  • 必须有处于未受击状态
  • 必须必须必须处于猛男状态

用最简单的分支语句写就是这样

1
2
3
if(在空中&&未受击&&我是猛男&&输入为j)

​ 空中劈砍();

再加一个动作 释放条件如下

  • 必须在空中
  • 必须处于受击状态
  • 必须必须必须处于御女状态

就是这样写了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if(在空中)

{
if(未受击)
{
if(猛男)
{

}
else if(御女)
{
释放动作
}
}
else if(受击)
{
...
}

}

显然恶心的一批,条件繁杂,但这还只是两个动作,四个状态,如果动作更多,状态更多,那直接摆烂得了。

是这样的,摆呗;

解释状态机

​ 从逻辑方面来讲:实现状态机的目的就是解耦,解除繁杂冗余的的条件判定,状态机是一个角色所有状态的总和,但同时只能允许一个状态(并发状态机可以算一个例外,但其实并发也可以算一个,因为并发状态是由主状态和其他状态组成,其他状态依附于主状态而生,是一体的),它像一个机器一样在不同状态之间来回切换以及运转,实现方法有多种,例如最简单的枚举式状态机,和其他诸如有限状态机,并发状态机,分层状态机,下推状态机

枚举式(状态机)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enum 状态
{
状态1,
状态2.
状态3.
状态4.
}
swicth(枚举状态)
{
case 状态1:
break;
case 状态2:
break;
case 状态3:
break;
case 状态4:
break;
}

优点:一定程度上缓解了冗余感,将处理不同状态的代码聚合在了一起,直观就是看起来舒服,写起来更加明了。

缺点:处理逻辑的效果有限,没有真正的抽象化。

因此,Here comes THE FSM

有限状态机

图1

最简单FSM实现,首先定义一状态接口,每一个状态都要实现其接口,

伪代码

1
2
3
4
5
6
7
8
public interface IState
{
int StateID { get; }
string StateName { get; }
void OnStateEnter();
void OnStateUpdate();
void OnStateExit();
}

定义一个FSM类 收集所有状态,在内部实现状态转换功能等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class FSM
{
IState currentState;
Dictionary<int, IState> myStates;
int statesCount=>myStates.Count;
void ChangeState(IState newState)
{
currentState.OnStateExit();
currentState=newState;
currentState.OnStateEnter();
}
void AddState();
void RemoveState();

然后在对应生命周期函数允许就好了

1
2
3
4
5
6
7
8
public class PlayerMonoBehavior
{
FSM fsm;
void Update()
{
fsm.currentState.OnStateUpdate();
}
}

大抵就是这样,可以不断地根据需求继续优化和封装。

个人觉得每一个状态都需要重写一个类非常麻烦,就自己练了下

大致想法:“希望通过玩家定义的状态枚举,通过泛型传参的方式创建泛型状态机,在构造状态机的同时,根据状态枚举,自动创建不同的状态,我再通过委托传递,设置不同状态的函数即可

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
//状态基类
namespace CustomFsm
{
public class StateBase<TState> where TState : struct,Enum//泛型约束指定为enum
{
private FSM<TState> fsm;
private TState state;
public FSM<TState> Fsm { get { return fsm; } }
public TState State { get { return state; } }

//枚举获取哈希码的值就是其Int值,不过后来发现存在装箱,强转也不给过,一时半会没找到如何避免装箱的方法
public int StateID => state.GetHashCode();
//委托值元组记录三个状态函数,
public ValueTuple<Action, Action, Action> actionTuple;
public StateBase(TState state, FSM<TState> fsm)
{
this.state = state;
this.fsm = fsm;
}
public StateBase(TState state,FSM<TState> fsm, ValueTuple<Action, Action, Action> actionTuple):this(state,fsm)
=> this.actionTuple = actionTuple;
public void InitEvent(Action Enter=null,Action Update=null, Action Exist=null)
{
actionTuple.Item1 += Enter;
actionTuple.Item2 += Update;
actionTuple.Item3 += Exist;
}

}
}
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
//状态机
namespace CustomFsm
{
public class FSM<TState> where TState : Enum
{
StateBase<TState> currentState;
Dictionary<TState, StateBase<TState>> myStates;
public int statesCount => myStates.Count;
public FSM()
{
Array enumsValue = Enum.GetValues(typeof(TState));
CreateStatesDic(enumsValue);
}
//切换状态
public void ChangeState(TState state) /*=> currentState = myStates[GetIdByName(stateName)];*/
{
currentState.actionTuple.Item3.Invoke();
currentState = myStates[state];
}
//添加状态
public void AddState(StateBase<TState> state)
{
myStates.Add(state.State, state);
}
//移除状态
public bool RemoveState(TState state)
{
return myStates.Remove(state);
}
//清空状态
public void ClearState(){}
//初始化状态
public void InitStateEvent(TState state, Action Enter = null, Action Update = null, Action Exist = null)
{
myStates[state].ActionTuple.Item1 += Enter;
myStates[state].ActionTuple.Item2 += Update;
myStates[state].ActionTuple.Item3 += Exist;
}
//根据枚举创建状态并保存
private Dictionary<int, StateBase<TState>> CreateStatesDic(Array enums)
{
Dictionary<int, StateBase<TState>> dic = new Dictionary<int, StateBase<TState>>();
for (int i = 0; i < enums.Length; i++)
{
var state = new StateBase<TState>((TState)enums.GetValue(i), this);
AddState(state);
}
return dic;
}
}
}

这样用即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 public enum PlayerState
{
k1,
k2,
k3
}
class Test
{
static void Main(string[] args)
{
FSM<PlayerState> fSM = new FSM<PlayerState>();
fSM.InitStateEvent(PlayerState.k1, A, B, C);

}
void A() { }
void B() { }
void C() { }
}

还有许多有意思的地方可以加,比如:给状态间的转换增加异步的模式;让状态机成为大脑,游戏角色成为他的组件,写一个Ai;又或者更加傻瓜式,像Unity那样通过反射的方式实现自动化(反射获取方法后再检索函数名,规范命名如“状态1_Update”,有就自动添加进状态函数里)等等等

未完待续。。。🙅

并发状态机

分层状态机

下推状态机