Menu
CoddyTech

クラスとしての状態

CoddyのUnityジャーニー「Unityのゲームアーキテクチャ」セクションの一部。レッスン 35/64。

セクション3のenumを使ったswitchでは、すべての状態の処理を1つのUpdateにまとめています。しかし、状態がそれぞれ独自のデータを持つようになると、この方法では拡張が難しくなります。1つの状態だけが使うタイマーが、ほかのすべての状態のフィールドと並んでMonoBehaviourのフィールドになり、各caseで使うデータを忘れずにリセットしなければなりません。C#のステートパターンでは、それぞれの状態に専用のクラスを用意し、すべてのクラスに1つのインターフェースを実装させます。この道路標識灯は点滅するか点灯し続け、Spaceキーで2つの状態を切り替えます。

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

チャレンジ

中級

タレットのスキャン状態を記述しましょう。

  1. IState.csで、void Enter();、void Tick();、void Exit();を持つpublic interface IStateを宣言します。
  2. TurretStates.csで、頭部を受け取るScanState : IStateを記述します。Enter:角度を0にし、頭部をColor.greenにします。Tick:頭部のローカルy角度を-45から45の間で、1秒あたり90度動かします。
  3. Turret:Startで状態を1つ開始し、Updateでそれを更新します。

「プレイ」を押すと、緑色の頭部が往復します。その後、「確認」を押します。

自分で試してみよう

using UnityEngine;

// ScanState

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

自分で練習してみよう: Unityプレイグラウンド