Performance Patterns for Juice: Keeping It Fast
Object pooling for particles, GPU instancing for VFX, audio bus management, and frame-budget-aware LOD for feedback systems. How to maintain 60fps while running rich feedback - with profiler patterns and common performance pitfalls.
28 April 2026 ยท 4 min read
Game feel effects can kill frame rate if implemented carelessly. High-frequency feedback -- gunfire, impact particles, floating damage text -- triggered many times per second creates garbage collection spikes and CPU/GPU overhead that can turn a polished game into a stuttering one. The good news is that the same patterns that keep juice fast also make it easier to tune and maintain.
Object Pooling: The Non-Negotiable
Never use Instantiate/Destroy (or equivalent engine calls) for frequently triggered objects. Every particle burst, floating text object, projectile, and debris chunk should be pre-allocated at scene load and recycled from a pool. Object pooling eliminates the two most expensive operations in real-time juice: memory allocation and garbage collection.
A minimal Unity pool looks like this: pre-allocate a fixed number of objects at start, deactivate them, store them in a Queue. On Get(), dequeue and activate. On Return(), deactivate and enqueue. Add a fallback Instantiate() for when the pool is exhausted (but size the pool to make this rare). The entire pattern is about 30 lines of code and eliminates allocations for every juice effect that uses it.
Pool sizing: profile your heaviest 1-second sequence (typically a screen full of enemies being hit simultaneously) and count how many of each pooled type fire simultaneously. Pre-allocate 1.5x that number. This gives headroom without wasting memory.
ScriptableObject Juice Profiles
Define juice parameters as ScriptableObjects rather than hardcoded values. A JuiceProfile asset contains: shake amplitude, shake duration, shake frequency, particle count, particle speed range, particle lifetime, hit stop frames, audio clip reference, pitch randomisation range, rumble intensity, rumble duration. All events in the same tier reference the same profile. Changing the HeavyHit profile changes every heavy hit in the game simultaneously.
This pattern solves three problems at once. It enables designer iteration without code changes. It enforces consistency -- all Heavy tier hits share the same profile, so they cannot drift apart through per-event tweaking. And it enables runtime accessibility overrides: multiply all profile values by a global scalar from the player's settings before the effect fires. One global change adjusts every effect.
Performance Budget per Event Tier
Define explicit performance budgets for each tier before building effects. A reasonable baseline: Light tier -- 8 to 16 particles, 1 pooled SFX, minimal camera shake, no post-processing. Medium tier -- 16 to 40 particles, 1 to 2 SFX, light shake, vignette flash. Heavy tier -- 40 to 80 particles, 2 to 3 SFX, medium shake, chromatic aberration pulse. Epic tier -- 80 to 150 particles, 3 to 4 layered SFX, large shake, full post-processing event.
These are starting budgets, not absolute limits. Profile on your target hardware and adjust. The important constraint is that tiers are ordered: Heavy must use more resources than Medium, and Medium more than Light. If your Medium events use 200 particles, your Heavy events will need 400 to read as different, and your frame budget will collapse when multiple Heavy events fire simultaneously.
Camera Shake Without Physics
Camera shake implemented via physics (adding forces, spring systems) is more expensive than it needs to be. Use a dedicated shake controller that offsets the camera transform directly using a noise function or decaying sinusoid. Apply the offset in LateUpdate() after all other camera logic has run. This approach has near-zero CPU overhead, is fully deterministic, and is easy to pool and stack.
Stackable shakes: when multiple events fire close together (multiple hits in one frame), do not reset the current shake -- add the new amplitude to the existing shake state and let them decay together. Resetting on each new event creates a stuttering, choppy feel. Addition creates a cumulative crescendo.
Audio Performance
Pool AudioSource components exactly like game objects. Triggering PlayOneShot() on a new AudioSource every time creates garbage. Pre-allocate a pool of AudioSource components, play through them in rotation, and return them to the pool when the clip finishes.
Limit simultaneous sounds: cap how many instances of the same SFX can play simultaneously. Two overlapping hit sounds sound rich. Twenty overlapping hit sounds distort and peak out. A simple global mixer group limiter or a per-SFX instance cap prevents audio overload during heavy combat sequences.
Profiling First, Optimising Second
The performance patterns in this article are proven wins that apply to most games. But do not apply them blindly before profiling. Profile first -- find the actual bottleneck in your specific game. Object pooling is the right fix if your bottleneck is GC allocation. Tier budgets are the right fix if your bottleneck is overdraw or particle CPU cost. Audio pooling is the right fix if your bottleneck is AudioSource instantiation. Profile, then apply the pattern that matches the actual problem.
Target hardware matters enormously. A particle budget that runs comfortably on a PC will collapse on mobile. Define your minimum specification early and profile against it throughout development. Discovering your juice effects are too expensive at pre-release means either cutting effects or cutting the target platform. Both are avoidable with consistent profiling.
Part of a series
Systems & Process