状态机
本课属于 Coddy Unity 学习旅程的“Unity 游戏架构”单元。第 37 课,共 64 课。
Beacon 的 SetState 方法在所有具有状态的对象中都会相同:炮塔、敌人、门。可以把这段代码放在一个独立的类中,也就是状态机里,由它保存当前状态并切换状态。每个对象都创建一个状态机并调用它,而不是重复编写代码:
public class StateMachine
{
public IState Current { get; private set; }
public void ChangeState(IState next)
{
Current?.Exit();
Current = next;
Current.Enter();
}
public void Tick()
{
Current?.Tick();
}
}
public class Beacon : MonoBehaviour
{
public Renderer lamp;
private StateMachine machine = new StateMachine();
private IState blink;
private IState steady;
void Start()
{
blink = new BlinkState(lamp);
steady = new SteadyState();
machine.ChangeState(steady);
}
void Update()
{
if (Keyboard.current.spaceKey.wasPressedThisFrame) machine.ChangeState(machine.Current == steady ? blink : steady);
machine.Tick();
}
}第一次调用 ChangeState 时没有要退出的状态:Current 是 null,?. 会跳过对 Exit 的调用。第 1 单元提醒过,?. 无法识别 Unity 的假 null;这指的是已销毁的 Unity 对象,而这些状态是普通的 C# 对象,所以这里使用 ?. 是安全的。Current 有一个 private set,因此没有脚本能绕过 ChangeState 及其 Exit 和 Enter 来切换状态。
只要两个状态会修改同一个东西,ChangeState 中的顺序就很重要。Exit 会撤销旧状态所做的更改,然后 Enter 设置新状态,因此最终由新状态决定结果。如果 DarkState 的 Enter 关闭了灯,并且在 BlinkState 的 Exit 之前运行,那么 Exit 就会在黑暗状态下重新打开灯。StateMachine 也是一个普通类:灯塔、炮塔和敌人会分别用 new 创建自己的状态机,并在各自的 Update 中调用它的 Tick。
挑战
中等使用状态机运行玩家的移动状态。
- 在
StateMachine.cs中,编写public class StateMachine,包含public IState Current { get; private set; },并编写一个会对当前状态进行 Tick 的Tick()。 ChangeState(IState next)会忽略当前状态;否则,调用 Exit,设置Current,再调用 Enter。Player:Start 创建machine、walk和sneak,并进入行走状态;Update 在按住空格键时进入潜行状态,否则进入行走状态,然后调用 Tick。
按“开始”并按住空格键:Sneak: enter。然后按“检查”。
亲手试试
// StateMachine:Current、ChangeState 和 Tick
Unity 游戏架构中的所有课
自己练习: Unity 在线编辑器