Menu
CoddyTech

将状态表示为类

本课属于 Coddy Unity 学习旅程的“Unity 游戏架构”单元。第 35 课,共 64 课。

第 3 单元中,基于枚举的 switch 把每个状态的工作都放在一个 Update 里。状态开始携带自己的数据后,这种做法就难以扩展了:只有一个状态使用的计时器最终会成为 MonoBehaviour 的字段,和其他所有状态的字段放在一起,而且每个 case 都必须记得重置自己用到的内容。C# 的状态模式会为每个状态创建一个独立的类,并让每个类实现同一个接口。这个道路信标会闪烁或保持常亮,按 Space 键可以在两种状态之间切换:

public interface IState
{
    void Enter();
    void Tick();
    void Exit();
}

public class BlinkState : IState
{
    private Renderer lamp;
    private float timer;

    public BlinkState(Renderer lamp)
    {
        this.lamp = lamp;
    }

    public void Enter() { timer = 0f; }

    public void Tick()
    {
        timer += Time.deltaTime;
        if (timer >= 0.5f)
        {
            timer = 0f;
            lamp.enabled = !lamp.enabled;
        }
    }

    public void Exit() { lamp.enabled = true; }
}

public class SteadyState : IState
{
    public void Enter() { }
    public void Tick() { }
    public void Exit() { }
}

public class Beacon : MonoBehaviour
{
    public Renderer lamp;
    private IState blink;
    private IState steady;
    private IState current;

    void Start()
    {
        blink = new BlinkState(lamp);
        steady = new SteadyState();
        SetState(steady);
    }

    void Update()
    {
        if (Keyboard.current.spaceKey.wasPressedThisFrame) SetState(current == steady ? blink : steady);
        current.Tick();
    }

    void SetState(IState next)
    {
        if (current != null) current.Exit();
        current = next;
        current.Enter();
    }
}

这些状态是普通的 C# 类,不是 MonoBehaviour。你可以用 new 创建它们,并通过构造函数把它们需要的内容传进去,这里传入的是灯的 Renderer,因为它们不是组件,不能调用 GetComponent,也不能在 Inspector 中显示字段。Unity 也不会调用它们的方法:Beacon 才是 MonoBehaviour,因此它的 Update 会在每一帧调用 current.Tick()。

timer 只属于 BlinkState,而它的 Enter 会在信标每次开始闪烁时把计时器设为 0。状态结束时,Exit 会进行清理:灯熄灭时也可能按下 Space 键,而 Exit 会重新点亮它,因此 SteadyState 开始时灯不会是暗的。SetState 会先运行旧状态的 Exit,再运行新状态的 Enter。第三种状态,例如慢速脉冲,只需再添加一个类,Beacon 的 Update 无需改变。

challenge icon

挑战

中等

编写炮塔的扫描状态。

  1. 在 IState.cs 中,声明 public interface IState,并包含 void Enter();、void Tick(); 和 void Exit();。
  2. 在 TurretStates.cs 中,编写 ScanState : IState,并传入炮塔头部。进入时:角度设为 0,头部颜色设为 Color.green。每次更新时:让头部的局部 y 角度在 -45 和 45 之间以每秒 90 度的速度来回扫动。
  3. Turret:在 Start 中进入一个状态,在 Update 中更新它。

按下“开始”:绿色头部会来回扫动。然后按“检查”。

亲手试试

using UnityEngine;

// ScanState

Unity 游戏架构中的所有课

自己练习: Unity 在线编辑器