クラスとしての状態
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 を変更する必要はありません。
チャレンジ
中級見張りの砲台が庭を守っています。矢印キーで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
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。