はじめに
最近は、基本に戻って「オブジェクト指向でなぜつくるのか」という本を読んでいます。この内容について今度ご紹介できたらと思います。作業の合間で糖分補給のために、ラムネかチョコを食べるのですが、ブラックサンダーのマンゴー味にハマってます。
今回は、デザインパターンの1つであるStateパターンの基本的な使い方や考え方についてご紹介したいと思います。
Stateパターンってこんな感じか~といった導入のきっかけやふんわりとしたイメージをつかんでいただけたら嬉しいです!
まだまだ勉強中の身ではありますので、誤っている表現や使い方などがあれば、ぜひご指摘いただけると幸いです。
自分自身の成長にもつながり、この記事の質もより良くなると思っています。どうぞよろしくお願いいたします。
目次
- Stateパターンってなに??
- Stateパターンのメリット
- 具体的な使い方
- まとめ
- 参考文献
Stateパターンってなに?
ロボットを操作するゲームを作るとします。
このロボットは3つの状態があります。
・待機(Idle)
・歩く(Move)
・攻撃(Attack)
このロボットが「待機状態」から「歩く」「攻撃」状態に切り替わるとき、どうやって処理を書きますか??
基本的には2通りのやり方が考えられます。
◇ if文
if(isIdle)
{
//待機中の処理
}
else if(isMove)
{
//歩いているときの処理
}
else if(isAtack)
{
//攻撃しているときの処理
}
◇ switch文
public enum EnemyState //列挙型で状態を管理
{
Idle,
Move,
Attack
}
EnemyState currentState;
void Update()
{
switch (currentState)
{
case EnemyState.Idle:
IdleBehavior();
break;
case EnemyState.Move:
MoveBehavior();
break;
case EnemyState.Attack:
AttackBehavior();
break;
}
}
これでも処理は可能だと思いますが、状態が増えると....
- ネストが深くなる
- 条件が複雑になる
- 今何やっているかわからなくなってくる
- この状態の処理どこだっけ??ってなる
このような課題が増えてくると思います。
そんなときに....
Stateパターンが助けてくれる!!
一言で表すと....
「状態を、クラスごとに役割を分けて処理を整理してくれる仕組み」 です!
補足:
状態はキャラだけじゃない!!
状態は、キャラクターの行動以外にも使用されます。
ボスのフェーズの変化
- 通常状態 ⇒ 怒り状態 ⇒ 最終形態
ゲーム進行
- タイトル → ロード中 → プレイ中 → ポーズ → リザルト
ストーリー演出
- 会話中 → 選択肢待ち → アニメ再生中 → 入力待ち
こんな感じにゲーム中にはいろんな状態が存在しその中で処理をします!
自分も、アドベンチャーゲームを作成した際には、イントロステート、会話ステート、電話番号を入力するステート、エンディングステートの状態でゲームつくりました。
Stateパターンのメリット
- 各状態を独立したクラスで定義できる
- 状態が増えても管理しやすい
- 条件分岐を極力なくせる
- テスト・デバッグしやすい
- 「この状態では何をしてるか?」が一目でわかる(クラスで管理してるからね!)
具体的な使い方
今回は、ボスの状態フェーズを例に挙げてご紹介していきます!
・通常時
・怒り時
・最終形態
この3通りの状態を管理・切り替えをしていこうと思います。
ボスのHPの割合で状態がかわるようにしようと思います。
使用するスクリプト
・BossController.cs :状態を管理してくれるステートマシーンというもの
・IBossState:状態インターフェース、各Stateに同じ振る舞いをさせる。使用するメソッドを統一
・NormalState.cs
・EnragedState.cs
・FinalState.cs
・Boss.cs:ボスの管理(ステートやHPなど)
public interface IBossState
{
void Enter(Boss boss); // 状態に入ったとき 現ボスの状態を入れる
void Update(); // 毎フレームの処理
void Exit(); // 状態から抜けるとき
}
public class BossStateController
{
private IBossState _currentState;//現状のボス状態を保持
private Boss _boss;
public BossStateController(Boss boss)
{
_boss = boss;
}
public void ChangeState(IBossState newState)
{
_currentState?.Exit();//抜け処理
_currentState = newState; //初期化処理
_currentState.Enter(_boss);//Bossインスタンスを渡すことで、状態内でHPや演出の切り替えなどを扱えるように
}
//毎フレーム
public void Update()
{
_currentState?.Update();
}
public IBossState CurrentState => _currentState;//読み取り専用
}
public class NormalState : IBossState
{
private Boss _boss;
public void Enter(Boss boss)
{
_boss = boss;
Debug.Log("通常状態に入った!");
// 通常アニメ再生など
}
public void Update()
{
// 通常行動など
}
public void Exit()
{
Debug.Log("通常状態を抜ける");
}
}
public class EnragedState : IBossState
{
private Boss _boss;
public void Enter(Boss boss)
{
_boss = boss;
Debug.Log("怒り状態に入った!");
// パワーアップ演出など
}
public void Update()
{
// 怒り時の攻撃など
}
public void Exit()
{
Debug.Log("怒り状態を抜ける");
}
}
public class FinalFormState : IBossState
{
private Boss _boss;
public void Enter(Boss boss)
{
_boss = boss;
Debug.Log("最終形態に入った!");
// 派手な演出
}
public void Update()
{
// 強力な攻撃など
}
public void Exit()
{
Debug.Log("最終形態を抜ける");
}
}
using UnityEngine;
public class Boss : MonoBehaviour
{
public float MaxHP = 100f;
public float CurrentHP;
private BossStateController _stateController;
void Start()
{
CurrentHP = MaxHP;
_stateController = new BossStateController(this);
_stateController.ChangeState(new NormalState());
}
void Update()
{
_stateController.Update();
// 状態遷移トリガー(HPの割合で切替)
if (CurrentHP <= MaxHP * 0.3f && !(_stateController.CurrentState is FinalFormState))
{
_stateController.ChangeState(new FinalFormState());
}
else if (CurrentHP <= MaxHP * 0.7f && !(_stateController.CurrentState is EnragedState))
{
_stateController.ChangeState(new EnragedState());
}
}
// ダメージを受ける関数
public void TakeDamage(float amount)
{
CurrentHP -= amount;
CurrentHP = Mathf.Max(CurrentHP, 0);
Debug.Log($"Boss HP: {CurrentHP}");
}
}
こんな感じでボスの状態を管理できます!
攻撃用のスクリプトも一応置いておきます!
クリックすると、ボスにダメージを与えるような処理です
public class AttackTest : MonoBehaviour
{
public Boss boss;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
boss.TakeDamage(10f);
}
}
}
出力結果
通常状態に入った!
Boss HP: 90
Boss HP: 80
Boss HP: 70
怒り状態に入った!
Boss HP: 60
Boss HP: 30
最終形態に入った!
まとめ
Stateパターンって?
if文やSwitch文を使用せずに状態管理をしてくれるやつ!!
メリットは?
ネストが減らせて見やすくなる!状態を追加するときや変更するときにクラスが分かれているから見やすい!
今回は、Stateパターンについて紹介しました。ふんわりと理解いただけましたでしょうか??少しでもStateパターンってこんなものかとイメージしていただけると幸です。
これからもちょっとした気づきやゲーム制作で使用したツールや技術を共有できればと思います!!!