《游戏编程模式》-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中类共享代码的形式为继承,我们便可以为这些状态创建父类作为父状态,子状态进行继承即可。
下推状态机
通过状态栈来管理状态,把新状态推入栈,当前状态为栈顶的状态,当我们不需要该状态时出栈即可,这样便可以实现记忆状态的目的。