Keep your place in this quest

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

ゲームプレイが成長するにつれて、現在行っていることによって異なる動作をするオブジェクトが必要になることがあります。敵は待機、巡回、追跡、攻撃、スタン、または死んでいる状態にあることがあります。ドアは閉じた状態、開きつつある状態、開いている状態、またはロックされた状態になることがあります。ボス戦は複数のフェーズを経ることができます。

ここで便利になるのがステートマシンです。

image.png

上の画像では、新しいプロジェクト用にCraveが作成したデフォルトの敵の表示と、その右側に開いているステートマシンを見ることができます。私たちは "wander" 状態の中におり、"random selector"(初期状態、緑色で表されています)、"go to random"、"idle" といった内部状態を持っています。状態間の遷移は矢印で表されており、各状態にはそれぞれのロジックがあり、Pythonで示されているように、またはロジックブリックで表現されることがあります。

ステートマシンは、無関係な条件で満ちた大きなスクリプトを書くのではなく、明確な状態に振る舞いを整理するのに役立ちます。

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

  • ステートとは何か。
  • トランジションとは何か。
  • 階層型ステートマシンがなぜ有用なのか。
  • Caveのステートマシンコンポーネントがエンティティにどのようにフィットするのか。
  • On EnterOn Update、および On Exit がどのように機能するのか。
  • ステートやトランジション内でPythonとロジックブリックをどのように使用できるのか。

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

ステートとは何か?

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

image.png

例えば、敵は以下のようなステートを持つことがあります:

  • Idle
  • Patrol
  • Chase
  • Attack
  • Dead

これらのステートのうち、通常は一度に1つだけが敵を制御するべきです。敵が Patrol 状態のときは攻撃行動を実行してはいけません。 Dead 状態のときはプレイヤーを追いかけ続けるべきではありません。

これにより、各ステートは明確な役割を持つので、ゲームプレイを理解しやすくなります。

ステート 何をする可能性があるか
Idle じっと待つ。
Patrol ポイント間を移動する。
Chase プレイヤーに向かって移動する。
Attack 攻撃アニメーションを再生し、ダメージを与える。
Dead 移動を無効にし、死亡アニメーションを再生する。

「このフレームで敵は何をすべきか?」と尋ねるのではなく、「敵は現在どのステートにいるのか?」と尋ねることができます。

トランジションとは何か?

トランジションは、ステートマシンを1つのステートから別のステートに移動させるルールです。

image.png

例えば:

  • 敵が動き始めるとき、Idle から Patrol へ、タイマーの後。
  • プレイヤーが発見されたときに、Patrol から Chase へ。
  • 敵が近づいたときに、Chase から Attack へ。
  • 攻撃が終わったとき、Attack から Chase へ。
  • ヘルスがゼロに達したときに、どのステートからでも Dead へ。

トランジションは、その行動自体ではなく、「今、状態を変更すべきか?」と決定するものです。

その分離が重要です。Attack ステートは攻撃に集中することができ、トランジションはそのステートに入ったり出たりする時間を決定します。

階層型ステートマシンが助ける理由

Caveは階層型ステートマシンアプローチを使用します。これは、ステートが別のステートの内部に整理できることを意味します。それは、多くの動作が共通の親アイデアを共有するため、便利です。

例えば、敵は以下のように整理されることがあります:

Alive
    Idle
    Patrol
    Chase
    Attack
Dead

IdlePatrolChase、および Attack ステートはすべて広義の Alive ステートに属しています。これにより、構造が理解しやすくなり、同じロジックをいくつもの場所に繰り返さずに済みます。

初心者プロジェクトの場合、シンプルなフラットなステートマシンから始めることができます。しかし、ゲームプレイが複雑になるにつれて、階層がロジックをきれいに保つのに役立ちます。

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

シーンでステートマシンを使用するには、エンティティにステートマシンコンポーネントが必要です。

このコンポーネントは、エンティティをステートマシンアセットに接続します。アセットは、ステート、トランジション、ロジックを定義し、コンポーネントはゲームプレイ中にそのロジックをエンティティで実行します。

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

アセット コンポーネント
Pythonスクリプト Pythonコンポーネントがそれを実行します。
アニメーション アニメーションコンポーネントがそれを再生します。
ステートマシン ステートマシンコンポーネントがそれを実行します。

実際には、あなたの敵エンティティはステートマシンコンポーネントを持ち、そのコンポーネントが敵AIステートマシンを実行することができます。

ステートマシンアセットはプロパティを持つことができ、プロパティタブに移動することで見ることができ、それらのプロパティはステートマシンコンポーネント内でローカルに変更できるため、異なるタイプのエンティティに異なる設定を持つモジュラーなステートマシンを作成できます。

ステートイベント

各ステートは3つの重要な瞬間に反応できます:

イベント 実行されるタイミング 一般的な使用法
On Enter ステートが開始したとき。 タイマーをリセットし、変数を初期化し、アニメーションを再生し、ターゲットを選択します。
On Update ステートがアクティブな間。 移動し、距離をチェックし、タイマーを更新します。
On Exit ステートを離れるとき。 効果を停止し、一時的な値をクリアします。

例えば、Attack ステートは次のように機能するかもしれません:

  • On Enter: 攻撃アニメーションを再生します。
  • On Update: ヒットの瞬間やアニメーションコールバックを待ちます。
  • On Exit: 攻撃フラグをクリアします。

これにより、各ステートは小さな自己完結した動作のような感覚を持ちます。

ステート内のPython

ステートマシンはロジックにPythonを使用できます。

各ステートのPythonコード内では、Caveは entityscene などの便利な変数を提供しますので、ステートはステートマシンコンポーネントを所有するエンティティを制御できます。

例えば、あるステートが入るときにサウンドを再生することができます:

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

また、エンティティからプロパティを読み取ることもできます:

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

これによりステートロジックがそれに属する動作に近く保たれます。

トランジション内のPython

トランジションもPythonを使用できます。トランジションPythonロジックでは、Caveは 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 ステートでプレイヤーのトランスフォームとエンティティのトランスフォームを2つの変数に保存することができます。これにより、キャッシュされ、毎フレームこのデータを取得する必要がなくなります。

ステートマシン内のロジックブリック

ステートマシンはロジックブリックも使用できます。

これは、Pythonコードを書くのではなく、視覚的に動作を構築したいときに便利です。多くのゲームプレイタスクに対して、ロジックブリックはイベント、条件、およびアクションを接続する非常にアプローチしやすい方法です。

ロジックブリックは、ステートやトランジション内のPythonコードで説明したのとまったく同じように機能します。ステートのロジックブリックとトランジションのロジックブリックは、開始するために必要なブリックで初期化されています。

動作を構築する際に、Pythonとロジックブリックの両方を使用することができ、どちらの方が明確に感じられるかによって選択できます。もしステートやトランジションが両方とも持っている場合、両方が実行され、どちらかが発生すればトランジションが発生します。

ステートマシンを使用すべき時

オブジェクトに明確な動作モードがある場合は、ステートマシンを使用してください。

良い例には:

  • 敵AI。
  • ボスフェーズ。
  • ドアやインタラクティブオブジェクト。
  • ミッションまたはクエストの流れ。
  • カットシーンの進行。
  • キャラクターの能力ステート。

ほんの一つのアクションを持つ小さなオブジェクトには、ステートマシンは必要ない場合があります。ピックアップがサウンドを再生し、消えるだけの場合は、小さなPythonスクリプトやロジックブリックのセットアップで十分なことがあります。

覚えておくべきこと

ステートマシンは、ゲームプレイをステートとトランジションに整理します。

ステートは現在起こっていることを記述します。トランジションは別のものに切り替える時期を決定します。On EnterOn Update、および On Exit は、各ステートを集中させて可読性を保つのに役立ちます。

簡単な動作に対しては、スクリプトで十分かもしれません。しかし複数のモードを持つ動作に対しては、ステートマシンがロジックを理解しやすくし、拡張しやすくすることが一般的です。