Game Programming · June 30, 2026
Frame Pacing and Performance Profiling in Shmups: Finding and Fixing Hitches
Average framerate is a misleading metric for shmup performance. A game that averages 60 fps but drops to 38 fps for six frames every time a large enemy explodes is not a 60 fps game in any sense that the player experiences. Those six frames of dropped frame time are exactly the moments when the player is reacting to the explosion and repositioning — the worst possible moment for visual stutter. Consistent frame pacing, measured as frame-time variance rather than average fps, is the metric that matters.
Published June 30, 2026
Understanding frame time vs framerate
A 60 fps target means 16.67ms per frame. Frame time is the duration of a single frame; framerate is derived from it. Frame pacing refers to how consistent the frame-time intervals are. Perfect 60 fps has every frame exactly 16.67ms. A game with poor pacing might deliver: 16ms, 17ms, 16ms, 34ms (dropped frame), 15ms, 16ms... and average 60 fps over a second despite the visible stutter during the 34ms frame.
The practical implication: monitor both framerate and frame time in your debug build. A sudden spike in frame time is a hitch, regardless of what the average fps shows. Aim to keep frame time variance below 3ms in normal gameplay, with a maximum allowed spike of 6–8ms during known expensive events (large explosions, wave spawns).
Where shmup hitches come from
Profiling a shmup typically reveals hitches in one of five categories:
- Particle system burst: Spawning 80 particles simultaneously for a large explosion creates a one-frame cost spike for particle system setup, initial velocity calculation, and the first emission step. Spread the spawn across two to three frames using a deferred emitter if bursts exceed 40–50 particles.
- Garbage collection pause: Allocating bullet objects on every spawn triggers GC pauses when the allocator needs to reclaim memory. Object pooling eliminates this entirely — see the object pooling guide for implementation details. The hitch pattern for GC is irregular and difficult to predict, which makes it particularly disruptive during dense sections.
- Collision broadphase burst: When a large enemy dies and simultaneously spawns ten small sub-enemies plus twelve pickups, the physics engine re-runs its broadphase spatial query with a larger entity count in the same frame. Stagger the spawns across two to three frames using a queue.
- Scene resource loading: Loading audio files, textures, or scene files during gameplay causes IO-bound hitches. All resources used in a stage must be preloaded at the stage loading screen before gameplay begins, never loaded on demand during play.
- Draw call batching break: A single sprite that is not using the same material, shader, or texture atlas as its neighbours breaks the sprite batcher and causes an extra draw call. In a scene with hundreds of bullets, unbatched sprites multiply draw calls significantly. Keep all bullets that share the same visual on the same atlas and material.
Using the engine profiler
Most game engines include a built-in frame profiler. Godot's profiler shows per-function CPU time per frame. Unity's Profiler shows similar data. The workflow for finding hitches:
- Run a gameplay session that includes the worst-case scenario: a boss with multiple simultaneous attacks, a large explosion cluster, and a wave transition.
- Record the profiler session or enable frame-time logging to a file.
- Identify the frames with the highest frame time. In most profilers, these appear as tall spikes in the frame time timeline.
- Click into a spike frame and read the call tree. The function consuming the most time in that frame is the first target for optimization.
- Fix the highest-cost function, re-profile, and repeat until frame time spikes are within the acceptable window.
Budgeting explosions
Large boss explosions are guaranteed to be frame time expensive and unavoidable — they are spectacle moments the game needs. The correct approach is to budget them explicitly rather than trying to make them cheap.
Define an explosion budget per frame: maximum particles spawned this frame from all explosion sources combined. A particle spawn queue holds all pending explosion requests and draws from them at the budget cap each frame until the queue is empty. The explosion may take two or three frames to fully spawn all particles, but no single frame exceeds budget. The visual impact is identical at 60 fps; the stutter is eliminated.
Delta-time normalisation as a fallback
Even well-optimized shmups will occasionally drop frames on low-end hardware. Delta-time normalisation ensures that game logic advances by the correct amount of simulated time regardless of how long the frame actually took. All movement, timer, and physics updates are multiplied by the delta time since the last frame rather than assuming a fixed frame duration.
Delta-time normalisation does not fix frame stutter — the player will still see the stutter visually. What it prevents is gameplay consequences from the stutter: bullets teleporting, hitboxes skipping, timers advancing incorrectly. A game that maintains correct simulation across dropped frames is far more playable on target-minimum hardware than one that freezes and then lurches forward.
The one caveat: delta-time normalisation and fixed timestep physics are in tension. Physics simulations designed for fixed timestep (as recommended in the fixed timestep guide) should remain on fixed steps; delta-time normalisation applies to rendering and non-physics game logic only.