How We Optimize Unity Games for Performance
60 FPS is table stakes for shooters. Players will refund games that stutter during firefights. Here's how we actually approach performance optimization in Unity FPS games.
Step Zero: Profile Before Optimizing
The most common optimization mistake is guessing. Someone decides "the shaders are too heavy" and spends a week rewriting them, only to discover the problem was garbage collection spikes from string allocations.
We start every optimization effort by profiling. Unity's built-in profiler shows where time is spent each frame. The frame debugger shows exactly what the GPU is doing. Memory profiler catches allocation patterns. We don't change code until we know what's actually slow.
The Questions We Answer
- CPU bound or GPU bound?
- If CPU, main thread or render thread?
- Consistent low framerate or spikes/hitches?
- Does it correlate with specific game events?
The Usual Suspects in FPS Games
Draw Calls
Every time Unity tells the GPU to draw something, there's overhead. An FPS with hundreds of individual objects - weapons, bullets, enemies, environmental props - can easily hit draw call limits on weaker hardware.
Fixes: Static batching for non-moving geometry. GPU instancing for repeated objects (trees, debris, shell casings). SRP Batcher with HDRP/URP. Aggressive LODs to reduce distant geometry. The goal is typically under 1000 draw calls for mid-range target hardware.
Overdraw
In a corridor shooter, you might render the same pixel 10+ times as walls, characters, particles, and UI stack on top of each other. The GPU does the work for each layer even though only the top one is visible.
Fixes: Occluder geometry to cull hidden rooms. Careful particle effect design (fewer overlapping particles, shorter lifetimes). Sorting objects front-to-back when possible. Reducing alpha-blended effects.
Physics
Every rigidbody and collider costs. A scene with hundreds of physics objects all interacting - bullet casings bouncing, ragdolls colliding, destructible props - can bottleneck the CPU.
Fixes: Layer matrix configuration to prevent unnecessary collision checks (bullets don't need to collide with particles). Simplified colliders instead of mesh colliders. Pooling physics objects and properly deactivating/sleeping inactive ones. Consider replacing physics with cheaper approximations for non-critical elements.
The Garbage Collection Problem
Unity's garbage collector stops the world when it runs. In a 60 FPS game, you have 16.6ms per frame. A GC spike of 20ms means a visible hitch. In a shooter, that hitch might cost a player their life.
The only fix is generating less garbage. The sources we look for:
- String operations in gameplay code. Every string concatenation allocates. Use StringBuilder or cached strings.
- LINQ queries in hot paths. They allocate enumerators. Replace with explicit loops.
- Boxing when value types get used as objects. Common with generic collections.
- GetComponent calls that allocate. Cache references at startup.
- Event handlers using lambdas that capture variables.
We use Memory Profiler to track allocations frame by frame, then eliminate them systematically. The goal is zero allocations during gameplay.
HDRP vs URP
HDRP produces beautiful visuals but has higher baseline cost. We use it for PC-focused projects targeting mid-to-high-end hardware. TOGETHER: OR WE DIE uses HDRP because the visual target justifies it.
URP is lighter weight and better suited for mobile, Switch, and lower-end PC targets. MATH FPS uses URP because it needs to run on a wide hardware range.
The choice affects everything downstream: shader complexity, post-processing options, lighting approach. It's not something you can easily change mid-project.
Platform-Specific Optimization
PC optimization is different from console optimization. On PC, you're targeting a range of hardware and often providing quality settings. On console, you're targeting fixed hardware and expected to hit specific performance targets (30 or 60 FPS) without stutter.
Mobile is another category entirely. GPU architectures are different (tile-based renderers), thermal constraints matter (performance drops as devices heat up), and memory is much more limited.
We define target hardware early and test on that hardware regularly. An optimization that helps on a 4090 might not matter on a GTX 1060, and vice versa.
The best optimization is not rendering things players can't see. Occlusion culling, LOD systems, and careful level design that limits sight lines will do more than shader micro-optimizations.
When to Optimize
We check performance baselines weekly throughout development. A 5% regression each week compounds into disaster. Catching problems early is cheaper than fixing them at the end.
Major optimization passes happen at three points: after core systems are in (to establish baselines), during alpha (to ensure playability on target hardware), and during beta (final polish and edge cases).
Struggling with Performance?
If your Unity game isn't hitting frame rate targets, we can help. Send us a build and we'll identify the bottlenecks.
REQUEST ASSESSMENT