Stateパターンとは?

デザインパターンとは、プログラミングでよく出てくる問題に対する「定番の解決方法」のことです。

料理のレシピのように、「こういう問題にはこの方法で解決しよう!」という先人の知恵をまとめたものです。

Stateパターンは、オブジェクトの状態が変わったときに、その動作も変えたい場合に使えるデザインパターンです。

Stateパターンの例

例えば信号機。赤黄青と色が変わります。

その色によって私たちが取る行動は変わります。

  • 赤:止まれ
  • 黄:注意して止まる準備をする
  • 青:進め

ゲームでもNPCの行動でも

  • 待機中
  • 巡回移動中
  • 敵を発見して追跡中

といった状態によって取る行動が変わります。

このくらいならif文やswitch文でも実装できそうです。

ですが状態が増えていくといくつか問題が起こります。

  • switch文が巨大になる
  • 状態ごとの処理が混在して分かりづらい
  • 新しい状態を追加するのが大変
  • 状態固有の変数をどこに持たせるか難しい

Stateパターンの考え方

Stateパターンでは各状態(State)ごとにクラス化します。

例えば、

  • 「赤信号」「黄色信号」「青信号」
  • 「待機中」「巡回移動中」「敵を発見して追跡中」

といった単位です。

そして今の状態に応じた処理を実行します。

switchStateパターン
一カ所に全ての状態の処理を書く状態ごとにクラスを作る
状態が増えると巨大化する状態が増えても個別のクラスには影響しない
状態固有のデータ管理が難しい状態固有のデータを状態別クラス内で管理できる
修正時に他の状態に影響が出る可能性がある修正は対象の状態別クラスのみ

Stateパターンの実装の例

実際にStateパターンを実装するコードの例を考えます。

あくまで例なので、ご自身のプロジェクトなどにあわせて作ってください。

インターフェイス

まず各状態ごとのインターフェイスを考えます。

public interface IState
{
	// 状態に入る
	void Enter();
	// フレームごとの処理
	void Update();
	// 状態を抜ける
	void Exit();
}

各状態の実装

インターフェイスを継承した各状態のクラスを作成します。

実際に待機状態のクラスを作ってみます。

public class IdleState : IState
{
		private StateController _controller;
		
		public IdleState(StateController controller)
		{
			_controller = controller;
		}
		
		public void Enter()
		{
			// 待機状態に入った
		}
		
		public void Update()
		{
			// 毎フレームの処理
			// 例えば、プレイヤーが近付いたら追跡状態に移る
			float distance = Vector3.Distance(Player.transform.position, transform.position);
			if(distance < 5f)
			{
				// 近くにいるので追跡状態に移行
				_controller.ChangeState(new ChaseState(_controller));
			}
		}
		
		public void Exit()
		{
			// 状態から抜ける
		}
}

Enterメソッドで状態中の変数などを初期化できます。

Exitメソッドでは状態から抜ける際の後処理を記述できます。

これを状態ごとに好きなだけ作成します。

状態を制御する

次に状態を制御するクラスを用意します。

public class StateController : MonoBehaviour
{
	// 現在の状態
	private IState _currentState;

	private void Start()
	{
		ChangeState(new IdleState(this));
	}
	
	private void Update()
	{
		// 現在の状態を実行
		_currentState?.Update();
	}
	
	// 状態の変更
	public void ChangeState(IState state)
	{
		// 現在の状態を終了
		_currentState?.Exit();
		
		// 新しい状態に変更
		_currentState = state;
		
		// 新しい状態を開始
		_currentState?.Enter();
	}
}

実装してみた結果

このような実装にしたことで、各状態ごとの責任が明確になりました。

IdleStateは待機状態の、ChaseStateは追跡状態の処理のみ記述すればよくなります。

状態が増える場合は状態用のクラスを用意すればよくなるので、

switch文が肥大化することもありません。

状態固有のデータも管理しやすくなりました。

まとめ

今回はデザインパターンのひとつ、Stateパターンについて紹介しました。

ゲームを作る上で様々な場面で状態によって動作を変える必要がある状況はよく発生します。

if文やswitch文で実装した場合複雑になりがちですが、

Stateパターンを利用すれば綺麗に実装することができます。

状態ごとの処理がクラス内で完結しているので追加や修正もしやすいでしょう。

コードが複雑になってきたな、と思ったらStateパターンを導入する機会かもしれません。

📣おしらせ!

Unity Asset Storeで

The 17th Unity Awards – Publisher Bundle

が開催中です。

Unity Awardsを受賞した限定バンドルを格安で入手するチャンスです。

🔗関連ページ