NPCの行動 · 有限状態マシンからLLMまで

NPCの行動 — ステートマシン、ビヘイビアツリー、GOAP、AI駆動型NPC

自分だけのNPCの行動を作る

NPCはどうやって次に何をするか決めるのか。ステートマシン、ビヘイビアツリー、GOAP、ユーティリティAI、LLM駆動のNPC — ブラウザで動く3Dの実例とともに解説します。

✦ゲーム
作る:NPCの行動

NPCの行動とは、モデル、体力バー、衝突カプセルを、何かを欲しがっているように見えるものへと変える論理です。それは通常、毎フレームまたは一定のティックで、ひとつの問いに繰り返し答えます。世界について知っていることを踏まえて、次に何をすべきか。このページのそれ以外のもの — 有限状態マシン、ビヘイビアツリー、GOAP、ユーティリティAI、LLM駆動の意図 — は、その問いへの異なる答えであり、それぞれコストと失敗の仕方が異なります。

私たちは、Neta Studioの3Dゲームビルダーの中で、ブラウザのタブ上で動くNPCを作っています。その制約が以下の助言の形を決めています。専用サーバーはなく、別スレッドの物理演算もなく、毎ティック40ミリ秒も探索するプランナーのための予算もありません。ミドルレンジのノートパソコンで、描画の隣で動かせるほど安くできない技術は、格下げするか、処理の速い経路から外します。以下の各セクションでは、私たちが実際に出荷しているものと、それぞれの手法がどこで破綻するかを説明します。

手短な答え:NPCはどう決めるか

NPCは、決められた周期で判断関数を走らせ、世界の状態のごく一部(対象までの距離、視線、現在の体力、最後に確認した位置)を読み取り、移動系とアニメーション系が実行できる行動を返すことで決定します。周期は論理と同じくらい重要です。100ミリ秒から250ミリ秒のティックで判断し、移動と衝突を毎フレーム行えば、毎フレーム判断する論理の何分の一ものコストで済み、ほとんどの場合プレイヤーには違いが分かりません。

4つの古典的なモデルは、複雑さがどこに宿るかが異なります。ステートマシンは、判断を名前付きの状態間の遷移として符号化します。ビヘイビアツリーは、判断をセレクターノードとシーケンスノードの階層として符号化します。GOAPは、目標と、事前条件と効果を持つ行動の集合として符号化し、計画を探索します。ユーティリティAIは、すべての候補行動に対する採点関数として符号化します。LLM駆動のNPCは、高レベルの意図を自然言語で符号化し、その結果をより安価なモデルの一つへ引き渡します。

実際にはこれらを組み合わせます。ユーティリティの採点器が目標を選び、ビヘイビアツリーかステートマシンがそれを実行し、ナビメッシュが体を動かし、言語モデルは対話と意図にしか触れません。このページの残りでは、各層を順に取り上げ、あなたが遭遇する具体的な失敗の仕方に名前を付けていきます。

ステートマシン(有限状態マシン)

有限状態マシンは最も安価で役立つモデルであり、ほとんどのNPCにとって最初の正しい選択です。状態の集合 — 待機、巡回、追跡、攻撃、逃走、気絶 — と、それらの間の遷移条件の集合を定義します。ティックごとに現在の状態からの遷移を評価し、発火すれば切り替えます。コストは確認する遷移の数に比例し、それは小さなものです。状態はひとつの変数なので、シリアライズ、デバッグ、エディタのオーバーレイへの表示がごく簡単です。

失敗の仕方は振動です。プレイヤーから5.1メートルの位置にいるNPCが巡回から追跡へ遷移し、4.9メートルまで移動すると、対称的な遷移がそれを巡回へ戻します。境界で永遠に震え続けます。修正はヒステリシスです。5.0メートルで追跡に入り、6.5メートルで抜けます。同じ問題は、プレイヤーが柱の陰で横に動くとちらつく視線判定にも現れ、同じ修正が当てはまります。条件がNティック連続で成立することを要求するか、最後に視認してから2秒間NPCを追跡に留める記憶タイマーを加えます。

2つ目の失敗の仕方は遷移の絡まりです。5つの状態には10の遷移が必要で、10の状態には45近く必要になります。新しい状態はそれぞれ、そこへ到達しうるあらゆる状態と結線しなければならず、条件は筋道立てて考えにくい仕方で重なります。遷移表が1画面に収まらなくなったら、それがビヘイビアツリーへ移る合図です。

巡回する衛兵の具体的なFSM:待機(2秒待つ)→ 巡回(次の経路点へ歩く)→ 警戒(物音の方向へ向く、1.5秒)→ 追跡(最後に確認した位置へ走って移動)→ 攻撃(2メートル以内でクールダウンが切れている場合)→ 捜索(最後に確認した位置の周辺を8秒さまよう)→ 巡回。7つの状態、およそ12の遷移。そして捜索の記憶タイマーが、これをぎくしゃくではなく意図的に感じさせます。

  • 単一変数の状態現在の状態はひとつの列挙値です。デバッグはひとつの値を読むことであり、スタックから意図を再構成することではありません。
  • あらゆるしきい値にヒステリシス両方向に遷移しうる距離やタイマーには、入り口と出口で異なる値が必要です。さもなければNPCは境界で振動します。
  • 遷移の予算表が維持できなくなる前に、状態はおよそ10個未満に抑えましょう。それを超えたら、ビヘイビアツリーに切り替えるか、サブマシンに分割します。

ビヘイビアツリー

ビヘイビアツリーは、遷移表を階層に置き換えます。葉ノードは行動(移動、アニメーション再生、待機)と条件(プレイヤーが見える、体力がしきい値未満)です。2種類の複合ノードがほとんどの仕事をします。セレクターは子を順に試し、どれかが成功した時点で成功を返します — これが優先度リストです。シーケンスは子を順に実行し、どれかが失敗した時点で失敗します — これが手順書です。

規模への強みは組み合わせやすさです。新しい振る舞いを加えることは、セレクターの下にサブツリーを加えることであり、新しい状態を到達しうるあらゆる状態に結線することではありません。優先度は子の順序に明示され、重なり合うブール条件の集合よりはるかに読みやすいものです。サブツリーは再利用できます。同じ「物音を調べる」枝が、衛兵にも犬にも民間人にも使えます。

ツリーは自動的に速くなるわけではありません。素朴な実装はティックのたびにツリー全体を再評価します。だからこそ、判断は100〜250ミリ秒の周期で回し続け、現在実行中の行動に毎フレームの移動を任せるのです。長時間動く行動には、ツリーが覚えておく実行中状態が必要です。さもなければ行動はティックごとに最初からやり直され、NPCは経路の先頭へ滑り戻ります。ビヘイビアツリーを提供するほとんどのエンジン — UnityのBehaviorパッケージ、Unrealの組み込みツリー、あるいはTypeScriptで書いた小さな自作のもの — は、実行中という戻り値でこれを扱いますが、NPCが途切れるときに最初に確認すべき点はそこです。

戦闘NPCのセレクター構造を、優先度順に示します。(1) 体力が25パーセント未満なら、シーケンス[遮蔽物へ逃走、回復]。(2) プレイヤーが見えて2メートル以内なら、シーケンス[プレイヤーの方を向く、攻撃]。(3) プレイヤーが見えるなら、シーケンス[回り込み位置へ移動、攻撃]。(4) 最後に確認した位置が設定されているなら、捜索。(5) 巡回。セレクターは条件が通った最初の枝を選びます。これらの子の並べ替えが、コードに触れずに攻撃性を調整する方法です。

  • セレクターノード成功した最初の子で成功を返します。フォールバックの優先度に使います。最も限定的な振る舞いを試し、次に、そして既定の振る舞いへ。
  • シーケンスノード失敗した最初の子で失敗します。手順1が起きなければ手順2が無意味な、順序立った手続きに使います。
  • 実行中状態長い行動は実行中を返し、止まったところから再開しなければなりません。すぐに成功を返すと、ツリーはティックごとにそれをやり直し、移動が壊れます。
  • ブラックボードツリーが読み書きする事実のための共有キー値ストア — 対象位置、警戒度、クールダウン終了時刻。小さく、型を付けて保ちます。

GOAP(ゴール指向型アクションプランニング)

GOAPはモデルを反転させます。いつ遷移するかを記述する代わりに、行動が何をし、何を必要とするかを記述し、プランナーが目標に到達する順序を探索します。各行動は事前条件、効果、コストを持ちます。「攻撃」は武器を持つことと射程内を必要とし、対象を死亡させる。「武器を取る」は武器が利用可能であることを必要とし、武器を持つことを設定する。「対象へ移動」は対象を把握していることを必要とし、射程内を設定する。プランナーは目標から始めて逆向きに進み、その効果が目標を満たす最も安価な行動の連鎖を見つけます。

これは、世界の状態が豊かで、正しい順序が自明でないときに報われます。視線を切り、装填し、位置を変え、それから交戦する必要があるNPCは、設計者が明示的に書かなかった計画を生み出します。プランナーが事前条件のグラフから順序を発見したからです。また制作面でも報われます。新しいアイテムや能力を加えることは、ひとつの行動定義を加えることであり、それについて知るべきあらゆるステートマシンを編集することではありません。

コストは探索です。素朴なプランナーは計画を見つけるまで行動の組み合わせを探索し、行動と状態変数を加えるにつれて爆発しえます。標準的な緩和策はヒューリスティック — 目標までの距離推定を伴う状態グラフ上のA* — に厳格な展開上限を加えたものです。私たち自身のブラウザ環境では、計画は判断ティックで計算され、数百ノードの展開を上限とし、それを超えるものはキャッシュされた計画か単純なビヘイビアツリーの枝へフォールバックします。正直な限界はこれです。GOAPは、意味のある行動順序の数が多いときは探索コストに見合い、NPCがたった3つのことしかしないなら無駄なオーバーヘッドです。

  • 事前条件と効果各行動は、実行するために真でなければならないことと、それが変えるものを宣言します。プランナーはコードではなく、それらの宣言の上で推論します。
  • 目標からの後向き探索望む状態から始め、それを生み出す効果を持つ行動を見つけ、その事前条件に対して再帰します。通常は前向き探索より安価です。
  • 展開の上限計画の呼び出しごとにノードの展開を上限で抑えます。上限を超えたらフォールバックします — プランナーにフレームを停滞させてはいけません。
  • 計画のキャッシュ世界の状態が意味を持って変化したときだけ再計画します。静的な状況は前の計画をゼロコストで再利用します。

ユーティリティAI

ユーティリティAIはすべての選択肢を採点し、最も高いものを選びます。各候補行動は、世界の状態を数値へ写す関数を持ち、通常は0から1に正規化されます。「逃走」は体力が低く敵が近いときに高得点になります。「攻撃」は対象が射程内で弾薬が0より多いときに高得点になります。「ぶらつく」は他に差し迫ったものがなければ中程度の得点になります。NPCは最大値を取り、任意で小さなランダムな揺らぎを加えて、同点が予測不能に解決されるようにします。

これは最も意外な見た目のキャラクターを生み出すモデルです。振る舞いが、明示的な規則からではなく、曲線の相互作用から創発するからです。採点の重みがわずかに異なる2体のNPCは、分岐コードを一切持たずに目に見えて違う振る舞いをし、性格の変化に自然に合います。また、実行時に調整するのが最も簡単なモデルでもあります。デバッグパネルに重みを表示し、ゲームの実行中にドラッグするだけです。

失敗の仕方は、平坦な、あるいはノイズの多い効用です。すべての行動が0.4を採点すると、NPCは優柔不断に見え、絶えず切り替わります。最高得点が2つの近い値の間でティックごとにひっくり返ると、悪いステートマシンと同じ振動が起きます。標準的な修正はコミットメント期間です。ひとつの行動が選ばれたら、一定時間だけ最低限コミットします — 500ミリ秒が妥当な既定値です — ただし最高得点が、コミットした行動の得点を意味のある差で超えた場合を除きます。曲線も重要です。体力に対する線形の応答曲線は、毎回の戦いでまったく同じ瞬間に逃げるNPCを生みます。指数関数的あるいはロジスティックな曲線は、状況に応じて動くしきい値を生みます。

  • 行動ごとの採点関数各行動は世界の状態を0から1の効用へ写します。ひとつを再調整しても他に触れずに済むよう、独立に保ちます。
  • コミットメント期間ひとたび選んだら、最低時間だけ行動を保持します。ほぼ等しい得点の間でティックごとにひっくり返るのを防ぎます。
  • 調整可能な重み重みを実行中に公開します。重みだけが異なる2体のNPCは別のキャラクターとして読め、これは利用できる最も安価な性格システムです。
  • 応答曲線線形の曲線は固定のしきい値を生みます。ロジスティックな曲線は、魔法の数値ではなく状況に応じてNPCを反応させます。

ナビゲーションと衝突

判断の論理が目的地を選びます。ナビゲーションが体をそこへ運びます。ナビメッシュは三角形分割された歩行可能な面です。経路探索はそのポリゴングラフ上でA*を走らせ、結果はNPCがたどるポリゴン辺のリストになります。私たちはレベル形状から読み込み時にナビメッシュを構築してキャッシュします。毎フレーム再構築することは、ブラウザゲームでフレームレートを落とす最も簡単な方法だからです。

ステアリングは最後の数メートルと、刻々の移動を担います。シークとアライブが前方向のベクトルを生み、分離がNPCを近くのエージェントから遠ざけ、障害物回避が、扉や別のキャラクターのような動的な妨害物の周りへ経路を曲げます。ステアリングは毎フレーム走らせ、経路探索は目的地が変わったか経路が無効になったときだけ走らせます。すべてのNPCに対して毎フレーム経路を再計算することは、よくある、避けられるコストです。

衝突は、NPCが壁を通り抜けるのを止める処理です。ナビメッシュは経路を歩行可能な地面に保ちますが、ステアリングと物理は依然としてキャラクターを形状の中へ押し込みうるので、キャラクターコントローラーは移動の後でコライダーに対するめり込みを解消します。私たちのアイソメトリックの世界 — Mini WorldとThe Cyclops' Island — では、ここが最も目立つNPCの不具合の出所です。キャラクターの半径より狭い隙間を通そうとする経路や、角でエージェントを壁へ押し込むステアリングのベクトルです。ナビメッシュ上でエージェント半径を正しく設定し、分離の力をコントローラーの押し出し強度より下に保つことが、その大半を解消します。

1

01

静的な形状から読み込み時にナビメッシュを焼き込み、キャッシュします。毎フレーム再構築しないこと。

2

02

エージェントの半径と高さは、モデルの境界ボックスではなく、キャラクターの実際の衝突カプセルに合わせて設定します。

3

03

経路探索は、目的地が変わったとき、経路が塞がれたとき、あるいはNPCがしきい値を超えて経路から外れたときだけ走らせます。

4

04

ステアリング、分離、障害物回避は、分離の力に上限を設けて毎フレーム走らせます。

5

05

移動の後でコライダーに対する衝突を解消し、それからキャラクターをナビメッシュ上へスナップし直します。

6

06

経路とナビメッシュをデバッグのオーバーレイに記録し、可視化します — 不具合は通常、論理的ではなく空間的です。

LLM駆動のNPC

言語モデルは、ナビゲーションの層ではなく、意図と対話の層で状況を変えます。経路探索、ステアリング、衝突はまったくそのままです。応答に数百ミリ秒かかるモデルは、毎フレーム動く必要のある体を駆動できないからです。モデルにできるのは、プレイヤーの言葉を踏まえてどの目標が筋が通るかを決めること、キャラクターに即した応答文を生成すること、状況が曖昧で手書きの規則が平坦なものしか生まないときに高レベルの選択肢の間で選ぶことです。

実用的なアーキテクチャは2つの速度を分離します。即時の反応はステートマシンかユーティリティの採点器で走ります — 攻撃、逃走、遮蔽 — 1、2フレーム以内に起きる必要があるからです。熟慮的な意図は、はるかに遅い周期でモデルを通り、たぶんやり取りごとに一度か数秒ごとで、その出力は高速な層が実行する構造化された命令です。目標を追従に設定する、プレイヤーへの態度を敵対的に設定する、対話の行を設定する。モデルは位置に直接触れることはありません。

正直な限界は、遅延、コスト、決定性です。応答時間は十分に長いので、NPCは入力を即座に、しぐさや短い定型の台詞で受け止めなければならず、さもなければ壊れているように見えます。コストは呼び出す頻度に比例するので、まとめて呼び、キャッシュします。そして出力は非決定的なので、ゲームに届く前にスキーマに対して検証しなければなりません — 解析に失敗した自由文の応答は、対話システムを落とすのではなく、既定の台詞へフォールバックすべきです。モデルを、型付きインターフェースの背後にある信頼できないサービスとして扱えば、統合は持ちこたえます。

  • 型付きの命令出力モデルは、ゲームコードが解析する散文ではなく、構造化された意図を返します。スキーマに対して検証し、不正なものはすべて拒否します。
  • 2速度のループ反応には速いローカル論理、熟慮には遅いモデル呼び出し。ネットワークの往復を移動の経路に載せてはいけません。
  • 即時の受け止め入力が届いたらすぐに、しぐさや短い定型の台詞を再生し、モデルが応答したらそれを差し替えます。遅延を隠します。
  • 穏当なフォールバック呼び出しが失敗したかタイムアウトしたら、ステートマシンの既定の応答へフォールバックします。NPCがモデルを待って停滞してはいけません。
Ashenmere — 決闘しながら対戦相手の判断を読み取ります。
The Cyclops' Island — アイソメトリックのグリッド上でのナビゲーションと衝突。

それが属する世界へ足を踏み入れよう

スプライトは、立つ場所を持たないキャラクターです。最後の一歩は、他にはないものです。作ったばかりのスプライトシートを、誰かが歩き回れる世界に置く — 小さな島、惑星、雨の街角。このサイトのすべての世界は、ひとつの文章から作られ、遊べるページとして公開されています。

3Dゲームビルダーに戻る

完成した31の3Dワールド、ゲーム、シーンがすべて1つのページに — そのすべてが遊べます。

プラットフォーム: Neta Studioページ: NPCの行動 — ステートマシン、ビヘイビアツリー、GOAP、AI駆動型NPC次に読む: /explore/guide/what-is-a-non-player-character · /explore/guide/what-are-npcs · /explore/guide/ai-npc
×