Wave Forge
Wave Forge orchestrates enemies and squads over time. It lets you design wave sequences for tower defense, horde mode, roguelike encounters, and any game mode that spawns enemies in timed waves. Each wave sequence is built from a three-level hierarchy: Sequences > Waves > Entries.
Wave Forge links to your Enemy Database (required) for individual enemy
spawns and optionally to a Squad Database for spawning pre-configured
squad groups. Open it from
Window → Simple Enemy Forge → Wave Forge Wizard.
The wizard has 5 steps: Setup, Definitions, Sequences, Settings, and Generate.
Understanding the Wave Hierarchy
Wave sequences use a three-level nesting structure. A Sequence contains multiple Waves, and each wave contains multiple Entries that define what to spawn and when.
In this example, the "Forest Assault" sequence plays three waves in order. The first wave sends scouts and wolves, the second brings the main force including a full squad group, and the final wave spawns a boss.
Entry Structure
Each entry within a wave is either a single enemy or a squad group, toggled in the wizard. Entries have the following fields:
| Field | Type | Description |
|---|---|---|
enemyCode |
string | Enemy code from the linked Enemy Database. Empty if using a squad. |
squadCode |
string | Squad code from the linked Squad Database. Empty if using a single enemy. |
count |
int | How many to spawn from this entry. |
spawnDelay |
float | Delay in seconds between each individual spawn within this entry. |
startTime |
float | Time offset in seconds from the start of the wave before this entry begins spawning. |
The IsSquad property returns true if the entry references a squad code,
allowing your spawn handler to branch between single-enemy and squad-group spawning logic.
Loop Settings
Each wave sequence has built-in loop settings for endless or escalating game modes:
| Field | Type | Description |
|---|---|---|
loopAfterLast |
bool | When true, the sequence restarts from Wave 1 after the last wave finishes. |
difficultyScalePerLoop |
float | Multiplier applied each loop iteration (e.g., 1.2 means 20% harder per loop). Compounds across loops. |
maxLoops |
int | Maximum number of loop iterations. 0 means infinite looping. |
Loop settings make Wave Forge ideal for endless modes. A sequence with
loopAfterLast = true, difficultyScalePerLoop = 1.15, and
maxLoops = 0 creates an infinitely escalating challenge.
Step 1 — Setup
Configure the basics and link your databases.
Enter a prefix for generated types (e.g., "ForestWaves" generates
ForestWavesType.cs, ForestWavesDatabase.cs, etc.)
Link one or more Enemy Database ScriptableObjects. These provide the enemy codes that entries can reference. At least one enemy database is required.
Link Squad Database ScriptableObjects if you want entries that can spawn entire squad groups. When linked, entries gain an enemy/squad toggle.
Multiple databases are supported for both types. When duplicates exist across databases, the first database's version wins.
Step 2 — Definitions
Define the dynamic property system for your wave sequences. These properties are attached to sequences, not individual waves or entries.
- Categories — Dropdown properties like Biome, Difficulty Tier, or Game Mode
- Flags — Boolean toggles like Is Tutorial, Has Boss Wave, Is Endless
- Numerics — Number fields like Recommended Level, Total Enemy Count, Estimated Duration
- Texts — String fields like Wave Briefing, Completion Reward Description
This step also provides schema export and import for AI-assisted content creation. Export your definitions as Full JSON, Light JSON, or Markdown, send them to an AI, and import the generated sequences back.
Step 3 — Sequences
Build your wave sequences in a split-panel editor. The left panel shows a paginated list of sequences, and the right panel shows the selected sequence's details.
For each sequence, you define:
- Identity — Name, Code (auto-generated from name), Description
- Waves — An ordered list of waves, each with a name, preDelay, postDelay, and entries
- Entries per wave — Each entry has an enemy/squad toggle, code dropdown, count, spawnDelay, and startTime
- Loop Settings — loopAfterLast, difficultyScalePerLoop, maxLoops
- Dynamic Properties — Values for all categories, flags, numerics, and texts defined in Step 2
Waves and entries use nested ReorderableLists with foldouts, so you can reorder waves by dragging and expand/collapse each one to manage complex sequences.
Step 4 — Settings
Choose output paths for the generated scripts and database asset.
- Scripts Path — Where to write the generated
.csfiles (enum, database, editor) - Asset Path — Where to create the
.assetdatabase file
Step 5 — Generate
Click Generate to create all output files. Wave Forge uses the same two-phase generation as all other forges:
Generates
{Prefix}Type.cs (enum), {Prefix}Database.cs
(ScriptableObject), and {Prefix}DatabaseEditor.cs (custom editor).
Triggers AssetDatabase.Refresh() and domain reload.
After domain reload, an
[InitializeOnLoad] handler detects a SessionState
flag and creates the {Prefix}Database.asset from temporary JSON data
via delayCall.
After generation, your project will contain:
Runtime Helper: SimpleWaveRunner
SimpleWaveRunner is a pure C# event-driven wave runner with no MonoBehaviour
dependency. You drive it by calling Update(deltaTime) each frame.
Runner Controls
| Method | Description |
|---|---|
Start(sequence) |
Begin running a wave sequence from the beginning. |
Update(deltaTime) |
Drive the state machine forward. Call each frame. |
Pause() |
Pause the runner. No spawns are dispatched until resumed. |
Resume() |
Resume after pausing. |
Stop() |
Stop the runner and reset to idle. |
SkipCurrentWave() |
Skip remaining spawns in the current wave and advance to the next. |
SkipToWave(index) |
Jump to a specific wave index. |
Runner State
| Property | Type | Description |
|---|---|---|
CurrentSequence |
SimpleWaveSequence? |
The wave sequence currently being run, or null if idle/stopped. Read-only. |
Phase |
WaveRunnerPhase |
Current phase: Idle, PreDelay, Spawning, PostDelay, or Complete. |
CurrentWaveIndex |
int | Index of the current wave within the sequence. |
LoopCount |
int | Number of completed loop iterations (0 on first play-through). |
CurrentDifficultyScale |
float | Cumulative difficulty scale (starts at 1.0, multiplied each loop). |
ElapsedTime |
float | Total elapsed time since Start, excluding paused time. |
IsRunning |
bool | True if actively running (not idle, not complete, not paused). |
IsComplete |
bool | True if the sequence has finished. |
IsPaused |
bool | True if the runner is paused. |
Events
| Event | Args | Description |
|---|---|---|
OnWaveStarted |
(int waveIndex, string waveName) |
Fires when a wave's preDelay ends and spawning begins. |
OnSpawnRequested |
(WaveSpawnRequest request) |
Fires once per individual spawn. Handler should instantiate the enemy or squad. |
OnWaveCompleted |
(int waveIndex, string waveName) |
Fires when all entries in a wave have been dispatched. |
OnSequenceCompleted |
(none) | Fires when the entire sequence is done (no more waves or loops). |
OnLoopStarted |
(int loopCount, float difficultyScale) |
Fires when the sequence loops back to the beginning. |
OnPaused |
(none) | Fires when the runner is paused. |
OnResumed |
(none) | Fires when the runner is resumed. |
Runtime Data Structures
SimpleWaveSequence
A complete wave sequence with identity, waves, loop settings, and dynamic properties.
SimpleWave
A single wave within a sequence, containing timed spawn entries.
SimpleWaveEntry
A single spawn entry within a wave — either an enemy or a squad.
ISimpleWaveDataSource
Interface implemented by generated wave databases.
Tips
Use preDelay and postDelay
Give players a breather between waves. A 3-5 second postDelay lets players prepare, while a short preDelay can display a "Wave incoming!" warning.
Mix Enemies and Squads
Combine individual enemy spawns with squad groups in the same wave. Use squads for coordinated groups and individual entries for stragglers or bosses.
Stagger with startTime
Use different startTime values within a wave to create staggered spawns.
Fast enemies at 0s, ranged at 2s, and heavies at 5s creates natural pressure waves.
Loop for Endless Modes
Enable loopAfterLast with a difficultyScalePerLoop of 1.1-1.3
for escalating endless modes. Use maxLoops = 0 for truly infinite play.