godot-game-loop-waves について
敵のウェーブを実装する Godot 開発者向けスキルです。WaveResource と管理ノードでスポーン時刻やクリア条件を扱い、密度に応じて SceneTree ノードからサーバー側 RID までの構成を選べます。
主な機能
- 01WaveResource による遭遇データ定義
- 02管理ノードによる時刻とクリア条件の制御
- 03Marker と重み付きスポーン位置
- 04密度に応じたノード、MultiMesh、RID の構成
- 05プールと非同期パス検索のパターン
活用シーン
- 01ストーリーミッションの敵ウェーブ構築
- 02難易度が上昇するエンドレスモード作成
- 03プレイヤーの Area3D 進入を契機に遭遇開始
- 04大規模な敵群に適したスポーン構成の選択
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ thedivergentai/ game-dev-agentic-skills --skill godot-game-loop-wavesインストール後、Claude Code で/godot-game-loop-wavesを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/migration-notes.mdwave-expert-patterns.mdscripts/wave_loop_patterns.gdwave_manager.gdwave_resource.gdwave_spawner.gdwave_weighted_spawner.gd詳細
アーキテクチャ設計: 「Wave-State」パターン
マスター実装では、ウェーブをデータ駆動型の遷移として扱います。スポーン数をハードコードするのではなく、WaveResource を使用して「エンカウント」を定義し、WaveManager が順番に処理します。
主な役割
- マネージャー:タイムラインを統括します。ウェーブ間の遅延を処理し、「勝利」条件(敵がすべて倒されること)を追跡します。
- スポナー:敵が出現する位置の空間情報を提供する、疎結合のノードです(
wave_spawner.gd/wave_weighted_spawner.gd)。 - リソース:コードに触れずにデザイナーがゲームのバランスを調整できる、不変のデータコンテナーです。
密度の判断ツリー(コーディング前に規模を選択)
| 実行中の密度 | アプローチ | 必須の読み込み |
|---|---|---|
| SceneTree の敵が約 80 体未満 | ノードマネージャー + Marker スポナー | wave_manager.gd、wave_spawner.gd、wave_resource.gd |
| 群れのビジュアル/数百体 | MultiMesh + 重み付け構成 | wave_loop_patterns.gd(MultiMesh / 非同期パス)、wave_weighted_spawner.gd |
| 約 10,000 体 | PhysicsServer / NavigationServer の RID(モブごとの Node なし) | wave_loop_patterns.gd のサーバー RID パターン。wave_manager のノードスポーンでスケールさせないでください |
wave_manager.gd は SceneTree の標準的な方法です(add_child を遅延実行し、グループ/シグナルでクリア数を追跡、オプションでプールを使用)。プロトタイプから中規模までを対象とします。RID 群れの場合は、数千のノードをインスタンス化せず、wave_loop_patterns.gd に従ってください。
構成の標準的な手順
- wave_resource.gd の構成テーブルを作成します。
- wave_spawner.gd の Marker を配置します(バリエーションの重み付けが必要な場合は wave_weighted_spawner.gd を使用します)。
- wave_manager.gd の
spawnerにその Marker を指定します。マネージャーはスポーンを遅延実行し、&"enemies"グループとシグナルを使ってクリアを判定します。 - 固定数を重み付けに置き換える場合は、構成ループから
WaveWeightedSpawner.spawn_enemy()を呼び出します(または、マネージャーのspawnerに重み付けノードを設定します)。
エキスパート向けコードパターン
1. 非同期ウェーブトリガー
カスタムタイムラインを作成する前に必ず読むこと。wave_manager.gd の await タイマーを使用します。スポーンには call_deferred(&"add_child", …) を使い、シグナル/グループでクリアを判定します(get_children() による走査は使用しません)。
2. 構成ベースのスポーン
wave_resource.gd でバリエーションを定義し、wave_spawner.gd / wave_weighted_spawner.gd でユニットを配置します。マネージャー内にシーンのパスをハードコードしないでください。
マスター判断マトリクス:進行方式
| パターン | 最適な用途 | ロジック |
|---|---|---|
| リニア | ストーリーミッション | 手作業で作成した WaveResource のリスト。 |
| エンドレス | サバイバルモード | 乗数計算を用いてコードで生成する WaveResource。 |
| トリガー式 | RPG のエンカウント | プレイヤーが Area3D に入ったときのみウェーブを開始します。 |
絶対に避けること
- 敵をすべて探すために get_children() を繰り返し呼び出さないでください — これは非常に低速です。敵は必ず「enemies」グループに追加し、効率的にアクセスするには
get_tree().get_nodes_in_group(&"enemies")を使用してください。 - 数百体の敵に対して、常に instantiate() と queue_free() を繰り返さないでください — これによりガベージコレクションによるカクつきが発生します。オブジェクトプールを使用して、既存の敵インスタンスを再利用してください。
- 群れを表現するために、個別の MeshInstance3D ノードを何千個も生成しないでください — ドローコールが大幅に増加します。
MultiMeshInstance3Dを使用して、何千ものメッシュを単一の GPU 呼び出しにまとめてください。 - 何百ものエージェントの経路探索をメインスレッドで計算しないでください — ゲームがフリーズします。ナビゲーション領域で
use_async_iterationsを有効にするか、NavigationServer3D.query_path()を使用してください。 - 子ノードを追加する前に is_inside_tree() を確認し忘れないでください — スポナーが削除待ち状態の場合、子ノードの追加時にクラッシュします。スポナーが引き続きツリー内でアクティブであることを必ず確認してください。
- プリロードしたリソース(stats.tres など)を、生成したモブに直接割り当てないでください — すべてのモブで同じヘルス値やステータスが共有されます。各モブに固有のデータを持たせるため、必ず
base_stats.duplicate_deep()を呼び出してください。 - 頻繁に呼び出すグループ処理に標準の文字列を使用しないでください — ハッシュ性能を最適化し、不要な文字列の割り当てを避けるため、必ず
StringName(&"enemies"、&"take_damage")を使用してください。 - 物理コールバック内でエンティティを同期的に直接生成しないでください — 物理処理中にノードをインスタンス化すると、物理状態が破損する可能性があります。必ず
call_deferred(&"add_child", enemy)を使用してください。 - 死亡した敵の CollisionShapes を有効なままにしないでください — 死体がタワーやナビゲーションを妨げます。死亡時にすぐ
set_deferred("disabled", true)を使用してください。 - 複雑な Object 型を MultiplayerSynchronizer で同期しないでください — サポートされているのはプリミティブ型のみです。複雑なデータを同期する場合は、UID または ID を同期し、クライアント側でデータをローカル検索してください。
- プレイヤーへのフィードバックなしにウェーブを自動開始しないでください — プレイヤーが自分で判断できるよう、UI のカウントダウン、視覚的な「ウェーブ接近中」エフェクト、または開始ボタンを必ず用意してください。
- スポーン位置を (0,0,0) にハードコードしないでください — エディターで
Marker3Dノードを使用すれば、コードを調べずにスポーンポイントを視覚的に調整できます。 - 毎フレーム、子ノードを数えてウェーブの完了を確認しないでください — 処理コストが高すぎます。ローカルカウンターを維持するか、シグナルベースのシステムを使用して、アクティブな敵の数を追跡してください。
- すべてのエンティティタイプに同じナビゲーションマップを使用しないでください — 飛行する敵と歩行する敵がいる場合は、経路探索の問題を防ぐため、別々のナビゲーションマップを使用してください。
- スポナーのコリジョンシェイプを不均等にスケーリングしないでください — コリジョン検出の計算が破綻します。代わりにシェイプリソースのプロパティを調整してください。
利用可能なスクリプト
必須:対応するパターンを実装する前に、適切なスクリプトを読んでください。
wave_loop_patterns.gd
エキスパート向けパターン 10 種:MultiMesh による群れ、非同期経路探索、バックグラウンドでのプリロード、サーバー側の物理モブ。
wave_manager.gd
タイムラインとウェーブ間の遅延を管理し、グループ数とシグナルを使って全滅を追跡します。call_deferred による add_child を使用し、use_pool / recycle_enemy によるプールは任意です。
wave_resource.gd
ウェーブの構成と難易度設定のデータコンテナー。
wave_spawner.gd
Marker3D の空間ポータル — オプションで半径方向のジッターを加えた get_spawn_position() を使用します。WaveManager.spawner として接続してください。
wave_weighted_spawner.gd
Marker で重み付きランダム選択によって敵を選びます。固定数ではなく、確率に基づいて構成にバリエーションを持たせる場合に使用してください。
エキスパート向けウェーブパターン
1. 群れのオクルージョンカリング
何百もの敵がいる場合のパフォーマンスを最適化するには、オクルージョンカリングを有効にします。
- 設定:アリーナに
OccluderInstance3Dを追加してベイクします。 - 結果:壁や柱の背後に完全に隠れた敵は GPU で処理されなくなり、FPS が大幅に向上します。
2. ウェーブ UI アーキテクチャ
CanvasLayer とシグナルを使用して、ウェーブデータと UI を分離します。
- ウェーブカウンター:現在のウェーブ数と合計ウェーブ数を表示します。
- ヘルスバー:ボスには
CanvasLayer上のTextureProgressBarを使用するか、個々の敵のヘルスバーにはビューポートテクスチャを使ったSprite3Dを使用します。
必要に応じて参照する専門知識
LLM の無知に関するルール: 一般的なエージェントが読む前に知らない情報は、参照資料を読み込んでください。専門的な差分は決して削除しないでください。
- wave-expert-patterns.md — 基本的な説明の復元(アーキテクチャ、理由、実装の詳細)
参考資料
段階的な情報開示:特定の API を調査するときにのみ公式ドキュメントのリンクを開き、関連分野へ案内するときにのみ関連スキルを読み込んでください。関連情報をすべて事前に読み込まないでください。
公式ドキュメント
- リソース —
WaveResourceの構成と遅延をデザイナーが編集できるようにし、マネージャーにスポーンテーブルをハードコードせずに済みます。 - ノードとシーンインスタンス —
PackedScene.instantiate()とadd_child/call_deferredを組み合わせる方法は、ウェーブの敵を安全にスポーンする手段です。 - グループ — 毎フレーム子ノードを走査する代わりに、
StringNameグループとget_node_count_in_groupを使って生存中の敵を追跡します。 - アイドル処理と物理処理 — タイミング制御にはタイマー/
awaitを使用します。遅延処理せずに物理コールバック中にインスタンス化しないでください。 - SceneTreeTimer — ウェーブ開始前の遅延やスポーン間隔には
create_timerを使用し、終了しない_processのカウントダウンは避けます。 - シグナルの使用 —
wave_started/wave_cleared/all_waves_completeにより、UI、オーディオ、戦闘処理をマネージャーのタイムラインから分離できます。 - バックグラウンド読み込み — ボスや負荷の高いウェーブには
ResourceLoader.load_threaded_requestを使用し、最初のスポーン時に処理が停止するのを防ぎます。 - 乱数生成 —
RandomNumberGenerator.rand_weightedを使用して、重み付きの構成とスポーンの揺らぎを設定します。 - MultiMesh の使用 — ミニオンが何百体もいてドローコールが急増する場合は、群れのビジュアルをまとめて描画します。
- オクルージョンカリング — カメラ外のアリーナの敵を非表示にし、密集したウェーブでも GPU の負荷を抑えます。
- ナビゲーションの概要(3D) — 非同期のエージェント経路と、飛行ユニット用・歩行ユニット用に分けたマップを使用します。
- サーバーの使用 — SceneTree のノードでは拡張性が足りない場合は、PhysicsServer3D/NavigationServer3D の RID で群れを管理します。
関連スキル
前提条件
- godot-project-foundations — WaveManager をアリーナに組み込む前に、シーンツリー、エクスポート、グループについて確認します。
- godot-gdscript-mastery — 非同期のウェーブトリガーが依存する、型付き
await、シグナル、call_deferredのパターンを扱います。 - godot-resource-data-patterns —
Resource/@exportによる構成とduplicate_deepを扱い、スポーンした敵同士でステータスが共有されないようにします。
補完
- godot-signal-architecture — ウェーブ/UI/戦闘シグナルの所有権を扱い、カウントダウンとクリアイベントでリークが発生しないようにします。
- godot-combat-system — ウェーブの敵ユニットが倒れた際にアクティブな敵のカウンターを減らす、ヒットボックス、死亡、ダメージを扱います。
- godot-navigation-pathfinding — 数百体のウェーブエージェントの経路探索に使う、NavigationServer のクエリ、回避マスク、マップを扱います。
- godot-scene-management — ウェーブで PackedScenes が頻繁に入れ替わる際の、プール、スレッド化された読み込み、安全な追加/削除を扱います。
- godot-performance-optimization — 群れの密度予算に向けた MultiMesh、オクルージョン、サーバー側ボディを扱います。
- godot-monte-carlo-balancer — エンドレスモードのリリース前に、スタイルマトリックスを使ってスポーン数、出現率、難易度カーブをサンプリングします。
下流/利用側
- godot-genre-tower-defense — WaveResource のシーケンスとクリア勝利条件で駆動する、レーン/ポータルウェーブを扱います。
- godot-genre-shooter — ウェーブ制シューターのアリーナ/大群のスポーンペースと敵の多様性を扱います。
- godot-genre-survival — 同じマネージャーループを基盤とする、エンドレスの倍率ウェーブと圧力の上昇を扱います。
マスター
- godot-master — スキル横断での検索に使う、ライブラリのルーターとミラー化されたモジュールエントリです。