$npx -y skills add thedivergentai/GD-Agentic-Skills --skill godot-game-loop-wavesExpert patterns for managing combat waves, difficulty scaling, and automated enemy spawning in Godot 4. Use when building wave-based shooters, tower defense, or arena games.
| 1 | ## Godot 4.7 Baseline |
| 2 | |
| 3 | - Expert patterns in this skill target **Godot 4.7+** (stable, 2026-06-18). |
| 4 | - Consult the [Godot 4.7 migration guide](https://docs.godotengine.org/en/4.7/tutorials/migrating/upgrading_to_godot_4.7.html) when upgrading projects from 4.6. |
| 5 | - **NEVER** assume 4.6 defaults (stretch mode, audio area_mask, RichTextLabel percent flags) without checking 4.7 migration notes. |
| 6 | |
| 7 | # Wave Loop: Combat Pacing |
| 8 | |
| 9 | > [!NOTE] |
| 10 | > **Resource Context**: This module provides expert patterns for **Wave Loops**. Accessed via Godot Master. |
| 11 | |
| 12 | ## Architectural Thinking: The "Wave-State" Pattern |
| 13 | |
| 14 | A Master implementation treats waves as **Data-Driven Transitions**. Instead of hardcoding spawn counts, use a `WaveResource` to define "Encounters" that the `WaveManager` processes sequentially. |
| 15 | |
| 16 | ### Core Responsibilities |
| 17 | - **Manager**: Orchestrates the timeline. Handles delays between waves and tracks "Victory" conditions (all enemies dead). |
| 18 | - **Spawner**: Decoupled nodes that provide spatial context for where enemies appear. |
| 19 | - **Resource**: Immutable data containers that allow designers to rebalance the game without touching code. |
| 20 | |
| 21 | ## Expert Code Patterns |
| 22 | |
| 23 | ### 1. The Async Wave Trigger |
| 24 | Use `await` and timers to handle pacing without cluttering the `_process` loop. |
| 25 | |
| 26 | ```gdscript |
| 27 | # wave_manager.gd snippet |
| 28 | func start_next_wave(): |
| 29 | # Delay for juice/prep |
| 30 | await get_tree().create_timer(pre_delay).timeout |
| 31 | wave_started.emit() |
| 32 | _spawn_logic() |
| 33 | ``` |
| 34 | |
| 35 | ### 2. Composition-Based Spawning |
| 36 | Manage enemy variety using a Dictionary-based composition strategy in your `WaveResource`. |
| 37 | |
| 38 | ```gdscript |
| 39 | # wave_resource.gd |
| 40 | @export var compositions: Dictionary = { |
| 41 | "Res://Enemies/Goblin.tscn": 10, |
| 42 | "Res://Enemies/Orc.tscn": 2 |
| 43 | } |
| 44 | ``` |
| 45 | |
| 46 | ## Master Decision Matrix: Progression |
| 47 | |
| 48 | | Pattern | Best For | Logic | |
| 49 | | :--- | :--- | :--- | |
| 50 | | **Linear** | Story missions | Hand-crafted list of `WaveResource`. | |
| 51 | | **Endless** | Survival modes | Code-generated `WaveResource` with multiplier math. | |
| 52 | | **Triggered** | RPG Encounters | Wave starts only when player enters an `Area3D`. | |
| 53 | |
| 54 | ## NEVER Do |
| 55 | |
| 56 | - **NEVER iterate through get_children() to find all enemies** — This is extremely slow. Always add enemies to an "enemies" group and use `get_tree().get_nodes_in_group(&"enemies")` for efficient access. |
| 57 | - **NEVER constantly instantiate() and queue_free() hundreds of enemies** — This causes garbage collection stutters. Use an object pool to reuse existing enemy instances. |
| 58 | - **NEVER spawn thousands of separate MeshInstance3D nodes for swarms** — This will tank your draw calls. Use `MultiMeshInstance3D` to batch thousands of meshes into a single GPU call. |
| 59 | - **NEVER calculate pathfinding for hundreds of agents on the main thread** — This will freeze your game. Enable `use_async_iterations` on your navigation regions or use `NavigationServer3D.query_path()`. |
| 60 | - **NEVER forget to check is_inside_tree() before adding a child** — If the spawner is queued for deletion, adding a child will crash. Always verify the spawner is still active in the tree. |
| 61 | - **NEVER assign a preloaded resource (like stats.tres) directly to spawned mobs** — They will all share the exact same health/stats. Always call `base_stats.duplicate_deep()` to give each mob its own unique data. |
| 62 | - **NEVER use standard strings for high-frequency group calls** — Always use `StringName` (&"enemies", &"take_damage") for optimal hash performance and to avoid unnecessary string allocations. |
| 63 | - **NEVER spawn entities directly inside physics callbacks synchronously** — Instantiating nodes during physics steps can corrupt the physics state. Always use `call_deferred(&"add_child", enemy)`. |
| 64 | - **NEVER leave CollisionShapes on dead enemies active** — Corpses will block towers and navigation. Use `set_deferred("disabled", true)` immediately upon death. |
| 65 | - **NEVER synchronize complex Object types via MultiplayerSynchronizer** — It only supports primitive types. For complex data, sync a UID or ID and look up the data locally on the client. |
| 66 | - **NEVER auto-start waves without player feedback** — Always provide a UI countdown, a visual "Wave Incoming" effect, or a start button to maintain player agency. |
| 67 | - **NEVER hardcode spawn positions at (0,0,0)** — Use `Marker3D` nodes in the editor so you can visually adjust spawn points without digging into code. |
| 68 | - **NEVER check wave completion by counting children every frame** — It's too expensive. Maintain a local counter or use a signal-based system to track active enemy counts. |
| 69 | - **NEVER use the same navigation map for every entity type** — If you have flying and walking enemies, use separate navigation maps to prevent pathing issues. |
| 70 | - **NEVER scale collision shapes non-uniformly for spawners** — This breaks the collision detection math. Adjust the shape resource |