Keep your place in this quest

Log in or sign up for free to subscribe, follow lesson progress, and access more learning content.

ゲームプレイが進むにつれて、現在の状態によって異なる動作をするオブジェクトが必要になることがあります。敵は、待機中、巡回中、追跡中、攻撃中、気絶中、あるいは死亡している状態が考えられます。ドアは閉じている、開いている途中、開いている、またはロックされている状態があります。ボス戦は複数のフェーズを通過することがあります。

ここで役立つのがステートマシン(State Machines)です。

image.png

上の画像は、新しいプロジェクトでCaveが作成するデフォルトの敵キャラクターと、その右側に表示されたステートマシンの画面です。現在「wander(徘徊)」状態の中にいて、この状態には「random selector(ランダムセレクター)」という内部状態(初期状態で緑色で表示)、「go to random(ランダム移動)」、そして「idle(待機)」といった状態があります。矢印で示された状態間の遷移が見え、それぞれの状態はPythonコード(画像の例)またはLogic Bricksで独自のロジックを持つことができます。

ステートマシンは、大きな一つのスクリプトに無関係な条件を詰め込むのではなく、明確な状態に行動を整理するのに役立ちます。

このレッスンでは、以下を学びます:

  • ステートとは何か。
  • 遷移とは何か。
  • 階層型ステートマシンがなぜ役立つのか。
  • Caveのステートマシンコンポーネントがエンティティにどのように組み込まれるか。
  • On EnterOn UpdateOn Exitがどのように機能するか。
  • ステートや遷移内でPythonとLogic Bricksがどのように使われるか。

まずはメンタルモデルを理解することが目標です。それが理解できれば、エディターの操作がずっと楽になります。

ステートとは?

ステートとは、オブジェクトが現在何をしているかを表します。

image.png

例えば、敵は以下のような状態を持つことがあります:

  • Idle
  • Patrol
  • Chase
  • Attack
  • Dead

通常これらの状態は同時に一つだけが敵を制御します。もし敵がPatrolなら、攻撃行動は実行しません。Dead状態なら追跡も行いません。

これにより、各ステートが明確な役割を持つため、ゲームプレイの理解が簡単になります。

ステート 役割例
Idle じっと待機する
Patrol ポイント間を移動する
Chase プレイヤーに向かって移動する
Attack 攻撃アニメーションを再生してダメージを与える
Dead 移動を止めて死亡アニメーションを再生する

「今フレームで敵は何をすべきか?」を一つの巨大なスクリプトで問うのではなく、「敵は現在どのステートにあるか?」を問う形になっています。

遷移とは?

遷移とは、ステートマシンをある状態から別の状態へ切り替えるルールのことです。

image.png

例えば:

  • 敵が動き始めるとき、IdleからPatrol
  • プレイヤーを検知したらPatrolからChase
  • 敵が十分近づいたらChaseからAttack
  • 攻撃が終わったらAttackからChase
  • 体力がゼロになったらどの状態からでもDead

遷移は行動そのものではなく、「さあ状態を変えよう」という判断を下します。

この区分けは重要で、Attack状態は攻撃に集中し、遷移はどのタイミングでその状態に入るか/出るかを決定します。

階層型ステートマシンが役立つ理由

Caveでは階層型ステートマシンを採用しています。これはステートの中にさらに別のステートを階層的に組み込める仕組みです。多くの行動には共通の親となる概念があるため便利です。

例えば、敵の状態を次のように整理できます:

Alive
    Idle
    Patrol
    Chase
    Attack
Dead

IdlePatrolChaseAttackはすべて「Alive」という大きなステートの中に属します。この構造により分かりやすくなり、複数個所にまたがって同じロジックを繰り返すのを避けられます。

初心者のプロジェクトでは単純なフラットなステートマシンで始められますが、ゲームプレイが複雑になると階層はロジックを整理するのに役立ちます。

ステートマシンコンポーネント

ステートマシンをシーンで使うには、エンティティにState Machine Componentが必要です。

コンポーネントはエンティティとステートマシンアセットを結びつけます。アセットは状態、遷移、ロジックを定義し、コンポーネントがゲームプレイ中にそのロジックを実行します。

これは他のCaveのシステムと似ています:

アセット コンポーネント
Python Script Python Componentが実行
アニメーション Animation Componentが再生
ステートマシン State Machine Componentが実行

実際には、敵のエンティティにState Machine Componentを付けて、敵AIのステートマシンを動かすことができます。

ステートマシンアセットにはプロパティがあり、プロパティタブで確認できます。コンポーネント側でそれらのプロパティを局所的に変更できるため、異なる種類のエンティティで異なる設定を持つモジュラーなステートマシンが作成可能です。

ステートイベント

各ステートは以下の3つの重要なタイミングで動作できます:

イベント 実行タイミング 主な用途
On Enter ステート開始時 タイマーリセット、変数初期化、アニメ再生、ターゲット選択
On Update ステートが有効な間 移動、距離チェック、タイマー更新
On Exit ステート終了時 エフェクト停止、一時値クリア

例えばAttackステートはこう動きます:

  • On Enter: 攻撃アニメを開始
  • On Update: ヒットの瞬間やアニメ完了を待つ
  • On Exit: 攻撃フラグをクリア

こうして各ステートは独立した小さな行動ユニットのように見えます。

ステート内のPython

ステートマシンのロジックにはPythonが使えます。

各ステートのPythonコード内では、entitysceneなど便利な変数が用意されており、そのステートマシンコンポーネントを持つエンティティを制御できます。

例えば、ステートが開始した時に音を鳴らすコード:

cave.playSound("Enemy Alert", volume=0.8)

またエンティティのプロパティを読むこともできます:

health = entity.properties.get("health", 100)

これにより、ステートのロジックはその行動に密接に関連したコードになります。

遷移内のPython

遷移にもPythonが使えます。遷移Pythonコードではresult変数を使います。もしresultTrueなら遷移が実行されます。例えばDeadに遷移する条件は:

health = entity.properties.get("health", 100)
result = health <= 0

ChaseからAttackへの遷移で距離チェックが必要なら、敵スクリプトやプロパティでその位置データを取得する例:

player = scene.get("Player")
playerPos = player.getTransform().getWorldPosition()
entityPos = entity.getTransform().getWorldPosition()

distanceToPlayer = (playerPos - entityPos).length()
result = distanceToPlayer < 2.0

具体的なデータはゲームによりますが、アイディアは常に同じです:遷移がどのタイミングで状態を切り替えるかを決定します。

ヒント: 遷移中は、遷移元のステートで定義されたローカル変数がすべて利用可能です。先ほどの距離計算例では、ステートのOn Enterでプレイヤーとエンティティの変換情報をキャッシュしておくことで、毎フレームそのデータ取得のコストを減らすことができます。

ステートマシン内のLogic Bricks

ステートマシンではLogic Bricksも使えます。

Pythonコードを書く代わりに視覚的に動作を作りたい場合に便利です。多くのゲームプレイタスクで、Logic Bricksはイベント・条件・アクションをつなぐ親しみやすい方法です。

使い方は、ステート内や遷移のPythonコードと同じです。ステートのLogic Bricksと遷移のLogic Bricksは、すぐに使い始められるよう初期化されています。

PythonとLogic Bricksは、動作のわかりやすさに応じて混在して使えます。ステートまたは遷移が両方を持つ場合、両方が実行され、どちらかが成立すると遷移します。

ステートマシンを使うタイミング

オブジェクトの行動が明確に複数のモードに分かれているときにステートマシンを使いましょう。

代表例は以下の通りです:

  • 敵のAI
  • ボスのフェーズ
  • ドアやインタラクト可能なオブジェクト
  • ミッションやクエストの進行
  • カットシーンの進行
  • キャラクターの能力状態

ワンアクションだけの小さなオブジェクトでは、例えばピックアップが音を鳴らして消えるだけなら、小さなPythonスクリプトやLogic Brickのセットアップで十分です。

覚えておくべきこと

ステートマシンはゲームプレイを状態と遷移に整理します。

状態は現在の行動を示し、遷移はいつ別の状態に切り替えるか決めます。On EnterOn UpdateOn Exitは各状態の集中と読みやすさを助けます。

単純な行動ならスクリプトだけで足りますが、複数のモードを持つ行動ならステートマシンでロジックを理解しやすく、拡張もしやすくなります。