scenario-godot-architecture について
Godot 4.7.2でゲームを構築するゲームプレイプログラマーやアーキテクト向けのスキルです。検証済みの基本システム、ツール、テスト規則をまとめ、シーンの単独実行、定義と状態の分離、境界の検証を重視します。
主な機能
- 01単独実行できるシーン設計
- 02Resource によるデータ定義
- 03セーブ、入力、エクスポートの規則
- 04アーキテクチャ監査と型チェック
- 05GUT テストと検証ワークフロー
活用シーン
- 01再利用可能な Godot シーンの計画
- 02インベントリ、体力、ダメージシステムの設計
- 03セーブとロード処理の実装
- 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.mdexpert-notes.mdgui-paths.mdprocedures.mdsources.mdscripts/gd_architecture.pyscripts/agentkit/architecture/arch_audit.gdbench.gdbuild_catalog.gdpack_probe.gdscaffold.gdsetup_input.gdvalidate_data.gdscripts/templates/core/combat/damage_info.gdhealth_component.gdhitbox_3d.gdhurtbox_3d.gdscripts/templates/core/data/item_catalog.gditem_definition.gdscripts/templates/core/events/events.gdscripts/templates/core/input/input_actions.gdinput_remap.gdscripts/templates/core/inventory/inventory.gditem_stack.gdscripts/templates/core/main/base_level.gdmain_game.gdscripts/templates/core/save/persist_component.gdsave_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 です。
基本方針(エキスパートの視点)
-
すべてのシーンを単独で実行できるようにし、所有者が依存関係を注入します。 上位にアクセスするシーン(
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、空のままのノードエクスポートを検出します。 -
定義には 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)ではフィールドが失われました(検証済み)。 -
実行時にフォルダーを列挙せず、編集時にレジストリを構築します。 フォルダースキャンはエディターでは機能しますが、エクスポートすると動作しません(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はロードできました(検証済み[追加])。 -
セーブファイルはデータであり、コードではありません。 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])。 - GDScript が埋め込まれた
-
安定したルート、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でチェックし、拒否する場合は解放します(確認済み)。 -
入力は1つのテーブルで管理し、ツールから書き込みます。
InputActions.defaults()がsetup_inputに渡され、project.godotに[input]を書き込みます。すべてのイベントでdevice = -1を使用します。確認済み:コード内で作成したジョイパッドイベントはデフォルトで device 0 になり、パッド1とは一致しません。4.7ではキーボードは device 16、マウスは32を報告します(バージョンによる違い)。キーはphysical_keycodeで割り当てます[追加]。実行時の再割り当ては、InputEvent リソースではなく、通常の JSON としてuser://に保存します。 -
静的型付けはスタイルではなく、ゲートです。 型付けの警告はデフォルトで無効です(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()を使います(確認済み)。 -
ボディではなく、スプライトを反転します。 FAT Earth のルール(zLdTvkLsmgA [00:04:30])は当てはまりますが、原因は異なります。2Dで確認済み:シェイプは反転に追従しますが、
move_and_slideがスケール(-1, 1)を回転180度、scale.y -1 に書き換えるため、毎フレームscale.x = -1を設定するとボディが毎フレーム反転しました。flip_hを使い、マーカーの位置を反転します。 -
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", ...) から取得した、自分専用のクローンです。
- プロジェクト。
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"とストレッチ関連の行があり、オートロードは多くても数個であること。 - メインシーン。
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 が動作すること。 - データ。
ItemDefinitionの.tresファイル(id、max_stack、tags、icon、world_scene)を作成します。A.build_catalog(P)を実行し、次にA.validate_resources(P, root=..., cls="ItemDefinition", required=(...))を実行します。ゲート: 問題が0件で、id が一意であり、再読み込み後の件数がファイル数と一致すること。 - コンポーネント。
HealthComponent(Node:シグナル、耐性、diedは1回だけ発生)、Hurtbox3DとHitbox3D(Area:hurtbox にはエクスポートされたhealthと構成警告があること)、Inventory(RefCounted:スタック、余剰分、全件処理の削除)、および接着役に徹する薄い Player スクリプトを作成します。コンポーネントは単純に保ちます(Firebelley、rCu8vQrdDDI [00:03:42])。ゲート: 実際の物理オーバーラップで25ダメージを与えるテストを含む GUT テストに合格すること。 - セーブとロード。 セーブする各エンティティに
PersistComponent(save_id、properties、対象のsave_state())を追加します。残りの処理はSaveService.collect、write、read、applyが行います。ゲート: 型の往復変換、破損後の.bakフォールバック、v1 から v2 への移行(int が復元されること)、ゲーム全体(インベントリ、体力、位置)のテストに合格すること。 - 入力。
InputActionsテーブルを編集し、A.setup_input(P)を実行して、実行時にInputRemapを使用します。ゲート: アクションが13個あり、すべてのイベントがdevice -1であること。parse_input_event、パッド1での一致、競合時の移動、バインディングの往復変換のテストに合格すること。 - ゲート。
A.typing_gate(P)、A.lint(P)、bite テスト(ガードを1つ壊し、テストがちょうど1つ失敗することを確認)、A.pack_smoke(P, out, args)を実行します。ゲート: すべて成功すること。「品質ゲート」を参照してください。
数値
| 値 | ここでの測定値(この Mac のエディター実行ファイル 4.7.2) | 比較対象 |
|---|---|---|
| 型付きと型なしのバブルソート、n=2000 | 82.5 vs 124.1 ms(1.50x;3 回の実行で 1.46x〜1.50x) | Boon 4.1:116% 高速 |
Array.sort、n=200,000 | 23.5 vs 23.5 ms(1.00) | 組み込み関数では型付けの効果なし |
| GUT スイート、G1 | 30 テスト、129 アサーション、0.8 s | bite テスト:129 件中 126 件 |
エクスポート済み .pck、G1 | 1.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 フォルダーのスキャン、A04user://の.tres、A05str_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()の後には必ずファイルを読み直してください。
参考資料
references/expert-notes.md(エキスパートの原則、タイムスタンプ、最新の修正)、procedures.md(すべてのツール、テスト、記録された結果)、critique.md(自己レビューの評価基準)、gui-paths.md(エディター GUI の手順)、sources.md(URL)。scripts/gd_architecture.py、scripts/agentkit/architecture/*.gd、scripts/templates/core/:ツールと、テスト済みの G1 コア。