DEV Community

unity source code
unity source code

Posted on

Unity Mobile Performance: 7 Optimizations That Actually Move the Needle

If you've shipped a Unity mobile game, you've probably had this experience: everything runs at a buttery 60fps in the Editor, you build to a real Android device, and suddenly you're staring at 22fps and a phone that's warm enough to fry an egg on.

Mobile performance in Unity isn't really about one big fix. It's usually five or six smaller things stacking up — none of them individually dramatic, but together the difference between a game that feels professional and one that gets one-star reviews for "laggy gameplay." I've pulled together the optimizations that consistently make the biggest difference, roughly in the order I'd tackle them.


1. Stop Calling Instantiate() and Destroy() in Gameplay Loops

This is the single most common performance killer I see in mobile projects, especially anything with projectiles, particles, or enemy spawning — which is basically every action or arcade game.

Instantiate() and Destroy() are expensive. They trigger memory allocation, garbage collection pressure, and Awake/Start calls every single time. If your game fires a projectile every half-second and you're instantiating a new GameObject for each one, you're generating garbage constantly, and on mobile hardware the GC pause that follows is exactly what causes those visible frame stutters.

The fix is object pooling — pre-instantiate a pool of objects at load time, and instead of destroying them, deactivate and return them to the pool for reuse:

public class ObjectPool : MonoBehaviour
{
    [SerializeField] private GameObject prefab;
    [SerializeField] private int poolSize = 50;
    private Queue<GameObject> pool = new Queue<GameObject>();

    void Awake()
    {
        for (int i = 0; i < poolSize; i++)
        {
            GameObject obj = Instantiate(prefab);
            obj.SetActive(false);
            pool.Enqueue(obj);
        }
    }

    public GameObject Get()
    {
        if (pool.Count == 0)
        {
            GameObject obj = Instantiate(prefab);
            return obj;
        }
        GameObject pooledObj = pool.Dequeue();
        pooledObj.SetActive(true);
        return pooledObj;
    }

    public void Release(GameObject obj)
    {
        obj.SetActive(false);
        pool.Enqueue(obj);
    }
}
Enter fullscreen mode Exit fullscreen mode

Unity actually ships a built-in ObjectPool<T> class as of 2021 LTS if you want something more robust than a hand-rolled queue, but the principle is the same either way. Anything that spawns and despawns repeatedly — bullets, particles, enemies, pickups — should go through a pool. This one change alone has fixed more mobile frame-rate complaints than anything else on this list.


2. Audit Your Update() Calls

Every Update() method across every active MonoBehaviour runs every frame, and it adds up fast in ways that aren't obvious from the profiler timeline at a glance. A few habits that help:

  • Cache component references. GetComponent<T>() calls inside Update() are a classic mistake — cache the reference in Awake() once instead of looking it up 60 times a second.
  • Avoid Camera.main in hot paths. Camera.main does a FindObjectWithTag lookup under the hood unless you're on a fairly recent Unity version with caching improvements. Cache it once.
  • Move logic that doesn't need per-frame precision into coroutines or timers. AI decision-making, for example, rarely needs to run every frame — a check every 0.2 seconds is usually visually indistinguishable and a fraction of the cost.

The Unity Profiler's CPU Usage module will show you exactly which scripts are eating frame time — it's worth running a pass through it before you assume you know where the bottleneck is. Guessing at performance problems wastes more time than profiling does.


3. Batch Your Draw Calls

Draw calls are one of the more mobile-GPU-sensitive costs in Unity, since mobile GPUs generally have far less headroom than desktop for issuing large numbers of separate draw calls per frame.

Two practical levers:

  • Static batching for non-moving geometry — enable it in Player Settings and mark static objects as such in the Inspector.
  • Sprite atlasing for 2D games — combining multiple sprites into a single texture atlas means the renderer can batch draw calls for objects sharing that atlas instead of issuing one call per unique texture. Unity's built-in Sprite Atlas system handles this with minimal setup.

If you're building a 2D mobile game and haven't set up sprite atlases yet, this is usually a quick win worth doing early rather than retrofitting later once you have hundreds of loose sprites scattered across the project.


4. Watch Your Texture Memory Budget

Mobile devices have dramatically less GPU memory than the desktop machine you're developing on, and texture memory is one of the easiest things to blow past without noticing until you're testing on an actual mid-range device.

  • Set appropriate Max Size and Compression settings per platform in the texture import settings — don't ship 4K textures for assets that render at 128x128 on screen.
  • Use ASTC compression for Android where supported; it gives a meaningfully better quality-to-size ratio than older formats like ETC2 on devices that support it.
  • Check Mip Maps — useful for 3D games with objects at varying distances, generally unnecessary (and wasteful) for 2D sprite-based games.

5. Physics: Don't Let Collision Checks Run Wild

Physics calculations are another area where mobile CPUs fall behind quickly if you're not deliberate about it.

  • Use layer-based collision matrices (Project Settings → Physics) to prevent Unity from checking collisions between layers that will never realistically interact — UI elements and enemy hitboxes, for instance.
  • Prefer OverlapCircleNonAlloc / OverlapBoxNonAlloc over their allocating counterparts in any code that runs every frame — the allocating versions generate garbage on every call, which compounds the same GC pressure problem from point #1.
  • Lower the Fixed Timestep in Project Settings if your gameplay doesn't need high physics precision — the default is fine for most casual and mid-action mobile games, but it's worth checking rather than assuming.

6. Profile on Real Devices, Not the Editor

This one isn't a code fix, it's a workflow habit, but it matters more than most of the technical items above. The Unity Editor runs on desktop hardware with desktop memory bandwidth and desktop GPU drivers. Performance numbers in Play Mode tell you almost nothing reliable about how the game runs on a three-year-old mid-range Android phone, which — realistically — is the device profile a large chunk of your actual player base is using.

Use the Unity Profiler in remote/device mode, or Android's own GPU profiling tools, connected to a real physical device. Test on the cheapest device you're willing to support, not your own flagship phone. If it runs acceptably there, it'll run fine on everything above it.


7. Don't Let Ad SDKs Tank Your Frame Rate

This is a mobile-specific gotcha that catches a lot of developers off guard: ad network SDKs — AdMob included — can introduce their own frame drops, particularly around ad load and ad close transitions, if they're not integrated carefully.

A few things that help:

  • Preload ads well before you need them, rather than calling LoadAd() right as the player reaches the point where you want to show one. A rewarded ad that's already cached shows instantly; one loaded on-demand can stall the main thread.
  • Avoid triggering ad loads during active gameplay — load during natural pause points (menus, loading screens, level-end summary screens) instead of mid-action.
  • Be deliberate about where ads actually appear, not just how they're technically wired up. Interstitials fired at the wrong moment don't just feel jarring to players — they're also one of the more common causes of uninstalls, which matters just as much as raw frame-rate performance in the long run. I went deeper on ad placement strategy and balancing monetization against player experience in this guide on mobile game monetization in Unity if that's a piece of the puzzle you're still working through.

Putting It Together

None of these seven changes is individually dramatic. Object pooling might buy you a smoother frame time under heavy spawning. Sprite atlasing shaves draw calls. Careful texture compression frees up memory headroom. But stacked together, they're usually the difference between a mobile build that feels sluggish on mid-range hardware and one that runs cleanly across the actual device spread your players are using — which, for most mobile titles, skews a lot lower-end than the developer's own test phone.

If you're earlier in a project and want a working, already-optimized codebase to study rather than retrofitting these patterns into something built without them in mind, it can be worth looking at production-structured Unity source code as a reference — seeing pooling, batching, and ad-loading patterns implemented correctly in a shipped project teaches a lot more than reading about them in isolation. There's a catalog of complete Unity game projects, built with most of these optimizations already in place, over at Unity Source Code if you want a working reference point.


What's the mobile performance issue that's bitten you the hardest? Curious if others have run into the ad-SDK frame-drop problem specifically — it's one that doesn't get talked about nearly as much as pooling or draw calls but has cost me more debugging hours than either.

Top comments (0)