DEV Community

unity source code
unity source code

Posted on

Profiling Your Unity Mobile Build: A Practical Checklist Before You Ship published

Why Performance Debugging Gets Skipped

Most Unity mobile devs test on one device — usually a mid-to-high-end phone — and call it done. Then the build ships, reviews start mentioning stutter and battery drain, and retention quietly tanks on lower-end Android devices that make up a huge chunk of the actual install base.

This isn't a lecture about "always profile your game." You already know that. This is a practical checklist of the specific things that go wrong in Unity mobile projects most often, especially in casual and hyper-casual titles where a single dropped frame during an ad transition can tank a session.

1. Draw Calls and Batching

The most common performance killer in casual mobile games isn't complex shaders — it's unbatched draw calls from sprite-heavy UI and scene objects.

Quick checks:

csharp
// In the Profiler, check SetPass calls and Draw Calls under Rendering
// If either spikes above ~100-150 on mid-tier Android, investigate batching

  • Make sure sprites share the same atlas and material where possible.
  • Static batch anything that doesn't move (background props, campsite decorations, static UI panels).
  • Dynamic batching only helps for very low vertex-count meshes — don't rely on it for anything complex.

2. Garbage Collection Spikes

GC spikes are the silent killer of "smooth on my device, laggy in the wild" bug reports. They're especially brutal on Android, where GC pauses are more expensive than on iOS.

Common offenders in casual/kids' games specifically:

csharp
// BAD: allocates every frame
void Update() {
string status = "Score: " + score.ToString();
}

// BETTER: cache and reuse
StringBuilder _sb = new StringBuilder();
void UpdateScoreText() {
_sb.Clear();
_sb.Append("Score: ").Append(score);
scoreText.text = _sb.ToString();
}

Also watch for:

GetComponent() calls inside Update() instead of cached in Awake()/Start()
LINQ usage in per-frame code paths
foreach over List in hot loops in older Unity/IL2CPP configurations (boxing overhead)

3. Ad SDK Integration Is a Common, Under-Diagnosed Culprit

This one deserves special attention because it's rarely where developers look first. Monetization SDKs — AdMob mediation stacks in particular — can introduce frame drops, memory spikes, and even battery drain that have nothing to do with your actual gameplay code. Ad network SDKs run their own background threads, prefetch creatives, and sometimes hold onto references longer than they should.

Symptoms that point to SDK overhead rather than your own code:

  • Frame drops specifically around interstitial/rewarded ad load calls, not during gameplay
  • Memory that climbs steadily over a session and doesn't return to baseline after ads close
  • Battery drain reports concentrated in sessions with heavy ad frequency

I wrote a full breakdown of exactly how monetization SDKs impact performance and the concrete steps to mitigate it — preloading strategy, callback threading, and how to isolate SDK-caused frame drops from your own code in the Profiler: Monetization SDKs Are Probably Hurting Your Unity Game's Performance — Here's How to Fix It. Worth a read before you assume a performance issue is in your own gameplay code.

  1. Texture Memory and Compression Settings

Especially relevant for colorful, asset-heavy casual games:

  • Check your texture import settings per-platform. A texture compressed correctly for iOS (ASTC) but left at default for Android can bloat memory significantly.
  • Audit texture sizes against actual on-screen size. A 2048x2048 background asset displayed at 400x400 on screen is wasted memory and bandwidth.
  • Use the Memory Profiler package, not just the basic Profiler window, to see actual texture memory footprint per asset.

5. Physics and Update Loop Overhead

Casual games often use far more physics than they need to — colliders on decorative objects, unnecessary Rigidbody components, or Update() logic running on objects that are off-screen or inactive.

csharp
// Disable physics/logic on objects outside the camera view
void OnBecameInvisible() {
enabled = false;
}
void OnBecameVisible() {
enabled = true;
}

Small optimization, but it compounds fast in scenes with dozens of interactive props — which is common in exploration-style casual games.

A Minimal Pre-Ship Checklist

Before submitting a build, run through this on at least one low-tier and one mid-tier physical device (not just the Editor or a simulator):

  • Profiler: no GC spikes above ~1-2ms during normal gameplay
  • Draw calls stay reasonable across all scenes, not just the main menu
  • Ad load/show calls don't cause visible frame drops
  • Texture memory checked against platform-specific compression settings
  • Session tested with ads at production frequency, not disabled for testing
  • Battery drain tested over a realistic 15-20 minute session

Where to Go From Here

If you're building on top of a pre-existing Unity template rather than a fully custom project, these same profiling steps still apply — arguably more so, since you didn't write every system yourself and may not know where the SDK integration points or asset pipelines have hidden costs. Reviewing the technical scope and included systems before you start customizing helps avoid surprises later. You can browse structured, feature-documented Unity game projects in the Games category on Unity Source Code if you're comparing templates and want to check what's already integrated versus what you'll need to build or optimize yourself.

Performance work isn't glamorous, but it's the difference between a game that quietly loses players to stutter and one that actually holds retention long enough for your monetization to matter.

Top comments (0)