Every Unity developer has had the same sinking feeling: the game runs beautifully in the Editor on a development machine, but the moment it hits a real mobile device, frame rates crater, the phone overheats, and the battery drains in minutes.
Mobile optimization is not something you bolt on at the end. It is a discipline that needs to run through every stage of development. But even with the best intentions, performance issues creep in. Draw calls accumulate. Textures bloat. Garbage collection spikes appear in the profiler like clockwork.
At Ocean View Games, mobile performance is core to what we do. From our work on Domi Online - rendering 10,000+ GPU-instanced trees with minimal draw calls - to our founder's experience optimizing RuneScape for mobile hardware during his tenure at Jagex, we have developed a systematic approach to mobile optimization that we apply to every project.
This post is the checklist we use internally. It is practical, actionable, and organized by the order you should tackle things - starting with profiling (because you should never optimize blind) and working through the most impactful areas.
Step 0: Profile First, Optimize Second
This is the most important rule on this entire list. Never optimize based on intuition. Profile on real hardware, identify the actual bottleneck, and fix that specific problem.
The Profiling Toolkit
- Unity Profiler - Your primary tool. Use the CPU, GPU, Memory, and Rendering modules. Connect to your device via USB or WiFi for real hardware data. Editor profiling gives misleading results.
- Frame Debugger - Shows exactly what Unity is rendering each frame and in what order. Essential for identifying redundant draw calls and overdraw.
- Memory Profiler - A separate package (install from Package Manager) that provides detailed snapshots of managed and native memory. Use it to track texture memory, mesh data, and AudioClip allocations.
- Xcode Instruments (iOS) - Metal System Trace and Allocations instruments give GPU-level detail that Unity's profiler cannot.
- Android GPU Inspector / Snapdragon Profiler - For Android-specific GPU profiling, especially on Qualcomm chipsets.
What to Look For
Before you touch any code, answer these questions:
- Are you CPU-bound or GPU-bound? The Unity Profiler's timeline view shows this clearly. If the CPU frame time exceeds the GPU frame time, your bottleneck is scripts, physics, or animation - not rendering.
- Where are the spikes? Smooth 30fps with periodic 200ms spikes is a different problem than a consistent 20fps. Spikes usually indicate garbage collection or expensive one-off operations.
- What is the memory footprint? Mobile devices have hard memory limits. Exceeding them causes an instant, silent crash with no error log.
Key Takeaway: Always profile on the lowest-spec device you intend to support. A game that runs at 60fps on an iPhone 15 Pro and crashes on a 3-year-old Android is not optimized - it is untested.
Rendering and Draw Calls
Rendering is the most common bottleneck in mobile Unity games. Every draw call has CPU overhead, and mobile GPUs have strict limits on what they can process per frame.
The Draw Call Checklist
[ ] Enable Static Batching for all non-moving geometry. Mark objects as "Static" in the Inspector. Unity will combine meshes that share materials into single draw calls at build time.
[ ] Enable Dynamic Batching for small, moving objects (under 300 vertices). Note that dynamic batching has CPU overhead - profile to confirm it is actually helping.
[ ] Use GPU Instancing for repeated objects (trees, rocks, grass, props). This was critical for Domi Online, where we needed to render dense, interactive forests. We implemented a system that rendered 10,000+ trees with GPU Instancing, swapping to interactive "Stump" prefabs only when a player interacted with a specific tree.
[ ] Use Sprite Atlases for 2D games. Every separate sprite texture is a potential draw call break. Pack sprites into atlases using Unity's Sprite Atlas tool.
[ ] Minimize material variety. Each unique material requires a separate draw call. Use texture atlases to combine multiple textures into a single material where possible.
[ ] Use the SRP Batcher (if using URP or HDRP). The Scriptable Render Pipeline Batcher reduces the CPU cost of draw calls by batching shader property uploads. Ensure your shaders are SRP Batcher compatible.
[ ] Check the Frame Debugger. Look for draw calls that are rendering invisible objects, duplicate passes, or unnecessary post-processing effects.
Overdraw
Overdraw occurs when the GPU renders pixels that are immediately covered by other pixels. On mobile GPUs with limited fill rate, this is a serious performance drain.
- [ ] Use the Scene View's overdraw visualization mode to identify problem areas.
- [ ] Reduce transparent/alpha-blended objects. Particles are a common culprit - reduce particle counts and sizes.
- [ ] Avoid overlapping UI elements with transparency. Flatten your UI hierarchy where possible.
Texture Optimization
Textures are typically the single largest contributor to both memory usage and download size. Getting texture settings right has an outsized impact on mobile performance.
The Texture Checklist
[ ] Use ASTC compression for both iOS and Android. ASTC is the modern standard for mobile texture compression, offering better quality-per-bit than ETC2 or PVRTC. Unity supports ASTC on both platforms.
[ ] Set appropriate Max Size. Does a background texture really need to be 2048x2048 on a 6-inch phone screen? In many cases, 512x512 or 1024x1024 is indistinguishable to the player and uses 75% less memory.
[ ] Disable Read/Write Enabled on textures that do not need CPU access. When enabled, Unity keeps a copy of the texture in CPU memory in addition to GPU memory - doubling the memory cost.
[ ] Use mipmaps for 3D textures. Mipmaps slightly increase memory usage (by ~33%) but dramatically improve rendering performance and visual quality for objects at varying distances. Disable mipmaps only for UI textures and sprites that are always rendered at the same size.
[ ] Use Power-of-Two dimensions. Non-power-of-two (NPOT) textures cannot be compressed on many mobile GPUs and may be padded to the next power of two anyway, wasting memory.
[ ] Audit texture memory. Use the Memory Profiler to see exactly how much memory each texture consumes. Sort by size and question whether the largest textures justify their resolution.
Memory Management and Garbage Collection
Garbage collection (GC) spikes are the most common cause of stuttering in Unity mobile games. When the managed heap fills up, the Mono or IL2CPP runtime pauses the game to clean up unused allocations. On mobile hardware, these pauses are longer and more noticeable.
The Memory Checklist
[ ] Eliminate per-frame allocations. Use the Profiler's GC Alloc column to identify scripts that allocate memory every frame. Common offenders: string concatenation, LINQ queries,
foreachon certain collection types, boxing value types, andGetComponent<T>()calls.[ ] Cache component references. Call
GetComponent<T>()inAwake()orStart()and store the result. Never call it inUpdate().[ ] Use object pooling. Instead of instantiating and destroying objects (bullets, particles, enemies), maintain a pool of pre-instantiated objects and recycle them. This eliminates both instantiation cost and GC pressure.
[ ] Pre-allocate collections. If you know a
List<T>will hold 100 items, initialize it withnew List<T>(100)to avoid repeated internal array resizing.[ ] Use
StringBuilderfor any string operations in hot paths. String concatenation with+creates a new string object every time.[ ] Avoid closures and lambdas in hot paths. They generate hidden allocations. Use method references or pre-allocated delegates instead.
[ ] Use IL2CPP, not Mono. IL2CPP compiles C# to C++ ahead of time, producing faster code with better memory characteristics. It is required for iOS and strongly recommended for Android.
Key Takeaway: Every allocation in a frame-sensitive path is a future GC spike. The goal is zero allocations per frame in your core game loop. Use the Unity Profiler's GC Alloc column as your scorecard.
Shader Optimization
Shaders are often overlooked during optimization because their performance impact is not immediately visible in the CPU profiler. But on mobile GPUs, complex fragment shaders and unnecessary passes can halve your frame rate.
The Shader Checklist
[ ] Use URP (Universal Render Pipeline). URP is designed for mobile and mid-range hardware. It is significantly more efficient than the Built-in Render Pipeline for mobile targets.
[ ] Use the Lit shader sparingly. The URP Lit shader is versatile but expensive. For objects that do not need dynamic lighting (UI elements, skyboxes, distant terrain), use Unlit or Simple Lit shaders.
[ ] Minimize shader variants. Every keyword combination generates a separate shader variant. Disable unused features (emission, detail maps, specular highlights) in your materials to reduce variant count and build time.
[ ] Avoid complex fragment operations. Per-pixel calculations like real-time shadows, multiple light sources, and complex normal mapping are expensive on mobile GPUs. Use baked lighting where possible.
[ ] Bake lighting. For static environments, baked lightmaps provide beautiful lighting at nearly zero runtime GPU cost. Use Light Probes for dynamic objects moving through baked scenes.
[ ] Reduce real-time shadow usage. Real-time shadows are one of the most expensive rendering features on mobile. Consider blob shadows, projected shadows, or baked shadow maps as cheaper alternatives.
Physics and Update Loops
Physics calculations and inefficient update loops are common CPU bottlenecks, especially in games with many active objects.
The Physics Checklist
[ ] Reduce Fixed Timestep frequency. The default
fixedDeltaTimeof 0.02 (50 physics updates per second) may be unnecessary for your game. For turn-based or slower-paced games, 0.04 (25Hz) or even lower can save significant CPU time.[ ] Use simplified collision meshes. Never use Mesh Colliders on complex models. Use primitive colliders (Box, Sphere, Capsule) or simplified convex meshes.
[ ] Disable unnecessary Rigidbodies. Objects that do not need physics simulation should not have Rigidbody components.
[ ] Use layer-based collision matrix. In Project Settings > Physics, disable collision checking between layers that should never interact. This prevents the physics engine from wasting time on impossible collisions.
The Script Checklist
[ ] Avoid empty
Update()andFixedUpdate()methods. Unity calls these every frame even if they are empty. Remove them from any MonoBehaviour that does not use them.[ ] Use coroutines or timers for periodic checks. If you only need to check something every 0.5 seconds, do not check it every frame. Use
InvokeRepeating, coroutines withWaitForSeconds, or a manual timer.[ ] Minimize
FindObjectOfTypeand similar searches. These scan the entire scene hierarchy. Cache references at startup or use event-driven patterns.
Frame Rate Strategy
Not every game needs 60fps. Choosing the right target frame rate is a critical optimization decision that affects battery life, thermal performance, and development complexity.
30fps vs 60fps
- 60fps - Required for action games, competitive multiplayer, and any game where input responsiveness is critical. Demands aggressive optimization across all systems.
- 30fps - Acceptable for turn-based strategy, puzzles, narrative games, and educational titles. Halves the rendering budget, dramatically reduces heat generation, and extends battery life.
Adaptive Frame Rate
For games that have both intense and calm moments, consider an adaptive frame rate strategy:
- Run at 60fps during combat or action sequences.
- Drop to 30fps during menus, inventory screens, and idle states.
- Monitor device temperature and reduce the target if the phone is throttling.
Unity's Application.targetFrameRate makes this straightforward to implement. Use it alongside QualitySettings to adjust visual fidelity based on device capability.
Tier-Based Quality Settings
Not all phones are equal. We implement a device tier system on most of our mobile development projects:
- High tier (flagship devices) - Full resolution, 60fps, high-quality shadows, post-processing.
- Mid tier (2-3 year old devices) - Reduced resolution, 30fps, simplified shadows, minimal post-processing.
- Low tier (budget devices) - Further reduced resolution, 30fps, no real-time shadows, no post-processing.
Detect the device at startup using SystemInfo.graphicsDeviceName, available RAM, and processor core count, then automatically select the appropriate quality tier.
The Build and Deployment Checklist
Before shipping any mobile build, verify these settings:
- [ ] Scripting Backend: IL2CPP (not Mono). Better performance, required for iOS, and provides a layer of code obfuscation.
- [ ] Strip Engine Code: Enabled. Removes unused Unity modules from the build, reducing file size.
- [ ] Managed Stripping Level: Medium or High. Strips unused managed code. Test thoroughly, as aggressive stripping can remove code accessed only via reflection.
- [ ] Target Architecture: ARM64. 32-bit support is no longer required by iOS and is being phased out on Android.
- [ ] Graphics API: Vulkan (Android), Metal (iOS). These modern APIs provide better performance than OpenGL ES.
- [ ] Texture Compression: ASTC. As discussed above. Verify in Build Settings per platform.
- [ ] Remove debug logging.
Debug.Log()calls have measurable overhead on mobile. Use conditional compilation (#if UNITY_EDITOR) or strip them entirely for release builds.
Putting It All Together
Optimization is iterative. Profile, identify the biggest bottleneck, fix it, and profile again. Resist the temptation to optimize everything at once - focus on the changes that yield the largest measurable improvement.
The order of priority we follow on most mobile projects:
- Profile on real hardware to identify the actual bottleneck.
- Reduce draw calls through batching, instancing, and material consolidation.
- Optimize textures for mobile compression and appropriate resolution.
- Eliminate GC allocations in the game loop.
- Simplify shaders and lighting for mobile GPU constraints.
- Tune physics and script frequency to reduce CPU load.
- Set appropriate frame rate targets with tier-based quality settings.
- Verify build settings before every release.
If your Unity mobile project is struggling with performance and you need expert help, our performance optimization team can audit your project, identify the critical bottlenecks, and implement targeted fixes. We bring the same rigour we applied to RuneScape Mobile and Domi Online to every engagement.
Related Reading
- Performance Optimization Services
- Mobile Game Development Services
- Unity Game Development Services
- Domi Online MMORPG Case Study
- Empires Rise - Mobile Strategy Case Study
- Game Development Cost Estimator - Factor optimisation time into your project budget.
- Game QA Best Practices - The full testing methodology that follows optimisation.
























