Game Dev · August 2, 2026
Spawning Systems and Wave Design in Shmups: Building a Level From Nothing
A shmup level is, at its core, a directed sequence of enemy appearances. The spawner system — the code that reads a level definition and turns it into enemies arriving on screen at the right moments — is the engine that drives the entire play experience. Building one that is flexible enough for good design iteration without becoming a maintenance burden requires some upfront architecture thought.
Published August 2, 2026
Every shmup level can be described as a timeline: at time T, spawn enemy group G at position P with behavior B. The complexity of a spawner system comes from how richly you can express that description, how efficiently you can execute it, and how easily a designer — who may be you, six months from now — can edit and tune the wave sequence without touching the engine code.
The data-driven spawner
The worst approach is hardcoding wave data directly in game logic. It forces a recompile for every tuning change, makes the wave sequence impossible to inspect without reading code, and mixes design data with engine logic in a way that scales badly. The correct approach is data-driven: wave definitions live in an external file (JSON, CSV, or a custom format), and the spawner system reads, parses, and executes them at runtime.
Each entry in the wave list describes one spawn event: a time offset, an enemy type, a position or set of positions, and a movement pattern. The spawner reads the list in order and fires each event when the level timer crosses its time value. The trigger type shows a common extension: event-driven spawns rather than purely time-driven ones, letting the level wait for all enemies of a wave to be destroyed before advancing.
Time-based versus trigger-based spawning
Pure time-based spawning is simple to implement: the spawner walks the wave list and checks timestamps. But it has a critical design problem: if the player destroys enemies faster than expected, they may be ahead of the spawn timeline, creating dead zones where nothing is happening. If they are slower — taking damage, losing power, using fewer bombs — they may be falling behind, facing a level paced for a stronger player state.
Trigger-based spawning, where subsequent waves fire only when a condition is met (all enemies dead, a time window elapsed, the player crossed a scroll position), adapts to player performance automatically. The level breathes: dense waves until they are cleared, then a brief gap before the next. This produces a more dynamic feel and more forgiving pacing without requiring difficulty-adaptive code.
Hybrid systems combine both: time gates ensure the level moves forward even if a wave is taking too long, while trigger gates reward fast play with responsive pacing. Most polished commercial shmups use some version of this.
Formation types and their design purpose
The unit of composition in a shmup wave is the formation: the geometric arrangement of enemies as they appear and move. Different formations serve different design purposes and feel different to the player.
V-formations and line formations arriving from the top create a challenge of density and timing: the player must clear them before they clutter the field or before their bullets accumulate. Diagonal sweep formations that enter from a side and sweep across the screen force the player to move laterally, which changes the pacing and tests positional adaptability rather than fixed safe zones. Pincer formations arriving from both sides simultaneously create a compression feeling that elevates pressure.
The design rule is that each formation should serve a specific function in the level's arc. A V-formation of fast, lightly armored grunt enemies at the start of a level teaches the player to clear quickly and builds confidence. A side-sweep of heavily armored enemies in the middle forces repositioning and tests whether the player can operate under lateral pressure. A pincer of enemies that drop power-ups rewards aggressive play on both flanks. Formation design is applied difficulty design, not decoration.
Difficulty scaling through the spawner
A spawner that reads wave data from a file can apply difficulty modifiers at load time, creating the normal/hard/lunatic difficulty modes without duplicating the wave definitions.
Increasing enemy count and compressing timestamps produces more enemies arriving faster, which is the core of higher difficulty in a shmup. Enemy bullet speed and pattern density are typically stored on the enemy definition rather than the wave, so those are scaled separately. Combining all three axes — enemy count, spawn timing, and bullet speed — gives enough control to create genuinely distinct difficulty modes from a single wave file.
The level editor pipeline
If your wave definitions live in a structured file format, a level editor becomes feasible with relatively modest engineering effort. At the simplest, a timeline visualization that shows wave events as blocks along a horizontal axis, with drag-to-reposition and click-to-edit, is enough to make design iteration dramatically faster than hand-editing JSON. Flukz shipped with a level editor from its first public build, and the community wave files that resulted from that decision were more complex and varied than anything the original team could have produced alone.
Even without a graphical editor, separating wave data from engine code pays off immediately. Hot-reloading the wave file during development — detecting that the file changed and reparsing without restarting the engine — turns the tuning loop from edit-compile-run into edit-save-see. That iteration speed compounds over the weeks of wave design work a full shmup requires.