Game Programming · June 30, 2026
Scene and State Management in Shmups: Title, Gameplay, Pause, and Game Over
The scene flow of a finished shmup involves more states than most developers plan for at the start: title with attract mode demo, character or ship select, stage intro, gameplay, boss warning, mid-game pause, death animation, continue countdown, game over with high score entry, ending cutscene, and credits. These states connect in a graph with specific valid transitions, and implementing that graph poorly is one of the most common sources of hard-to-reproduce bugs in indie shmups.
Published June 30, 2026
Mapping the state graph early
Before writing a single scene transition, draw the complete state graph on paper or a whiteboard. Each node is a scene or mode; each directed edge is a valid transition trigger. The most important step is identifying which transitions can be initiated by the player and which are driven by game events, because these have different input-handling requirements.
Player-initiated transitions (pressing Start on the title screen, pressing Pause during gameplay) need input debouncing and must be blocked during transition animations to prevent double-triggering. Event-driven transitions (death animation completing, continue timer expiring) fire from game logic and must not be blocked by input state.
Common transitions that get overlooked at the planning stage:
- Title to attract demo and back to title after timeout
- Gameplay to pause and back, with the game world held exactly as it was
- Death to brief death animation (invulnerable period), then lives-check, then either respawn or game-over flow
- Boss clear to stage clear, then either next stage or ending
- Game over to continue countdown (if continues remain), or to high score entry, or directly to title
Scene vs overlay: when not to change scenes
Not every game mode requires a full scene change. The pause menu, for example, should almost never unload and reload the gameplay scene. Instead, pause is a state overlaid on an existing scene: the game world freezes (or runs at reduced time scale), a pause overlay UI is instantiated on top, and unpausing removes the overlay and restores time scale. Implementing pause as a full scene transition requires serialising and restoring the entire game state on every pause/unpause, which is expensive and error-prone.
The rule of thumb: if the underlying game world needs to be visible behind the new mode, implement it as an overlay, not a scene change. The death screen, boss health bar, and continue countdown all qualify as overlays. The title screen, character select, and credits are genuinely different scenes that should not retain gameplay state.
Resource cleanup and the audio leak trap
The most common bug in shmup scene transitions is audio that survives a scene change. A music track that was started in the gameplay scene continues playing through a different audio player object in the title screen scene, running underneath the title music and producing a layered mess. This happens when audio players are not parented to scene nodes and are created as persistent singletons.
The correct approach is explicit audio lifecycle management during every scene transition: before unloading a scene, stop and free all audio players that belong to it. The audio bus architecture described in audio and music integration helps here because all combat audio is bus-routed and can be silenced by stopping the bus rather than hunting individual players.
Similar leaks occur with timers, tween animations, and coroutines. Any time-based process started in a scene should be explicitly cancelled on scene exit. The cleanest implementation registers all active timers and tweens with the scene on creation and cancels them all in the scene's exit handler rather than tracking them individually across the codebase.
Pause implementation details
What should the pause state freeze? The answer depends on whether you want visual activity to continue during pause (animated backgrounds, particle systems winding down) or a complete halt. The simplest implementation freezes the entire game time scale to zero, which stops everything: physics, animations, particle emission, bullet movement. This is correct for most shmups and requires no special handling of individual systems.
Edge cases that need explicit handling even with a frozen time scale:
- The pause menu UI must still process input — it needs to be flagged as pause-exempt from the time scale
- Music should continue playing (pausing music on game-pause is disorienting unless the game specifically uses silence as a mood effect)
- Any UI animations, such as a pulsing cursor in the pause menu, should also be pause-exempt
The continue countdown
The continue countdown (typically 10 seconds, counting down while the player decides whether to continue) is a simple but frequently broken system. Common failures:
- The countdown timer does not stop when the player selects continue or game over before it expires, resulting in a delayed second game-over transition after the run has already resumed
- Pressing any button during the countdown registers as both a menu confirmation and a gameplay input when the gameplay scene resumes
- The timer continues running while a controller is disconnecting and reconnecting
All three of these failures share the same root cause: the countdown timer is not owned by the state machine and is not destroyed when the state it belongs to exits. Ensuring every timer is scoped to its state and destroyed on state exit eliminates this entire class of bug.
Testing the state graph
Automated testing of scene transitions is possible but requires effort; manual testing is practical and important. The key test cases are the boundary conditions: transitions that happen at unusual times, such as the player dying at the exact frame the boss dies, or pressing pause during the death animation, or reaching game-over during the boss intro cutscene. These edge cases expose state machine violations where two states are simultaneously active or a transition fires during a period when transitions should be locked.
Building a debug overlay that always shows the current game state makes these edge cases much easier to catch during development and saves significant debugging time later.