mcpField
S
scenario-godot-architecture

scenario-godot-architecture

github.com/scenario-labs開発1.2k163 回インストール1 日前に更新
リポジトリ

scenario-godot-architecture について

Godot 4.7.2でゲームを構築するゲームプレイプログラマーやアーキテクト向けのスキルです。検証済みの基本システム、ツール、テスト規則をまとめ、シーンの単独実行、定義と状態の分離、境界の検証を重視します。

主な機能

  1. 01単独実行できるシーン設計
  2. 02Resource によるデータ定義
  3. 03セーブ、入力、エクスポートの規則
  4. 04アーキテクチャ監査と型チェック
  5. 05GUT テストと検証ワークフロー

活用シーン

  1. 01再利用可能な Godot シーンの計画
  2. 02インベントリ、体力、ダメージシステムの設計
  3. 03セーブとロード処理の実装
  4. 04入力の再割り当てとエクスポート問題の確認

MCP 対応

scenario-godot-expert の知識が前提です。プロジェクトを GUI エディターで開いている場合、そのスキルの MCP チャネルを使うよう案内しています。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/scenario-labs/skills --skill scenario-godot-architecture

インストール後、Claude Code で/scenario-godot-architectureを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
critique.md
expert-notes.md
gui-paths.md
procedures.md
sources.md
scripts/
gd_architecture.py
scripts/agentkit/architecture/
arch_audit.gd
bench.gd
build_catalog.gd
pack_probe.gd
scaffold.gd
setup_input.gd
validate_data.gd
scripts/templates/core/combat/
damage_info.gd
health_component.gd
hitbox_3d.gd
hurtbox_3d.gd
scripts/templates/core/data/
item_catalog.gd
item_definition.gd
scripts/templates/core/events/
events.gd
scripts/templates/core/input/
input_actions.gd
input_remap.gd
scripts/templates/core/inventory/
inventory.gd
item_stack.gd
scripts/templates/core/main/
base_level.gd
main_game.gd
scripts/templates/core/save/
persist_component.gd
save_service.gd

詳細

翻訳後に原文が更新されています。最新の内容は原文をご確認ください。

Godot アーキテクチャ(ゲームプレイプログラマー兼アーキテクト)

対象:Godot 4.7.2.stable、macOS Apple Silicon。

Godot におけるエキスパートレベルのアーキテクチャとは、単独で実行できるシーン、Resource に格納されたデータ、小さなコンポーネントに分けられた振る舞い、そしてあらゆる境界(セーブファイル、入力、エクスポート)がテストで検証されていることを意味します。このスキルには、ソースに基づくエキスパート向けルールを収録しています。Godot 4.7.2 で挙動が異なる部分は修正済みです。また、テスト済みの G1 コア(インベントリ、体力とダメージ、セーブとロード、再バインド可能な入力)と、それを構築・監査するツールも含まれています。ここで名前を挙げた兄弟スキルが利用可能なスキルにない場合は、ユーザーにインストールを依頼してください(npx skills add scenario-labs/skills --skill <name>)。無人で進める場合は、ツールスキーマを確認して作業を進め、不足している点を明記してください。

必須の背景知識: scenario-godot-expert(チャネル、gd_run、レビューの反復、4.7 の落とし穴)。

状況(2026-10-02、第 2 ラウンド): 以下の内容はすべて、ここで Godot 4.7.2 上で実行しました。実環境でのテスト 12 件のうち、11 件が合格し、C# のチェックは未実施です。GUT テスト 30 件とオフラインの lint チェック 17 件も合格しています。第 2 ラウンドのプローブは references/procedures.md の P15 から P17 です。

基本方針(エキスパートの視点)

  1. すべてのシーンを単独で実行できるようにし、所有者が依存関係を注入します。 上位にアクセスするシーン(get_node("/root/...")、../)は、特定のツリーに結合されています。「呼び出しは下向き、シグナルは上向き、イベントは外向き」にします(Eric Peterson、yB3Wv-Lr7pg [00:05:54]、Godot ドキュメント「Scene organization」)。イベントバスのオートロードにはシグナルのみを持たせ、状態は保持させません(Peterson [00:17:36])。状態はツリーの上位で所有し、下位へ注入します。これは _ready が子ノードから先に実行されるためです(Mark Wilson、BgrHnJDt2ww [00:12:29])。arch_audit.gd は各シーンを単独でインスタンス化し、エラー、解決できない NodePath、空のままのノードエクスポートを検出します。

  2. 定義には Resource を使い、インスタンスごとの状態には使いません。 アイテムデータベースは Dictionary ではなく、型付きのカスタム Resource にします。これにより ID のタイプミスを防ぎ、ファイルの移動にもパスが追従します(Godotneers、4vAkTHeoORk [00:12:57])。ロード済みの Resource は共有インスタンスです。ある参照からの変更は、次の load() からも確認できます(検証済み)。そのため、定義は読み取り専用として扱います。スタック、体力、クールダウンは RefCounted または Node オブジェクトに格納します。インスタンスごとのコピー(耐久度、ランダム生成されたステータス)が必要な場合、Resource がフラットなら duplicate() を使い、外部のサブリソースを含む場合は duplicate_deep(Resource.DEEP_DUPLICATE_ALL) を使います。duplicate(true) では、それらが共有されたままになります(検証済み)。Resource クラスは 1 ファイルにつき 1 つにし、すべての _init パラメーターにデフォルト値を設定します(Godot ドキュメント「Resources」)。内部クラスの Resource は再読み込み時にフィールドが null になり、デフォルト値なしの _init(p_hp) ではフィールドが失われました(検証済み)。

  3. 実行時にフォルダーを列挙せず、編集時にレジストリを構築します。 フォルダースキャンはエディターでは機能しますが、エクスポートすると動作しません(Godotneers [01:13:35])。エクスポートされた .pck では、DirAccess.get_files_at が返したのは *.tres.remap のみでした(単純なスキャンでは 3 件中 0 件をロード)。一方、list_directory と build_catalog が生成した ItemCatalog では 3 件すべてが見つかりました(検証済み)。パックでは大文字と小文字が区別されます。大文字・小文字を誤ったパスは、「Case mismatch」という警告だけが出て macOS ではロードされましたが、パック内には存在しませんでした。そのため、ファイル名とフォルダー名はすべて小文字の snake_case にします(Godot ドキュメント「Project organization」、lint A11、A12)。.gdignore は空にする必要があり、パターンは指定できません(lint A15)。.gdignore はフォルダーをインポーターから隠しますが、load() からは隠しません。内部の .svg は「No loader found」エラーで失敗しましたが、テキスト形式の .tres はロードできました(検証済み[追加])。

  4. セーブファイルはデータであり、コードではありません。 Godot 4.7.2 で検証済み[追加]:

    • GDScript が埋め込まれた .tres を user:// に書き込むと、load() 時にそのスクリプトが実行されました。
    • str_to_var と ConfigFile.parse は、どちらも Object(Node,...) 形式のテキストから Node を生成しました。

    JSON.from_native を使った JSON エンベロープ(format、version、data)を使用してください。これにより int、StringName、Vector3 が保持されますが、通常の JSON では 3 が 3.0 として返されます。オブジェクトは受け付けず、エラーをログに記録して null を書き込みます。.tmp、.bak、rename を使ってアトミックに書き込み、バージョンに応じてマイグレーションします。最初からセーブを計画してください(Wilson [00:17:03])。

  5. 安定したルート、1階層のエントリーポイント、安定した親ノードの下にスポーンします。 MainGame が固定コンテナーを管理し、レベルはスポーンマーカーを公開して、プレイヤーを所有しないようにします(FAT Earth Studios、V4SO7foDoW4 [00:04:56])。弾丸は EntityRoot または EffectRoot の下に配置します。タワーを解放したとき、その子ノードも解放されたためです(Godotneers、W8gYHTjDCic [01:05:16])。レベルの切り替えはアイドル時間に行います(FAT Earth [00:10:13])。body_entered 内でレベルを切り替えると、「Removing a CollisionObject node during a physics callback」とログに出ます。request_level() は処理を遅延させるため、問題なく実行できます。ソースの instantiate() as BaseLevel を修正します[追加]:型が違うと null が返り、古いレベルが消えた後もインスタンスがリークしたため、まず is BaseLevel でチェックし、拒否する場合は解放します(確認済み)。

  6. 入力は1つのテーブルで管理し、ツールから書き込みます。 InputActions.defaults() が setup_input に渡され、project.godot に [input] を書き込みます。すべてのイベントで device = -1 を使用します。確認済み:コード内で作成したジョイパッドイベントはデフォルトで device 0 になり、パッド1とは一致しません。4.7ではキーボードは device 16、マウスは32を報告します(バージョンによる違い)。キーは physical_keycode で割り当てます[追加]。実行時の再割り当ては、InputEvent リソースではなく、通常の JSON として user:// に保存します。

  7. 静的型付けはスタイルではなく、ゲートです。 型付けの警告はデフォルトで無効です(Godot のドキュメント、静的型付け)。警告レベル(1)ではヘッドレス時に何も出力されないため、typing_gate が APFS クローン上で警告レベルをエラー(2)に設定します。型付けされたコードは、Boon の116%より小さい速度向上を示します(mNa0m2fvGOc [00:01:39]。数値を参照)。キャストの方針(ドキュメント、静的型付け):x as T は何も通知せず null を返しますが、型付き代入 var l: Label = node はエラー(「Trying to assign value of type 'Button' to a variable of type 'Label'」)を明示的に出し、関数を中断します。is と型付きローカル変数を使うか、型の違いがバグとなる場合は型付き代入を使います。as は null を処理する場合に限って使います。var n := get_node("X") は通常の Node 型と推論され、ゲートで n.position が拒否されます(「not present on the inferred type Node」)。var n: Node2D = $X と記述します。ネストされた型付きコレクションはコンパイルできません(「Nested typed collections are not supported」)。Array[Array] と Dictionary[StringName, Array] は使用できます。型付きの数学関数を使います。100万回の呼び出しで、clampf は clamp より1.70倍から1.74倍高速でした。また、Color に対する clamp() は解析エラーになるため、Color.clamp() を使います(確認済み)。

  8. ボディではなく、スプライトを反転します。 FAT Earth のルール(zLdTvkLsmgA [00:04:30])は当てはまりますが、原因は異なります。2Dで確認済み:シェイプは反転に追従しますが、move_and_slide がスケール(-1, 1)を回転180度、scale.y -1 に書き換えるため、毎フレーム scale.x = -1 を設定するとボディが毎フレーム反転しました。flip_h を使い、マーカーの位置を反転します。

  9. preload はスクリプトが保持する強参照です(FAT Earth、zLdTvkLsmgA [00:05:34])。確認済み:プリロードされた .tres は、保持元のスクリプトが読み込まれた時点でインスタンスなしにキャッシュされ、プローブがスクリプトを破棄した後もキャッシュに残りました。_ready 内の load() は、そのノードが解放されると解放されました。メニュー > カットシーン > レベルをプリロードする常駐ノードがあると、参照の連鎖が維持されます。小さな要素は使用元の近くでプリロードし、レベルやメニューは必要に応じて読み込みます(lint A09)。

ソースから得たその他の知見(短いルール)

ルールソースここでの確認内容
オープンな集合ではダックタイピングを使います:has_method(&"take_damage")、分類子としてのグループ、propagate_call(&"on_game_over")、call_group。独自のコンポーネントには is Hurtbox3D を使いますGodotneers、W8gYHTjDCic [00:19:36, 00:38:12]propagate_call が孫ノードまで到達しました
衝突が発生しない場合は、まずレイヤーまたはマスクの不具合を疑いますW8gYHTjDCic [01:03:09]arch_audit はレイヤー 0、マスク 0 を検出します。unmatched_masks(P16)は、どのオブジェクトもレイヤーとして使用していないマスクビットを検出します
状態の真実のソースとして Resource を使えば、ゲーム全体をダンプして再読み込みし、バグを再テストできます。開発ビルドのみ:プレイヤーが提供した .tres は決して読み込まないでください(スタンス 4)Szuflita、1qHPKg_Xovs [00:06:11]user://debug/ にダンプし、CACHE_MODE_IGNORE で再読み込み:値は保持されます
top_level = true、または 2 つの Node2D の間に通常の Node を置くと、トランスフォームの継承が切れますGodot ドキュメント、シーン構成親を x=100 に移動:両方の子は x=10 に留まり、通常の子は 110 に移動しました
ItemList.select() は item_selected を発行しません:ハンドラーを呼び出してください。リストの要求サイズは 0x0 です:custom_minimum_size を設定してください(ソースでは 250 px)4vAkTHeoORk [00:53:45, 00:47:32]シグナルは 0 件、最小サイズは (0, 0)
クラフトのチェックでは、レシピから削除する前にレシピをコピーします4vAkTHeoORk [01:07:14]Inventory.has_all テスト
マウスをキャプチャする 3D ゲームのダイアログでは、MOUSE_MODE_VISIBLE を設定し、閉じるときに以前のモードに戻します4vAkTHeoORk [00:37:01]ヘッドレスでは設定にかかわらずモード 0 が読み取られます:ウィンドウ表示または手動でテストしてください
デバッグ用のゴッドモード(重力なし、ヒットボックスなし、高速移動)を早い段階で作成しますzLdTvkLsmgA [00:02:34]構想のみ、未実行
Git:.godot/ を無視し、.uid サイドカーファイルをコミットし、.gitattributes に LFS パターンを設定し、アドオンの追加や Godot のアップグレード前にきれいな状態でコミットしますBatteryAcidDev、c3Jf-av_5NE [00:02:40, 00:22:20]git check-attr は png を LFS に振り分けます。スクリプトには .gd.uid が付き、テキストの .tres ファイルには付きません(git-lfs はインストールされていません)

最初に確認すること

  • 2D か 3D か、ジャンル、シングルプレイヤーかローカルマルチプレイヤーか(ローカルマルチプレイヤーでは、device = -1 ではなく、デバイスごとに1つのアクションセットが必要です)。
  • 対象プラットフォーム(エクスポート設定では大文字と小文字が区別されます。C# は Web にエクスポートできません)。
  • 言語:デフォルトは GDScript です。C# には .NET ビルドと SDK が必要です(.csproj があるか確認し、references/expert-notes.md に記載してください)。
  • セーブの要件(スロット、自動セーブ、バージョン管理、クラウド)、デバイス(パッド、再割り当て)、GUI エディターでプロジェクトを開いているかどうか(開いている場合は scenario-godot-expert の MCP チャネルを使用します)。

特に指定がない場合のデフォルト:型付き GDScript、レイヤー化レイアウト、シグナル専用の Events オートロード、定義には Resources、バージョン 2 の JSON セーブ、表から構築する入力、GUT テスト。

ワークフロー

セットアップ:import gd_architecture as A。P は gd_env.base_project("3d", ...) から取得した、自分専用のクローンです。

  1. プロジェクト。 A.unique_user_dir(P, "<unique name>") を実行し(Base3D のクローンはすべて、そうしないと同じ user:// を共有することが確認されています)、続いて A.scaffold_folders(P)、A.install_core(P)、A.register_autoload(P, "Events", "res://core/events/events.gd") を実行し、その後 gd_run.import_project(P) を実行します。ゲート: project.godot に 3d/physics_engine="Jolt Physics" とストレッチ関連の行があり、オートロードは多くても数個であること。
  2. メインシーン。 A.build_main_scene(P, props={"player_scene": ...}) を実行します。Main(常時) > Systems、World(一時停止可能) > LevelRoot、EntityRoot、EffectRoot、CanvasLayer の HUD 10、PauseLayer 20(一時停止時)、TransitionLayer 30、DebugLayer 40(常時)を構築し、後続のレイヤー用に間隔を空けます(FAT Earth)。ゲート: A.audit_scenes(P) にフラグがなく、一時停止テストで World が停止したまま、TransitionLayer が動作すること。
  3. データ。 ItemDefinition の .tres ファイル(id、max_stack、tags、icon、world_scene)を作成します。A.build_catalog(P) を実行し、次に A.validate_resources(P, root=..., cls="ItemDefinition", required=(...)) を実行します。ゲート: 問題が0件で、id が一意であり、再読み込み後の件数がファイル数と一致すること。
  4. コンポーネント。 HealthComponent(Node:シグナル、耐性、died は1回だけ発生)、Hurtbox3D と Hitbox3D(Area:hurtbox にはエクスポートされた health と構成警告があること)、Inventory(RefCounted:スタック、余剰分、全件処理の削除)、および接着役に徹する薄い Player スクリプトを作成します。コンポーネントは単純に保ちます(Firebelley、rCu8vQrdDDI [00:03:42])。ゲート: 実際の物理オーバーラップで25ダメージを与えるテストを含む GUT テストに合格すること。
  5. セーブとロード。 セーブする各エンティティに PersistComponent(save_id、properties、対象の save_state())を追加します。残りの処理は SaveService.collect、write、read、apply が行います。ゲート: 型の往復変換、破損後の .bak フォールバック、v1 から v2 への移行(int が復元されること)、ゲーム全体(インベントリ、体力、位置)のテストに合格すること。
  6. 入力。 InputActions テーブルを編集し、A.setup_input(P) を実行して、実行時に InputRemap を使用します。ゲート: アクションが13個あり、すべてのイベントが device -1 であること。parse_input_event、パッド1での一致、競合時の移動、バインディングの往復変換のテストに合格すること。
  7. ゲート。 A.typing_gate(P)、A.lint(P)、bite テスト(ガードを1つ壊し、テストがちょうど1つ失敗することを確認)、A.pack_smoke(P, out, args) を実行します。ゲート: すべて成功すること。「品質ゲート」を参照してください。

数値

値ここでの測定値(この Mac のエディター実行ファイル 4.7.2)比較対象
型付きと型なしのバブルソート、n=200082.5 vs 124.1 ms(1.50x;3 回の実行で 1.46x〜1.50x)Boon 4.1:116% 高速
Array.sort、n=200,00023.5 vs 23.5 ms(1.00)組み込み関数では型付けの効果なし
GUT スイート、G130 テスト、129 アサーション、0.8 sbite テスト:129 件中 126 件
エクスポート済み .pck、G11.59 MB、スモーク実行 0.3 sエクスポートテンプレート不要
厳格ゲートの検出数(unsafe_call_argument)8 ファイルで 33 件デフォルトゲート:22 ファイルで 0 件
clamp と clampf、1M 回の呼び出し29.5〜30.9 ms vs 17.1〜17.9 ms(1.70x〜1.74x、4 回の実行)型付き数学ヘルパー

品質ゲート

  • 測定可能:
    • typing_gate は、エラーとして扱われる警告を 0 件で返します。
    • lint はエラー検出を返しません(A03 フォルダーのスキャン、A04 user:// の .tres、A05 str_to_var、A07 内部 Resource、A08 デフォルト値なしの _init、A11 パスの大文字・小文字)。
    • audit_scenes はフラグを返しません。
    • validate_resources は問題を返しません。
    • GUT は成功し、bite テストは失敗します。
    • pack_smoke は、パック内で list_directory とカタログが読み込まれることを示します。
    • project.godot の Jolt、メインシーン、自動読み込みをテキストで確認します。
  • ビジュアル: ヘッドレスでは何も描画されません。このコア上のインベントリ UI またはデバッグ UI を確認する場合は、ウィンドウ表示で gd_run.capture_scene(P, "res://ui/inventory_debug.tscn", out="captures/inv.png", size=(1280, 720)) を使ってキャプチャし、image_checks を実行して画像を開きます(P17:11 フレーム、0.17 s)。レイアウトと操作感は scenario-godot-ui と scenario-godot-gameplay で扱います。GUI エディターでは、Scene ドックの警告アイコンは意図した箇所にのみ表示されます。

よくある間違い

間違い発生すること修正方法
通常の JSON.parse_string で保存count が 3.0 になり、Vector3 が文字列になるfrom_native と to_native を使うか、v1 では _restore_ints を使います
ジョブから ProjectSettings.save() を実行ヘッダーコメントとデフォルトキーが削除される(確認済み)想定どおりです。その後、Jolt と stretch を再確認します
load(path).instantiate()ゲートで安全でないメソッドアクセスが発生するvar ps: PackedScene = load(path) または preload を使います
add_child の前に @onready を読み取るnullコンストラクターでフィールドを設定し、add_child の後にノードを読み取ります(Godotneers [00:33:06])
GUT 9.7.1 の assert_engine_error(1)テストが失敗し、「Use assert_engine_error_count」と表示されるassert_push_error("text") または assert_engine_error_count(n) を使います

引き継ぎ

  • 受け取る元: scenario-godot-expert(概要、チャンネル、ツールキット);scenario-* スキル(ItemDefinition.icon および world_scene が参照するアイコンとモデル)。
  • 引き渡し先: scenario-godot-gameplay(状態マシン、これらのコンポーネントの操作感);scenario-godot-ui(Events および Inventory.changed 上の HUD、InputRemap.rebind による再バインド);scenario-godot-2d および scenario-godot-3d-world(スポーンマーカーを含む BaseLevel シーン);scenario-godot-multiplayer(体力とインベントリの権限);scenario-godot-pipeline-automation(ゲートの CI);scenario-godot-performance-export(実際のエクスポート)。
  • 形式: core/ フォルダー、project.godot のエントリー、テスト、エビデンス JSON。

Godot 4.7 に関する注意事項

  • コアでは型付き Dictionary(4.4)を使用しています。.uid サイドカー(4.4)はスクリプトと一緒に移動する必要があります(lint A14)。ResourceLoader.list_directory(4.4)と duplicate_deep(4.5)が使用できます。
  • FileAccess.store_* は bool を返します(4.4)。名前変更の前に確認してください。
  • class_name が解決されるのは --import の後です。自身のシーンをプリロードするスクリプトは、.tscn が存在するまで解析に失敗します(検証済み)。
  • エージェントが作成した project.godot のデフォルトは GodotPhysics3D です。Jolt を明示的に設定し、ProjectSettings.save() の後には必ずファイルを読み直してください。

参考資料

コメント