Menu
CoddyTech

クラスとしての状態

CoddyのUnityジャーニー「Unityにおけるゲームアーキテクチャ」セクションの一部。レッスン 28/50。

第 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();
    }
}

状態は MonoBehaviour ではなく、通常の C# クラスです。new を使って作成し、コンストラクターを通じて必要なものを渡します。ここではランプの Renderer です。これらはコンポーネントではないため、GetComponent を呼び出したり、Inspector にフィールドを表示したりできません。また、Unity がこれらのメソッドを呼び出すこともありません。MonoBehaviour なのは Beacon なので、その Update が毎フレーム current.Tick() を呼び出します。

timer は BlinkState だけが持つもので、Enter は信号機が点滅を始めるたびに値を 0 に設定します。Exit は状態が終わるときに後片付けをします。ランプが消えている間に Space が押されることもありますが、Exit がランプを再び点灯させるため、SteadyState は暗いランプで始まることはありません。SetState は古い状態の Exit を実行してから、新しい状態の Enter を実行します。ゆっくり点滅する状態など、3 つ目の状態を追加する場合は、クラスをもう 1 つ作るだけで済み、Beacon の Update を変更する必要はありません。

challenge icon

チャレンジ

中級

見張りの砲台が庭を守っています。矢印キーでPlayerを動かせます(準備完了)。砲台に、クラスとして記述する2つの状態を与えましょう。

  • IState.csで、void Enter()、void Tick()、void Exit()を持つpublic interface IStateを宣言します。
  • TurretStates.csで、これを実装する通常のクラスを2つ記述します。ScanStateはコンストラクターで砲台の頭部を受け取ります。頭部を左右に振ります。頭部のローカルy角度は、0から始まり、スキャンを開始するたびに上向きに動き、1秒あたり90度の速度で-45から45の間を移動します。Enterでは頭部の色もColor.greenにします。
  • AlertStateは頭部とプレイヤーを受け取ります。Enterでは頭部の色をColor.redにしてIntruder!をログに記録します。Tickでは傾けずに、y軸方向だけで頭部をプレイヤーに向けます。ExitではAll clearをログに記録します。
  • Turret.csでは、Startで両方の状態を作成し、スキャンを開始します。毎フレーム、プレイヤーが砲台からrange(4)より近ければ警戒状態に切り替え、近くなければスキャン状態に戻してから、現在の状態のTickを実行します。状態が変わるときは、古い状態のExitを実行してから、新しい状態のEnterを実行します。

Playを押し、砲台に近づいてから離れ、最後にCheckを押します。

自分で試してみよう

using UnityEngine;

// ScanState と AlertState
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Unityにおけるゲームアーキテクチャのすべてのレッスン