img

《游戏编程模式》-Bob Nystrom

书籍汉化版网址:https://gpp.tkchu.me/

笔记目录:https://hmxs.games/posts/2000/


状态模式

有限状态机FSM

有限状态机帮助我们梳理角色的状态,防止出现一些可能由“连点”等操作导致的让一个动作不断重复的bug。unity中的动画系统很大程度上便是基于FSM进行设计的。本章通过FSM引入状态模式

一张画有盒子的图表,盒子代表了站立,跳跃,俯卧和跳斩。标记了按键的按下和释放的箭头连接了这些盒子。

定义

  • 状态机拥有所有可能状态
  • 状态机同时只能存在一个状态
  • 状态机可以接收连续的输入与事件
  • 每个状态都有一系列状态转移方法,这些方法与输入和另一状态相关

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
namespace StatePattern
{
public class SimpleFSM : MonoBehaviour
{
// 状态枚举
private enum State
{
Idle,
Attack,
Walk
}

private State _current;// 当前状态

private void Start()
{
_current = State.Idle;// 初始状态
}

private void Update()
{
// 每帧判断状态
switch (_current)
{
case State.Idle:
Idle();
break;
case State.Attack:
Attack();
break;
case State.Walk:
Walk();
break;
default:
throw new ArgumentOutOfRangeException();
}
}

// 具体状态逻辑
private void Idle() { }
private void Attack() { }
private void Walk() { }
}
}

面向对象的状态模式

GoF: 允许一个对象在其内部状态发生变化时改变自己的行为,该对象看起来好像修改了它的类型

Switch方法已经可以很好的实现一个简单的状态机了,但是在FSM中,我们可以更进一步,通过OOP的思想来编写FSM。通过实现状态接口、让每个状态成为类、状态委托这三步便可以实现这一点。其核心目的是将状态的行为和数据封装到单一类中。

动态面向对象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
61
62
63
64
65
namespace StatePattern
{
public abstract class FsmBase
{
public virtual void OnEnter() {}

public virtual void OnExit() {}

public abstract void OnUpdate();

public abstract FsmBase TransitState();
}

public class IdleState : FsmBase
{
private float _changeTime;

public IdleState(float changeTime)
{
_changeTime = changeTime;
}

public override void OnEnter()
{
Debug.Log("Idle Enter");
}

public override void OnUpdate()
{
Debug.Log("Idle Update");
}

public override FsmBase TransitState()
{
_changeTime -= Time.deltaTime;
if (_changeTime < 0.1f)
{
return new AttackState();
}
return this;
}
}

public class AttackState : FsmBase
{
public override void OnExit()
{
Debug.Log("Attack Exit");
}

public override void OnUpdate()
{
Debug.Log("Attack Update");
}

public override FsmBase TransitState()
{
if (Input.GetKeyDown(KeyCode.A))
{
return new IdleState(3f);
}
return this;
}
}
}

人物脚本

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
namespace StatePattern
{
public class Test : MonoBehaviour
{
private FsmBase _current;

private void Start()
{
_current = new IdleState(1f);
_current.OnEnter();
}

private void Update()
{
FsmBase temp = _current.TransitState();
if (temp != _current)
{
_current.OnExit();
_current = temp;
_current.OnEnter();
}
_current.OnUpdate();
}
}
}

并发状态机

当我们通过FSM来管理一个角色的状态,有走、跑、跳等,若我们现在想为这个角色添加各类武器,若我们仍然通过单一FSM来进行管理,状态就会变成走斩、跑斩、跳劈、走射、跑射等,如果我们还想添加别的角色模块,就会让单一FSM的状态数量成指数上涨,这并不易于编写。

这种情况下,并发状态机便可以被使用,其核心便是分离角色状态,通过复数状态机进行管理,如上述例子中,可以分离为“角色动作”与“角色装备”,通过两个同时运行的状态机进行管理。

分层状态机

当状态机中有多个状态有部分相似的行为时,我们便可以通过分层状态机来减少代码编写量。

因为我们采用了OOP的思想来建构状态机,而OOP中类共享代码的形式为继承,我们便可以为这些状态创建父类作为父状态,子状态进行继承即可。

下推状态机

通过状态栈来管理状态,把新状态推入栈,当前状态为栈顶的状态,当我们不需要该状态时出栈即可,这样便可以实现记忆状态的目的。