はじめに
Stateパターンを試す記事です。 ステートパターン自体は以前から利用していたのですが、公式とは異なる実装だったので、公式のStateパターンを基に試してみました。
サンプルプロジェクト github.com
そもそもStateパターンとは
Gofパターンの一種で、オブジェクトの状態をクラスとして管理し、状態が変わるごとに振る舞いを変えるパターン。振る舞いを条件ごとに変えるのなら、単純にIfなどを使えば良いのですが、新たに状態を追加した場合は全体の条件を見直す必要があったりと色々不便です。この問題を解決しようとする思想。
まずは、あまりよろしくない方法で状態ごとに振る舞いを実行したいと思います。
単純にEnumを使っての実装
これでも単純にIfとフラグを使って分けるよりかは良いのですが、新たに状態を加える場合はどうしてもEnumを触る必要があります。また、条件として加える際には、すべての分岐をチェックする必要があったりと、あまりよろしくないです。
private enum State { Idle, Move, Jump } private State _state = State.Idle; private void Update() { OnStateChanged(); } private void OnStateChanged() { switch (_state) { case State.Idle: if (Input.GetKeyDown(KeyCode.Space)) { _state = State.Jump; } else if (Input.GetKey(KeyCode.W)) { _state = State.Move; } else { Idle(); } break; case State.Jump: Jump(); break; case State.Move: Move(); break; } } private void Idle() { Log("Idle"); } private void Move() { Log("Move"); _state = State.Idle; } private void Jump() { Log("Jump"); _state = State.Idle; } private void Log(string value) { Debug.Log(value); }
StateMachineを使って試してみる
先程のEnumで実装したものとは異なり、クラスを一つの状態として扱うように実装してみました。 キーを押すと、それに対応した状態がLogに表示されるサンプルです。公式のデモプロジェクトを基に作成しました。
状態をクラスごとに定義しているので、Enumの時の様に全体を見直す必要が無くなりました。そのため、新たに状態を追加したい場合も、他の条件や分岐を見直す必要がなく、単純にクラスを実装すれば良くなりました。
IState
各々の状態クラスで呼ばれるメソッドを定義しています。
public void Enter(); public void Update(); public void Exit();
IdleState
IdleクラスやAttackクラスが状態クラスで、遷移条件が合致した場合に、StateMachineに遷移を依頼います。
private Player _player; public IdleState(Player player) { _player = player; } public void Enter() { Debug.Log("IdleState Enter"); } public void Update() { if (_player.IsAttacked) { _player.StatetateMachine.TransitionTo(_player.StatetateMachine.attackState); } Debug.Log("IdleState Update"); } public void Exit() { Debug.Log("IdleState Exit"); }
AttackState
private Player _player; public AttackState(Player player) { _player = player; } public void Enter() { Debug.Log("AttackState Enter"); } public void Update() { _player.StatetateMachine.TransitionTo(_player.StatetateMachine.idleState); Debug.Log("AttackState Update"); } public void Exit() { Debug.Log("AttackState Exit"); }
StateMachine
ここで状態をまとめて管理しており、現在の状態に対して、Interface経由でUpdateやEnterを呼び出しています。
private IState state; public AttackState attackState; public IdleState idleState; public StateMachine(Player player) { attackState = new AttackState(player); idleState = new IdleState(player); } public void Initialize(IState state) { this.state = state; state.Enter(); } public void TransitionTo(IState nextState) { state.Exit(); state = nextState; nextState.Enter(); } public void Update() { state?.Update(); }
Player
ここでStateMachineを回して、状態に応じた振る舞いを依頼しています。
public StateMachine StatetateMachine; public bool IsAttacked = false; private void Start() { StatetateMachine = new StateMachine(this); StatetateMachine.Initialize(StatetateMachine.idleState); } private void Update() { IsAttacked = Input.GetKeyDown(KeyCode.Space); StatetateMachine.Update(); }
おわりに
UnityだとAnimatorで使われていたりと、使いどころが多いので是非覚えておきたいですね。また、ここからObserverを混ぜて実装するのもありだと思います。
是非、読者登録をしていただくと助かります!