状態遷移
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クラスからこれを継承しますが、通常のクラスではクラス名を指定する必要があります。
チャレンジ
中級FlytrapStates.csの各Tickで、ハエトリグサの状態遷移を記述しましょう。
- 開く:
trap.PlayerInReach()のとき、trap.machine.ChangeState(trap.snap);を呼び出します。 - 閉じる:
timerにTime.deltaTimeを加算します。0.5になったら、プレイヤーがまだ範囲内にいる場合はtrap.chewに、そうでなければtrap.openに状態を変更します。 - かむ:
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プレイグラウンド