Wave System
A wave spawner that scripts who spawns, where and when, chains waves into auto-advancing sets, and spawns them at named points on the stage that hosts them.
What the system is for
Wave-based games — survival shooters, tower-defense levels, arcade shmups — all need the same backbone: script a burst of spawns, wait for the player to clear it, then start the next one, until the stage is done. Without a dedicated system that logic ends up scattered across coroutines and magic numbers, with spawn positions hardcoded to Transform references that break the moment a level layout changes.
Serenity's Wave System separates that into two halves that compose cleanly. A wave is a scripted burst — which actor, which spawn point, what delay, how many, what cadence — authored as data. A stage is the spatial container that owns the named spawn points those entries spawn at. Wave Sets chain waves in order and auto-advance as each one clears, with no coroutine glue required.
The Unity problem
Hand-rolled wave logic tends to hardcode spawn positions as scene references, spawn counts as inline numbers, and the auto-advance rule as an ad hoc check somewhere in an Update loop. Reusing a wave layout on a second stage means duplicating and re-wiring all of it. Balancing a wave means editing code and re-entering Play mode to see the result.
Spawn points are a related but separate problem: a level's spawn locations belong to its spatial layout, not to any one wave. Coupling wave data directly to scene Transforms makes waves impossible to author outside a specific scene, and makes swapping a level's layout mean rewriting every wave that spawns into it.
How Serenity approaches it
A wave is a UnityWaveConfigSO holding an ordered list of UnitySpawnEntry values — actor, spawn point id, delay, count and cadence. An ordered UnityWaveSetSO chains waves for one stage, and IWaveService drives progression: configure from the set, spawn wave 0's entries, and when RemainingActors hits zero on the active wave it auto-advances to the next one, all the way to AllWavesClearedSignal.
Spawn points live separately, on the Stage: a prefab carrying a StageRoot and named SpawnPoint children, collected by an optional SpawnPointRegistry into a lookup the wave service resolves each entry's spawn point id against. Swapping which stage a wave set runs on, or reusing a spawn layout across levels, is a matter of pointing at a different stage prefab — the wave data itself is untouched.
How it fits into Serenity
Wave composes WaveClearedSignal and AllWavesClearedSignal through the event dispatcher: the service both emits WaveClearedSignal when a wave's remaining-actor count drains to zero and consumes it itself to trigger the next wave, so auto-advance requires no external wiring. AllWavesClearedSignal is the hook a Stage completion listener reacts to when the run is done.
Stage owns no wave data of its own — its StageRoot and SpawnPointRegistry exist purely to give the wave service a resolver for spawnPointId lookups, keeping the spatial authoring (where things happen) and the scripted content (what happens and when) as independent, recombinable pieces.
Practical workflow
- Build a stage prefab with a StageRoot, a SpawnPointRegistry and a set of named SpawnPoint children marking where actors should appear.
- Open Tools ▸ Serenity ▸ Create ▸ Wave ▸ Create Wave Set and step through Intent ▸ Stage ▸ Waves ▸ Timeline ▸ Preview ▸ Done to author every wave for that stage in one run.
- Pick up the wizard's live spawn-point validation on the Stage step, which reports the exact prefab and spawn-point status for the stage you targeted before you author a single spawn entry.
- Author each wave's spawn entries — actor, spawn point id, delay, count and cadence — and let the wizard chain them into an ordered UnityWaveSetSO in the same run.
- Wire ConfigureFromWaveSet and SetSpawnPointResolver at the composition root, then call StartNextWave to kick off wave 0; every wave after that advances automatically.
- In Play mode, open the runtime debug inspector for a live wave table — status, total and remaining actors per wave — plus a Force Next Wave button to jump ahead while testing.
What you get
- IWaveService with configure-from-set, auto-advancing progression and WaveClearedSignal / AllWavesClearedSignal
- UnityWaveConfigSO spawn bursts scripting actor, spawn point id, delay, count and cadence per entry
- Ordered UnityWaveSetSO chains that drive one wave at a time and advance automatically as each clears
- Create Wave Set wizard — Intent ▸ Stage ▸ Waves ▸ Timeline ▸ Preview ▸ Done — authoring a stage's full wave chain in one run
- Live spawn-point validation in the wizard, reporting stage prefab and spawn-point status before entries are authored
- Stage-owned spawn points — StageRoot plus named SpawnPoint children collected by a SpawnPointRegistry resolver
- Runtime debug inspector with a live wave table and a Force Next Wave button for fast iteration
- 1.3.0: wave cleanup now releases only the actors the wave itself spawned, never other systems' pooled actors
When to use this
- Survival shooters, tower-defense levels or arcade games that need scripted enemy bursts chained into a level's progression.
- Projects that want to reuse the same spawn layout across multiple wave sets, or the same waves across multiple stage layouts.
- Teams that want to balance spawn timing and counts as data, validated by a wizard, instead of editing coroutine code.
- Games where waves must run alongside other spawning systems — scripted encounters, ambient spawners — without one system releasing another's pooled actors.
Related systems
Use Serenity when you want scripted spawn waves that auto-advance on their own, spawn at named points owned by the stage that hosts them, and stay authored as data you can preview and rebalance without touching code.
English
Español
Català