Skip to main content

02.Stateパターン・Behavior Tree

3.Stateパターンで状態ごとの処理を分けよう

Stateパターンの板書

FSMを switch だけで作ると、状態が増えるほど1つのクラスに処理が集まりやすくなります。Stateパターンでは、状態ごとの処理を別クラスに分けることで整理します。

Q15.Stateパターンにすると何が変わる?

敵AIの状態が増え、Enemy::Update() の switch が長くなってきました。Stateパターンへ変更する考え方として最も適切なものを選んでください。

  • A.各状態の処理を IdleState、ChaseState などのクラスへ分け、Enemyは現在のStateを保持する
  • B.switch の中へさらに大きな if を追加して、すべてEnemyクラスへ集める
  • C.状態名だけ別クラスにし、処理は巨大な switch に残す
  • D.すべてのStateを同時実行して結果を平均する
答え

A

Stateパターンの狙いは、状態ごとの責任をクラスへ分けることです。

Q16.共通の Update() を用意する意味は?

IdleState、ChaseState、AttackState は、それぞれ処理内容が違います。それでも基底クラス StateBase に共通の Update() を用意する理由として最も適切なものを選んでください。

  • A.Enemy側が具体的なState名を知らなくても、現在状態に対して同じ形で Update() を呼べるようにするため
  • B.すべてのStateで同じ処理を強制し、状態ごとの差をなくすため
  • C.Stateを切り替えるたびにEnemyクラスを書き直すため
  • D.派生クラスから Update() を実装できなくするため
答え

A

State側に共通の入口を用意すると、Enemyは「今どの具体的Stateか」を意識せず更新できます。

Q17.virtual Update() は何のため?

class StateBase
{
public:
    virtual void Update(Enemy& enemy) = 0;
};

この virtual Update() の目的として正しいものを選んでください。

  • A.派生した状態ごとに異なる Update() を呼び分けるため
  • B.必ず同じ Update() だけを呼ぶため
  • C.メモリを0バイトにするため
  • D.状態を数値へ変換するため
答え

A

Q18.基底クラスポインタで現在Stateを持つ理由は?

Enemyが StateBase* currentState を持つ設計を考えます。この形にする利点として最も適切なものを選んでください。

  • A.IdleState、ChaseState、AttackState など異なるStateを、同じ変数へ差し替えて保持できる
  • B.派生Stateの処理を使わず、必ず StateBase の処理だけを実行できる
  • C.状態遷移のたびにEnemy本体の型を変更できる
  • D.同時にすべてのStateを実行できる
答え

A

共通の型で保持し、実際の振る舞いは派生Stateごとに変えるための形です。

Q19.ポリモーフィズムで「同じ呼び方・違う動作」を確認しよう

currentState が StateBase* 型でも、実際には ChaseState を指しているとします。

currentState->Update(enemy);

Update() が virtual で、ChaseState 側でオーバーライドされている場合の説明として正しいものを選んでください。

  • A.呼び出し方は共通だが、実際には ChaseState::Update() が実行される
  • B.ポインタの型が StateBase* なので、必ず基底クラスの処理しか実行できない
  • C.ChaseState を指しているとコンパイルできない
  • D.全Stateの Update() が順番に呼ばれる
答え

A

これがStateパターンで利用しているポリモーフィズムです。

Q20.Stateパターンで変更しやすくなる部分を考えよう

既存の Idle、Chase、Attack に加えて、新しく「ひるみ状態 Stunned」を追加します。

Stateパターンで整理されている場合の変更として自然なものを選んでください。

  • A.StunnedState を新しく作り、その状態固有の処理と必要な遷移を追加する
  • B.既存のStateクラスをすべて削除し、Enemyの1つの巨大な switch に戻す
  • C.新しいStateを追加するたびに、すべてのStateの Update() を同じ内容にする
  • D.Stateを増やすとFSMの遷移は使えなくなる
答え

A

状態ごとの処理が分離されているため、追加する責任を新しいStateへまとめやすいことが利点です。

Q21.new したStateを差し替えるときの注意点

Stateオブジェクトを new で生成し、遷移のたびに別のStateへ差し替える実装を考えます。古いStateを破棄せず、新しいStateを作り続けると何が起こりますか。

  • A.不要なメモリが解放されず、メモリリークにつながる
  • B.ポリモーフィズムが自動的に無効になる
  • C.すべてのStateがRootになる
  • D.Update() が必ず2回呼ばれる
答え

A

Stateパターンの補足で扱ったC++では、ポインタで扱うオブジェクトの寿命管理も重要です。


4.Behavior Treeの考え方を押さえよう

Behavior Tree導入の板書

Behavior Treeは、AIの判断や行動を木構造として組み立てる方法です。ここでは実装よりも、ノードの役割と処理の流れを押さえます。

Q22.Behavior Treeの構成要素を対応させよう

ノード 役割
Root ______
Selector ______
Sequence ______
Action ______

選択肢:

  • ツリーの開始点
  • 子を順に試し、どれかが成功すれば成功
  • 子を順に実行し、すべて成功すれば成功
  • 実際の移動・攻撃・待機などを行う
答え
  • Root → ツリーの開始点
  • Selector → 子を順に試し、どれかが成功すれば成功
  • Sequence → 子を順に実行し、すべて成功すれば成功
  • Action → 実際の移動・攻撃・待機などを行う

Behavior Tree詳細の板書

Q23.ノードの結果を穴埋めしよう

Behavior Treeのノードが返す代表的な3つの結果を穴埋めしてください。

  • 処理に成功した → ______
  • 処理に失敗した → ______
  • まだ処理中である → ______

選択肢:Success・Failure・Running

答え
  • 成功 → Success
  • 失敗 → Failure
  • 処理中 → Running

Q24.Sequenceの動きを選ぼう

Sequenceの説明として正しいものを選んでください。

  • A.子を順番に実行し、途中で失敗したらそこで失敗する
  • B.子のどれか1つが成功すれば必ず成功する
  • C.子をランダムに1つだけ実行する
  • D.必ずすべての子を同時実行する
答え

A

すべての子が成功したとき、Sequence全体が成功します。

Q25.Selectorの動きを選ぼう

Selectorの説明として正しいものを選んでください。

  • A.子を順番に試し、成功したものがあればそこで成功する
  • B.すべての子が成功しないと成功しない
  • C.必ず最初の子だけを実行する
  • D.状態を数値に変換する
答え

A

Q26.FSMとBehavior Treeを使い分けよう

次のAI設計に、FSMとBehavior Treeのどちらがより自然か対応させてください。

AIの設計 向いている仕組み
Idle、Chase、Attack のように現在状態を明確に切り替えたい ______
「攻撃できるなら攻撃、無理なら追跡、それも無理なら巡回」のように優先順の木で判断したい ______

選択肢:FSM・Behavior Tree

答え
  • 明確な状態と遷移 → FSM
  • 優先順や行動の組み合わせを木で表現 → Behavior Tree

どちらが常に優れているのではなく、整理したい問題の形に合わせて使い分けます。