Unity Mobile Optimization: Hit 60 FPS on Android and iOS

Illustration: a smartphone with a speedometer gauge and performance graph

A game that runs at 200 FPS on your desktop can crawl at 20 FPS on a mid-range phone, then drop further once the device warms up. Unity mobile optimization is less about clever tricks and more about working through a checklist: set the right frame rate, configure URP for mobile, compress textures properly, cut overdraw, batch draw calls, keep physics and scripts lean, and design for thermal throttling. This guide covers each step with settings and code that apply to Unity 6 and URP 17 on Android and iOS.

Measure on a Real Device First

The editor tells you almost nothing about mobile performance. Your desktop GPU, CPU and memory bandwidth are many times faster, and the editor itself adds overhead. Always profile a Development Build on an actual phone:

  1. In File > Build Profiles, pick Android or iOS and tick Development Build and Autoconnect Profiler.
  2. Build and run. The Profiler connects over USB (or Wi-Fi on the same network).
  3. Look at the CPU timeline. If the main thread waits on Gfx.WaitForPresent or similar, you are GPU bound. If script, physics or rendering setup takes most of the frame, you are CPU bound.

Pick a low-end target phone and test on it regularly. If your game runs well there, it runs well almost everywhere. Our Profiler beginner’s guide explains the timeline, and the performance budgets post shows how to split your 16.6 ms frame.

For a GPU breakdown, use the vendor tools: Android GPU Inspector, Arm Performance Studio, Snapdragon Profiler, or Xcode’s GPU frame capture on iOS.

Set the Target Frame Rate

On mobile, Unity caps the frame rate at 30 by default. Many people “optimize” for days without realising the game was simply capped. Set it explicitly at startup:

using UnityEngine;

public static class FrameRateSetup
{
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    static void Init()
    {
        QualitySettings.vSyncCount = 0;          // mobile ignores vSync, use targetFrameRate
        Application.targetFrameRate = 60;
    }
}

For phones with 90 Hz or 120 Hz screens, you can read the display rate with Screen.currentResolution.refreshRateRatio.value and target that, but higher rates cost far more battery and heat. A common approach is 60 for gameplay and 30 for menus, or offering a battery-saver option.

Stable is better than high. A locked 30 FPS feels smoother than a game bouncing between 40 and 60.

URP Settings for Mobile

URP is the right pipeline for mobile. Unity 6’s URP templates include a Mobile renderer and pipeline asset; make sure the quality level used on mobile points at it. In the URP Asset:

  • Render Scale: modern phones have huge resolutions. A render scale of 0.75 to 0.85 is often unnoticeable and saves a lot of fill rate. Keep UI at native resolution (Screen Space Overlay canvases are unaffected).
  • HDR: turn off unless you really need it. It doubles the bandwidth of the colour buffer.
  • Anti-aliasing (MSAA): 2x or 4x MSAA is relatively cheap on tile-based mobile GPUs. Avoid post-process AA like TAA on low-end devices.
  • Depth Texture and Opaque Texture: turn both off unless a shader needs them. Each forces an extra copy.
  • Shadows: one cascade, short max distance (20 to 40 m), low resolution, soft shadows off on low tiers. Consider baked lighting and blob shadows.
  • Additional lights: per-vertex or disabled, and keep the per-object limit low. Baked lighting is far cheaper than realtime on phones.
  • Post-processing: bloom and colour grading are affordable at reduced quality. Skip depth of field, motion blur and SSAO on mobile.

Create two or three quality levels (Low, Medium, High) each with its own URP asset, and pick one at startup based on SystemInfo.systemMemorySize or a device list.

Texture Compression and Memory

Uncompressed textures waste memory and bandwidth, and bandwidth is what heats phones up.

  • Format: use ASTC on both Android and iOS. Set it in the texture import settings per platform, or set the default in Build Profiles for Android. ASTC 6×6 is a good default; 4×4 for UI or hero art, 8×8 for large backgrounds.
  • Max Size: a 4096 texture on an object that covers 200 pixels of screen is pure waste. Override Max Size per platform to 1024 or 512 for most assets.
  • Mipmaps: enable for 3D textures (reduces bandwidth at distance), disable for UI sprites.
  • Read/Write: leave off unless a script reads pixels. On, it keeps a CPU copy and doubles memory.
  • Atlases: pack UI and 2D sprites into Sprite Atlases so they batch together.

Audio counts too. Set long music clips to Streaming and short sound effects to Decompress On Load with Vorbis or ADPCM compression. Use the Memory Profiler package to find what is really eating RAM. Phones kill apps that use too much memory, which often looks like a random crash.

Overdraw, Transparency and UI

Overdraw is when the GPU shades the same pixel several times in one frame. It is the number one GPU killer on mobile, and transparency is the main cause, because transparent pixels can’t be rejected by the depth test.

  • Use the Rendering Debugger (Window > Analysis > Rendering Debugger) and set the overdraw mode to visualise hot spots in the Scene view.
  • Large semi-transparent particles are a disaster. Use fewer, smaller particles and opaque or alpha-clipped materials where possible. The Particle System is the safer mobile choice over VFX Graph, which needs compute shader support.
  • Full-screen UI panels with transparent backgrounds stacked on top of each other add up fast. Disable the Canvas or GameObject of hidden screens instead of setting alpha to zero.
  • Split UI into multiple canvases: static elements on one, frequently changing elements (timers, health) on another, so a changing number doesn’t rebuild the whole UI.
  • Turn off Raycast Target on images and text that never need clicks.

Draw Calls and Batching

Each draw call costs CPU time on the render thread, and mobile CPUs are slow. Aim for a few hundred draw calls at most on mid-range phones, which you can check in the Frame Debugger.

  • SRP Batcher: on by default in URP. It doesn’t reduce draw calls but makes them much cheaper, as long as your shaders are SRP Batcher compatible (all URP Lit/Unlit and Shader Graph shaders are).
  • GPU Resident Drawer: new in Unity 6 and very effective for lots of identical meshes. It requires the Forward+ path and works on Vulkan and Metal; test it on your target devices, as gains vary.
  • Static batching: mark non-moving scenery Static. It costs memory but cuts draw calls.
  • GPU instancing: enable on materials for repeated objects like grass, rocks or coins, when not using the SRP Batcher path for them.
  • Fewer materials: share materials and use texture atlases. Every unique material breaks batches.
  • LOD Groups and culling: lower poly models at distance, and a sensible camera far clip plane.

🚀 Shortcut the optimisation grind

Mesh combiners, LOD generators, texture atlasing tools and mobile-friendly shader packs can save weeks of manual work. We picked the Asset Store tools that actually earn their price.

See our Asset Store picks →

Physics, Scripts and Garbage

  • Fixed Timestep: the default 0.02 (50 Hz) is fine. Don’t lower it to 0.01 for “smoother physics” on mobile; that doubles physics cost. Use interpolation instead.
  • Collision matrix: disable layer pairs that never interact. Edit it in Project Settings > Physics.
  • Primitive colliders: prefer box, sphere and capsule colliders over mesh colliders.
  • Avoid per-frame allocations: string concatenation in Update, LINQ, GetComponent every frame, and creating new lists all generate garbage, and the garbage collector causes hitches. Use non-allocating APIs like Physics.RaycastNonAlloc and cache component references.
  • Pool objects: Instantiate and Destroy are expensive. Reuse bullets, enemies and effects with UnityEngine.Pool.ObjectPool, as covered in our object pooling guide.
  • Incremental GC: keep it enabled in Player Settings so collections are spread across frames.
  • IL2CPP: use IL2CPP with ARM64 for release builds. It is faster than Mono and required for the Play Store’s 64-bit rule and for iOS.

Thermal Throttling and Battery

Phones have no fans. When they get hot, the OS lowers CPU and GPU clocks, and your frame rate can drop by a third after ten minutes of play. Testing for two minutes won’t reveal it.

  • Leave headroom: if your frame takes 15 ms out of a 16.6 ms budget on a cold phone, it will miss frames when warm. Aim for 10 to 12 ms.
  • Cap the frame rate: a game that renders at 60 when it only needs 30 wastes battery and heats up faster.
  • Adaptive Performance: the Adaptive Performance package (with the Samsung or Android provider) reports thermal state and lets you scale quality automatically, for example reducing render scale or shadow distance when the device warms up.
  • Lower update rates for non-critical systems: AI decisions, UI updates and pathfinding rarely need to run every frame.
  • Use On-Demand Rendering in menus: OnDemandRendering.renderFrameInterval = 2; renders every second frame while input stays responsive.

Run a 20-minute soak test on your low-end device with the Profiler attached and watch whether frame time creeps up.

Troubleshooting

Game is locked at exactly 30 FPS

You haven’t set Application.targetFrameRate. Mobile defaults to 30.

FPS fine at first, drops after a few minutes

Thermal throttling. Reduce GPU load, cap the frame rate and leave more headroom.

Regular stutters every few seconds

Garbage collection. Look for GC.Alloc in the Profiler’s CPU hierarchy and remove per-frame allocations.

App crashes on older phones only

Usually out of memory. Check texture sizes and formats with the Memory Profiler and lower Max Size overrides.

Everything looks blurry after lowering render scale

Raise it slightly, or use FSR upscaling in the URP asset’s upscaling filter. Make sure UI uses an overlay canvas so it stays sharp.

Pink materials on device

A shader isn’t included in the build or isn’t supported by the graphics API. See our pink materials fix.

FAQ

Is 30 FPS acceptable for a mobile game?

Yes, for many genres like puzzle and strategy games. Action games and anything with fast scrolling feel much better at 60.

Vulkan or OpenGL ES on Android?

Vulkan is the default and usually faster on modern devices. Keep OpenGL ES 3 as a fallback for older hardware.

Should I use Forward or Forward+ on mobile?

Forward is fine for few lights. Forward+ handles many lights better and is required for the GPU Resident Drawer.

How many triangles can a phone handle?

Mid-range phones comfortably draw a few hundred thousand per frame. Draw calls, overdraw and shaders usually matter more than raw triangle count.

Does the Unity splash screen affect performance?

No, only load time at startup.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *