Menu
CoddyTech

状態遷移

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

状態がいつ変わるかを決めるのは誰でしょうか?所有者が決めるようにすると、switchの場合と同じように、すべてのルールが1つのUpdateに戻ってしまいます。一般的な答えは、各状態が自分自身の終了方法を知っているというものです。Tickの中で条件を確認し、次の状態をマシンに要求します。この爆弾はプレイヤーが近づくまで待ち、その後導火線に火をつけ、プレイヤーが逃げると待機状態に戻ります。

public class Bomb : MonoBehaviour
{
    public Transform player;
    public StateMachine machine = new StateMachine();
    public IState idle;
    public IState fuse;

    void Start()
    {
        idle = new IdleState(this);
        fuse = new FuseState(this);
        machine.ChangeState(idle);
    }

    void Update() { machine.Tick(); }

    public bool PlayerNear()
    {
        return Vector3.Distance(transform.position, player.position) < 3f;
    }
}

public class IdleState : IState
{
    private Bomb bomb;
    public IdleState(Bomb bomb) { this.bomb = bomb; }

    public void Enter() { }
    public void Exit() { }

    public void Tick()
    {
        if (bomb.PlayerNear()) bomb.machine.ChangeState(bomb.fuse);
    }
}

public class FuseState : IState
{
    private Bomb bomb;
    private float timer;
    public FuseState(Bomb bomb) { this.bomb = bomb; }

    public void Enter() { timer = 2f; }
    public void Exit() { }

    public void Tick()
    {
        if (!bomb.PlayerNear())
        {
            bomb.machine.ChangeState(bomb.idle);
            return;
        }
        timer -= Time.deltaTime;
        if (timer <= 0f)
        {
            Debug.Log("Boom!");
            Object.Destroy(bomb.gameObject);
        }
    }
}

各状態はコンストラクターで爆弾を受け取り、それを通じてマシン、プレイヤーの確認、他の状態にアクセスします。爆弾はStartで各状態を一度だけ作成するため、状態が別の状態をnewで作成することはありません。コンストラクター内で互いを作成する2つの状態は、スタックがオーバーフローするまでループしてしまいます。爆弾のUpdateはマシンをTickするだけで、ルールはそれぞれが属する状態にあります。2秒の導火線はFuseStateだけが扱います。

ChangeStateはFuseStateのExitとIdleStateのEnterをすぐに実行しますが、呼び出し後もFuseStateのTickの残りは実行されます。returnで処理を止めないと、すでに終了した状態の導火線がもう1フレーム燃えてしまいます。DestroyはObject.Destroyと記述します。MonoBehaviourはUnityのObjectクラスからこれを継承しますが、通常のクラスではクラス名を指定する必要があります。

challenge icon

チャレンジ

中級

FlytrapStates.csの各Tickで、ハエトリグサの状態遷移を記述しましょう。

  1. 開く: trap.PlayerInReach()のとき、trap.machine.ChangeState(trap.snap);を呼び出します。
  2. 閉じる: timerにTime.deltaTimeを加算します。0.5になったら、プレイヤーがまだ範囲内にいる場合はtrap.chewに、そうでなければtrap.openに状態を変更します。
  3. かむ: timerを3からカウントダウンします。0になったら、trap.openに状態を変更します。

「プレイ」を押して、罠に近づき、そのあと回避しましょう。それから「確認」を押します。

自分で試してみよう

using UnityEngine;

public class OpenState : IState
{
    private Flytrap trap;

    public OpenState(Flytrap trap)
    {
        this.trap = trap;
    }

    public void Enter()
    {
        trap.Paint(new Color(0.3f, 0.8f, 0.2f));
    }

    public void Tick()
    {
        // プレイヤーが届く範囲に入ったら閉じる
    }

    public void Exit()
    {
    }
}

public class SnapState : IState
{
    private Flytrap trap;
    private float timer;

    public SnapState(Flytrap trap)
    {
        this.trap = trap;
    }

    public void Enter()
    {
        timer = 0f;
        trap.Paint(new Color(1f, 0.55f, 0.1f));
    }

    public void Tick()
    {
        // 0.5秒後:プレイヤーがまだ届く範囲にいれば咀嚼し、いなければ再び開く
    }

    public void Exit()
    {
    }
}

public class ChewState : IState
{
    private Flytrap trap;
    private float timer;

    public ChewState(Flytrap trap)
    {
        this.trap = trap;
    }

    public void Enter()
    {
        timer = 3f;
        trap.Paint(new Color(0.5f, 0.05f, 0.1f));
        Debug.Log("Chomp!");
    }

    public void Tick()
    {
        // 3秒後:再び開く
    }

    public void Exit()
    {
    }
}

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

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