scenario-godot-gameplay について
Godot 4.7.2でナビゲーション、AI、戦闘、会話、プロシージャルコンテンツを扱う開発者向けのゲームプレイシステムスキルです。シーンに依存しない判断ロジック、データ駆動のルール、測定可能なテストとベンチマークを重視します。
主な機能
- 01ナビメッシュの構築と同期
- 02規模に応じた AI 方式の選択
- 03衝突、ヒットボックス、弾の設計
- 04セーブ検証とバージョン移行
- 05会話、クエスト、生成コンテンツのワークフロー
活用シーン
- 01敵集団のフレームコスト測定
- 02NavigationAgent3D の移動検証
- 03ヒット判定と高速弾のテスト
- 04セーブ、会話、ダンジョン生成の確認
MCP 対応
scenario-godot-expert の知識が前提です。入力には、このスキルが MCP サーバーまたはクライアントを提供するとの記載はありません。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ scenario-labs/ skills --skill scenario-godot-gameplayインストール後、Claude Code で/scenario-godot-gameplayを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/critique.mdexpert-notes.mdgui-paths.mdprocedures.mdsources.mdscripts/agentkit/gameplay/arena.gdbrain.gdbullet_pool.gdcombat_layers.gdcrowd_bench.gddungeon_gen.gdenemy_body.gdenemy_manager.gdhero_enemy.gdhit_log.gdhitbox_3d.gdhurtbox_3d.gdnav_tools.gdperception.gdquest_rules.gdsave_io.gdstate_machine.gdstate.gdscripts/agentkit/gameplay/inventory/inventory.gditem_stack.gditem.gdscripts/agentkit/gameplay/states/attack.gdchase.gdpatrol.gdsearch.gd詳細
Godot のゲームプレイシステム(ゲームプレイシステムプログラマー)
対象:Godot 4.7.2.stable、Jolt、macOS Apple Silicon。
エキスパートレベルでは、ゲームプレイシステムはシーンなしでテストできる小さな契約です。純粋な判定関数、データ用の Resources、検出用のグループを使います。そのコストは、ゲームが必要とするエージェント数で測定し、異論が出る前に把握します。基本方針は、ルールをデータとして構築し、--fixed-fps 60 でヘッドレス実行してから、キャプチャを 1 つ確認することです。ここで名前が挙がっている関連スキルが利用可能なスキルにない場合は、ユーザーにインストールを依頼してください
(npx skills add scenario-labs/skills --skill <name>)。無人で進める場合は、ツールスキーマを確認して作業を続け、不足している点を明示してください。
必須の前提知識: scenario-godot-expert(チャンネル、gd_run、レビューの反復、4.7 の落とし穴)。
ステータス(2026-10-02): Godot 4.7.2 で、ライブテスト 16 件、ラウンド 2 のチェック(P15)、オフラインスイートが合格しています(references/procedures.md、tests/code/godot-gameplay/)。
Dialogue Manager 4.1.0、LimboAI 1.8.1、GUT 9.7.1 は、それぞれ独自のプロジェクトコピーで実行しました。
方針(エキスパートならではの差分)
- ベンチマークには必ず
--fixed-fps 60を使い、通常のヘッドレス実行は避けてください。 ヘッドレスの各フレームは 6.9 ms スリープし(low_processor_usage_mode_sleep_usec)、フレーム数よりも少ない物理ティック数で実行されます。Performance.TIME_*モニターは約 1 秒に 1 回更新され、固定 fps では 0 を返します。フレームあたりの実時間ミリ秒を測定し、3 回の中央値を取り、同じ実行内の A/B ペアを比較してください(実測、P0)。 - ベースラインが考えるほど、群衆はボトルネックではありません。 NavigationAgent3D の回避を使う 200 体の CharacterBody3D の敵は、M5 Max でフレームあたり約 2 ms のコストです。コストはエージェント 800 体まで線形に増加し(7.3 ms)、200 体では回避自体のコストはノイズの範囲内です。
max_neighborsや経路再計算間隔を調整しても、測定可能な改善はありませんでした。回避の利点は正確性です。回避なしでは重なり合うペアが 3108 組あったのに対し、回避ありでは 0 組でした(P2)。エージェントにノードが不要な場合に限り、サーバーエージェントと MultiMesh 1 つに切り替えてください(200 体で 0.26 ms)。 - 「マップ」ではなく、割り当てたメッシュの準備を待ってください。 ドキュメントには物理フレームを 1 回待つとあります。実測では、最初の同期に 3 フレームかかりました。再ベイク後、領域が古いポリゴンをまだ提供している間にマップ ID が変わりました。割り当て前に
region_get_iteration_idを読み取り、その値が変わるまで待ってから、さらにマップ同期を 1 回行ってください(nav_tools.wait_applied、P1)。 - 静的コライダーのグループからベイクしてください。 新しい NavigationMesh は、領域の子からメッシュとコライダーの両方を解析するため、領域の下に NPC がいると 3 m の穴ができてしまいます(Bramwell 2W4JP48oZ8U 00:12:37、実測 P1)。
- 判断条件に応じて AI の段階を選んでください。 群衆には enum の脳を使い(Shaggy Dev oqFbZoA2lnU 00:00:32)、ヒーローやプレイヤーキャラクターにはノード FSM を使います。デザイナーが行動を作成する場合はビヘイビアツリーを使い(Chap.C E_FIy2dTkNc)、汎用アクションを多数組み合わせる場合に限り GOAP を使ってください(Wahaha Yes cm5Jxo31plw)。処理予算の目安:敵 40 体を 5 Hz で更新すると AI なしの場合と比べて 0.07 ms、全敵を毎ティック更新すると 0.45 ms かかります(P3)。
- ヒットボックスはマスク、食らい判定はレイヤーを設定し、1 回の攻撃につき HitLog を 1 つ使ってください(Queble cX-vzfmzjnE 00:06:36、00:12:55)。新しいエリアはレイヤー 1、マスク 1 で始まり、所有者自身にもヒットします(P4)。
- 高速弾はデータとして扱うか、CCD 付きのボディを使ってください。
continuous_cdがない場合、30 m/s でも 8 個中 6 個のボールが厚さ 0.1 m の壁を通り抜けました。Jolt は 500 m/s で上限に達します。1 つの MultiMesh で描画するレイ掃引データ弾のコストは、1000 発あたり 0.35 ms です。CCD 付きのプールされた RigidBody3D は 1.05 ms かかります(P5)。 .tresの保存ファイルはコードです。 GDScript の sub_resource はロード時に実行されます(Godotneers 43BZsLZheA4 01:03:14、P6 で再現)。ロード前にスキャンし、CACHE_MODE_IGNOREでロードしてから、バージョンを確認して移行し、暗号化するのではなく値を検証してください(01:01:37)。
NavigationAgent3D の仕様(P15、実測)
ドキュメントの警告(NavigationAgents のページ)と Bramwell(2W4JP48oZ8U 00:14:14)。特に記載がない限り、4.7.2 で測定:
- 回避には
target_positionが必要です。 回避を有効にして目標を設定しない場合、safe_velocityは 30 ティックの間 0 のままでした。目標を設定すると、3 m/s の速度がそのまま返されました。 - 回避は経路探索に影響しません。 エージェントはナビメッシュや物理に関する知識を持たない円形のため、壁は引き続き経路から決まります(ドキュメント)。
- 物理フレームごとに
get_next_path_position()を 1 回呼び出し、経路の終端に到達した後は呼び出さないでください。is_navigation_finished()が true になった後も最後のポイント(ここでは 0.93 m 先)を返すため、そこでステアリングを止めないとエージェントがじわじわ進みます。 path_desired_distanceは、1 ティック分の移動距離と、床面から上にあるナビメッシュの浮動小数点誤差(約 0.3 m)を超える値にする必要があります。 25 m/s(1 ティックあたり 0.42 m)で 0.1 に設定した場合、1800 ティック中 1782 ティックでエージェントが逆走し、到着しませんでした。1.0 に設定すると 182 ティックで到着しました。0.1 に設定し、足元を床面の高さで測定した場合、最初の経路ポイントから進みませんでした。- デフォルト値:
time_horizon_agentsは 1.0、time_horizon_obstaclesは 0.0 です。エージェントが早く減速しすぎる場合は、ホライズンを短くしてください。max_neighborsが少なすぎると、回避が無視されます(ドキュメントによるもの。ここでは未測定)。
最初に確認する項目
| 入力項目 | 計画に影響する理由 | デフォルト |
|---|---|---|
| 画面上のエージェント数 | ノードかサーバーエージェントか、思考予算 | 200 |
| ゲームプレイのフレーム予算 | すべてのベンチマークに適用される上限 | 60 fps で 16.67 ms の 4 分の 1(4.2 ms)[追加] |
| 2D または 3D | NavigationAgent2D か 3D か、Area2D か 3D か | 3D |
| 振る舞いの作成者 | コード(FSM、enum)かデザイナー(LimboAI)か | コード |
| セーブデータの信頼性 | 自分のファイルのみか、共有または MOD 対応か | 共有:スキャンまたは JSON |
| プラットフォーム | 入力デバイス、CPU 予算 | デスクトップ、キーボードとマウス、およびゲームパッド |
ワークフロー
- プロジェクトをセットアップします。
gd_env.base_project("3d", P)で Base3D をクローンし、独自のapplication/config/nameを設定します。そうしないと、クローン間でuser://が共有されます。gg.install(P)を実行します。- GATE:project.godot に
3d/physics_engine="Jolt Physics"と指定されたレンダラーがあり、gd_run.check_all(P)が解析エラー 0 件を報告すること。
- レベルのナビゲーション。
- 静的ジオメトリはグループ
nav_sourceに入れます。make_navmesh(parsed colliders, source group)でメッシュを構築し、次にbake、その後wait_appliedを実行します。 - GATE:サンプル地点間の
map_get_pathが空でなく、障害物との交差が 0 件であること。ポリゴン数が 0 を超えていること。NPC のスポーン地点に穴がないこと(P1)。
- 静的ジオメトリはグループ
- 移動と群衆。
- 回避を有効にし、
velocity_computedからmove_and_slideを呼び出します。再経路探索は頻度を制限し、ステアリングは XZ のみで行います。 gg.crowd_bench(P, n)を 50、200、800 で実行し、次にgg.scaling_verdictを実行します。独自のベンチマークシーンでは、壁時計時間と並べてPerformance.add_custom_monitor(&"ai/thinks", callable)とOBJECT_ORPHAN_NODE_COUNTをログに記録します(迷子になったNode.new()を 5 つ作成した後で 5 と表示されました。P15)。- GATE:重複ペアが 0 件であること。指数が 1.3 未満であること。目標数での中央値がゲームプレイ予算(
gg.budget_share)内に収まること。孤立ノード数が一定であること。その後、群衆のウィンドウ表示キャプチャを開くこと。
- 回避を有効にし、
- AI。
- 脳をヒステリシス付きの
decide(state, percept)として記述します。知覚処理はまず低コストのもの(距離、視野角、レイ 1 本、古いヒットの確認)から行います。マネージャーは約 5 Hz で思考し、ティックごとに上限を設けます。遠くの敵は思考頻度を下げます。ティアの判定には二乗距離を使います。ヘッドレスではVisibleOnScreenNotifier3D.is_on_screen()が常に false になり、ベンチマークでは全員が遠距離ティアに入ってしまうためです(P15)。ノード FSM は主人公に使い、デザイナーが作成する場合は LimboAI を使います。 - GATE:GUT の遷移表がすべて成功すること。主人公のシナリオ履歴が期待されるシーケンス(パトロール、追跡、攻撃、追跡、捜索、パトロール)と一致すること。思考コストの A/B を測定すること(P3、P9、P13)。
- 脳をヒステリシス付きの
- 戦闘。
- レイヤーに名前を付けます(World 1、Player 2、Enemy 3、PlayerHurtbox 5、EnemyHurtbox 6、Projectile 7)。ヒットボックスとハートボックスはキットから使用します。弾丸には
bullet_poolを使い、跳ね返るオブジェクトには CCD を有効にした RigidBody3D を使います。 - GATE:共有ログを使ったスイングで 1 ヒットを記録すること。同じ陣営なら 0 ヒットであること。トンネリングテストの最高速度で 8 件中 0 件になること。弾丸が独自のレイヤーにあること(P4、P5)。
- レイヤーに名前を付けます(World 1、Player 2、Enemy 3、PlayerHurtbox 5、EnemyHurtbox 6、Projectile 7)。ヒットボックスとハートボックスはキットから使用します。弾丸には
- 永続化。
- VERSION と
migrate()を備えた型付きセーブ Resource を使い、save_io.save_resource_atomicとload_resource_checkedを使用します。インベントリは所持者ごとに複製します。 - GATE:ラウンドトリップ後に一致すること。古いフィクスチャがすべて読み込めること。不正なフィクスチャが拒否されること。キャッシュテストに合格すること(P6、P7)。
- VERSION と
- 会話とクエスト。
- Dialogue Manager 4.x の
.dialogueファイルを使います。クエストの順序は、納品、続行、依頼です。 - GATE:インポート時にすべてのファイルがエラー 0 件でコンパイルされること。
gg.check_dialogue_textで欠落したキューが見つからないこと。ヘッドレスでの進行により各分岐に到達し、状態が変更されること(P8)。
- Dialogue Manager 4.x の
- 入力。
- 物理キーのアクションを使い、ゲームプレイ入力は
_unhandled_inputで処理します。キー割り当ては ConfigFile に保存します。 - GATE:合成プレスと just-pressed のテストに合格すること。STOP パネルが送信されたクリックを消費すること。再読み込み後もキー割り当てが保持されること(P10)。
- 物理キーのアクションを使い、ゲームプレイ入力は
- プロシージャルコンテンツ。
- まずシード付きデータ(
dungeon_gen)とバリデーターを用意し、次にジオメトリ、ベイク、経路の順で作成します。 - GATE:1000 個のシードでバリデーターの失敗が 0 件であること。同じシードで同じハッシュが生成されること。開始地点からボスまでの経路が存在すること。最上部のキャプチャを開くこと(P11)。
- まずシード付きデータ(
- 仕上げ。
test_gameplay_live.pyを実行し、証拠をコピーします。references/critique.mdに照らして自己評価します。
数値
| 値 | 測定値 | 比較条件 |
|---|---|---|
| 回避ありのボディ200体 | 1フレームあたり1.98 ms、重なり0件 | M5 Max、固定60 fps、障害物14個の60 mアリーナ |
| 回避ありのスケーリング | エージェント50体:1.07 ms、200体:2.00 ms、800体:7.31 ms(エージェント1体あたり約9 µs) | 同上 |
| サーバーエージェント200体とMultiMesh | 0.26 ms | 同上 |
| 思考コスト、敵200体 | 予算0.40 ms、毎ティック0.78 ms、なし0.33 ms | 5 Hz、1ティックあたり40体 |
| 初回のナビメッシュ同期 | 物理フレーム3回;リベイクは2〜16回後に適用 | 非同期反復オン(デフォルト) |
| ベイク | 5 ms、ポリゴン180個 | 60 mアリーナ、セル0.25 |
| 弾丸1000発 | データ0.35 ms;CCDありのRigidBody3Dは1.05 ms、CCDなしは1.00 ms(壁を45%の確率で通過) | 60 m/s、寿命2秒 |
| Joltの速度上限 | 500 m/s | physics/jolt_physics_3d/limits/max_linear_velocity |
| ダンジョン生成と検証 | 中央値88 µs、有効1000件中1000件 | メイン経路14部屋、分岐4本 |
| NavigationAgent3Dのデフォルト値 | radius 0.5、max_neighbors 10、neighbor_distance 50、max_speed 10、回避オフ | 4.7.2 ClassDB |
品質ゲート
- 測定可能な項目:群衆のメトリクス(重なり合うペア、ペア間の最小距離、スタック、床をすり抜けた数);
scaling_verdict;budget_share;意図的に一度失敗させた後、GUT(gd_run.run_tests)が成功;check_allの解析エラーが0件;1000シードでのバリデーター;保存用フィクスチャ;会話のコンパイル。 - ビジュアル:SubViewport内のウィンドウキャプチャ(160x90ウィンドウ)を
gd_review.image_checksで確認し、 1600 px以下のコンタクトシートを開いて目視します。プレイヤーの周囲で群衆の間隔が保たれていること、 障害物の中にエージェントがいないこと、スタートからボスまでのダンジョン経路を確認します。
よくある間違い
| 間違い | どのように現れるか | 修正 |
|---|---|---|
| 通常のヘッドレスモードでベンチマークを行う | 負荷にかかわらず「フレーム」あたり約 7 ms | --fixed-fps 60 |
| メッシュを割り当てた直後にパスを問い合わせる | パスが空で、エージェントがスポーン地点で動かない | wait_applied |
| NPC が内部にいる状態でデフォルトのベイク(ルートから Both)を行う | スポーン地点の周囲に穴ができる | グループと静的コライダー |
| 「速度を上げるために」回避をオフにする | 3108 組が重なり、敵が融合する | 回避をオンにする:200 体でも負荷はわずかです |
| 敵ごとに 1 つの Area3D を使って視界を判定する | ブロードフェーズの処理が増える | 距離、円錐、レイ 1 本 [追加] |
| テレポートしたボディへのレイ判定を信頼する | 敵が壁越しに「見える」 | ヒット位置が古くないか距離を確認する |
| ヒットエリアのレイヤーとマスクを 1 にする | 自分自身にヒットし、ダメージが 3 倍になる | マスクかレイヤーのどちらか一方のみを使う;HitLog |
area_entered 内で monitoring = false にする | 「in/out シグナル中は関数がブロックされます」 | set_deferred |
| デフォルトレイヤーに弾丸ボディを配置する | 1000 個のボディが衝突し、フレームあたり 110~170 ms かかる | 専用レイヤーを使い、マスクをワールドとターゲットに設定する |
| 高速ボディで CCD を使わない | 弾丸が 30 m/s で壁を通り抜ける | continuous_cd またはレイでスイープしたデータ |
ダウンロードした .tres を読み込む | スクリプトが実行される | load_resource_checked または from_native を使った JSON |
再保存後にデフォルトの load() を使う | 古い値が戻る | CACHE_MODE_IGNORE |
Array[String] に save.items = [...] を使う | 実行時エラー、または set() 経由で何も起きない | items.assign([...]) |
各宝箱でインベントリの .tres をプリロードする | ある宝箱の戦利品がすべての宝箱に表示される | 各所持者ごとに duplicate(true) |
| Selector の最初の子が常に成功する | 攻撃ブランチが実行されない(Chap.C E_FIy2dTkNc 00:17:27) | 最初にゲートを置く、BTProbability または AlwaysFail を使う |
| アクションに論理キーコードを使う | AZERTY で WASD が動作しない | 物理キーコードを使う |
| 物理補間がオンの状態でテレポートする | ジャンプ中、ボディが滑るように描画される [追加、未測定] | 移動後に reset_physics_interpolation() を呼び出す(このメソッドは存在します) |
オートロードに queue_free() を使う | 4.7.2 ではクラッシュしませんが、以降のアクセスはすべて「以前に解放済み」というエラーになり、読み取り値は 0 になります(ドキュメントではクラッシュすると記載) | 解放せず、代わりに状態をリセットする |
引き継ぎ
- 受け取るもの: プロジェクトのレイアウト、オートロードのポリシー、保存モデル(scenario-godot-architecture)、レベルと、
nav_source内の静的コライダーおよびコントローラー(scenario-godot-3d-world)、TileMapLayer のナビゲーション(scenario-godot-2d)、これら2つを通じた scenario-* スキルのアート。 - 引き渡すもの:
state_changedを scenario-godot-animation へ、再生イベントと優先度を scenario-godot-audio へ(ミックス、バス、減衰、リスナーはそちらで扱います)、インベントリーシグナル、会話バルーン、再割り当てメニューを scenario-godot-ui へ、ヒット位置と法線を scenario-godot-vfx へ、Resources と純粋なdecide()を scenario-godot-multiplayer へ、ベンチマーク JSON を scenario-godot-performance-export へ。
Godot 4.7 に関する注意
- ナビゲーションクラスは 4.7.2 では実験的です。リージョンとマップの反復処理はデフォルトで非同期のため、同期には数フレームかかります。
region_bake_navigation_meshは非推奨です。parse_source_geometry_dataとbake_from_source_geometry_data(または非同期版)を使用してください。region_get_iteration_idは存在します。query_path(params, result, callback)はコールバックを戻る前に実行しました(P15)。処理をフレーム外に移すものではありません。 - MultiMesh は1つのボックスとしてカリングされます。
custom_aabb(存在します)を設定し、大規模な群衆はエリアごとに分割してください。 - NavigationMesh の
geometry_parsed_geometry_typeのデフォルトは Both(2)です。セルサイズと高さのデフォルトは 0.25 です。 - 4.6 以降、新規プロジェクトでは Jolt が 3D のデフォルトですが、エージェントが作成した project.godot は、Jolt を設定しない限り GodotPhysics3D を使用します(リードの差分)。
duplicate(true)がコピーするのはファイル内サブリソースのみです(4.5 以降)。duplicate_deep(DEEP_DUPLICATE_ALL)は外部サブリソースもコピーします。JSON.from_nativeとto_nativeが存在します。通常の JSON 数値は float として返されます。InputEventKey.as_text()は "W - Physical" と表示します。新しい InputEventKey の device は 16 です (DEVICE_ID_KEYBOARD)。- アドオン:Dialogue Manager は 4.1.0 で、タイトルはキューです。DM3 の動画は1バージョン古いものです。 LimboAI 1.8.1 には、4.7.2 で動作する「4.6」GDExtension ビルドが同梱されています。
参考資料
references/procedures.md(コードと実行結果を含む P0~P15)、expert-notes.md(専門家による判断、タイムスタンプ)、critique.md(評価基準)、gui-paths.md(エディターのメニュー)、sources.md(動画、ドキュメント、アドオンのハッシュ)。scripts/gd_gameplay.py:インストール、run_job、crowd_bench、scaling_verdict、budget_share、会話のリント、セーブのスキャン。scripts/agentkit/gameplay/:nav_tools、arena、crowd_bench、 enemy_body、ステートマシンとステート、brain、perception、enemy_manager、hitbox、hurtbox、hit_log、 combat_layers、bullet_pool、save_io、inventory、quest_rules、dungeon_gen、voice_pool。