thedivergentai/godot-game-loop-waves
Expert 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.
npx skills add https://github.com/thedivergentai/GD-Agentic-Skills --skill godot-game-loop-waves
> [!NOTE]
> Resource Context: This module provides expert patterns for Wave Loops. Accessed via Godot Master.
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.
wave_spawner.gd / wave_weighted_spawner.gd).| Live density | Approach | MANDATORY loads |
| :--- | :--- | :--- |
| Under ~80 SceneTree enemies | Node manager + Marker spawners | wave_manager.gd, wave_spawner.gd, wave_resource.gd |
| Swarm visuals / hundreds | MultiMesh + weighted composition | wave_loop_patterns.gd (MultiMesh / async path), wave_weighted_spawner.gd |
| ~10k bodies | PhysicsServer / NavigationServer RIDs (no per-mob Node) | wave_loop_patterns.gd server-RID patterns; do not scale wave_manager node spawns |
wave_manager.gd is the SceneTree golden path (deferred add_child, group/signal clear counts, optional pool). Treat it as prototype→mid-scale — for RID swarms, follow wave_loop_patterns.gd instead of instantiating thousands of nodes.
spawner at that Marker; manager defers spawn and clears via &"enemies" group + signals.WaveWeightedSpawner.spawn_enemy() from the composition loop (or set manager spawner to the weighted node).Use await timers in wave_manager.gd — MANDATORY read before writing a custom timeline. Spawns use call_deferred(&"add_child", …); clear via signals/groups (not get_children() scans).
Define variety in wave_resource.gd; place units with wave_spawner.gd / wave_weighted_spawner.gd. Do not hardcode scene paths in the manager.
| Pattern | Best For | Logic |
| :--- | :--- | :--- |
| Linear | Story missions | Hand-crafted list of WaveResource. |
| Endless | Survival modes | Code-generated WaveResource with multiplier math. |
| Triggered | RPG Encounters | Wave starts only when player enters an Area3D. |
get_tree().get_nodes_in_group(&"enemies") for efficient access.MultiMeshInstance3D to batch thousands of meshes into a single GPU call.use_async_iterations on your navigation regions or use NavigationServer3D.query_path().base_stats.duplicate_deep() to give each mob its own unique data.StringName (&"enemies", &"take_damage") for optimal hash performance and to avoid unnecessary string allocations.call_deferred(&"add_child", enemy).set_deferred("disabled", true) immediately upon death.Marker3D nodes in the editor so you can visually adjust spawn points without digging into code.> MANDATORY: Read the appropriate script before implementing the corresponding pattern.
10 Expert patterns: MultiMesh swarms, async pathfinding, background preloading, and server-side physics mobs.
Orchestrates the timeline, delays between waves, and tracks clear via group counts + signals. Uses call_deferred add_child; optional pool via use_pool / recycle_enemy.
Data containers for wave compositions and difficulty settings.
Marker3D spatial portal — get_spawn_position() with optional radius jitter. Wire as WaveManager.spawner.
Weighted random enemy selection at a Marker. Use when composition variety is probability-driven rather than fixed counts.
To optimize performance with hundreds of enemies, enable Occlusion Culling.
OccluderInstance3D to your arena and bake it.Decouple your wave data from the UI using a CanvasLayer and signals.
TextureProgressBar on a CanvasLayer for bosses, or Sprite3D with a viewport texture for individual enemy health bars.> LLM-ignorance rule: If a general agent would not know it before reading, load the reference — never delete expert deltas.
> Progressive disclosure: open Official Documentation links only when researching a specific API; load Related Skills when routing to a peer domain — do not preload the whole lattice.
WaveResource compositions and delays stay designer-editable without hardcoding spawn tables in managers.PackedScene.instantiate() plus add_child / call_deferred is the safe spawn path for wave enemies.StringName groups and get_node_count_in_group instead of scanning children every frame.await; never instantiate mid-physics callback without deferring.create_timer without a forever _process countdown.wave_started / wave_cleared / all_waves_complete decouple UI, audio, and combat from the manager timeline.ResourceLoader.load_threaded_request bosses/heavy waves so first spawn does not hitch.RandomNumberGenerator.rand_weighted.await, signals, and call_deferred patterns the async wave trigger depends on.Resource/@export composition and duplicate_deep so spawned mobs do not share stats.Take thedivergentai/godot-game-loop-waves from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.