Optimizing Unity's Build Size for Mobile Games
19 Jun, 202012 min read
I will teach you how to get your Unity mobile game's build size under 15 MB, so you don't have problems building an Instant App for Google Play Store or an online WebGL version. I'll present to you the steps I took to downscale an empty Unity project from 17 MB to 6 MB.
This article covers part of the learning journey I took when developing the mobile game Rubix. Creating this game pushed me into learning many tips & tricks about Unity build size optimization. This article contains a compilation of different learning resources and personal experiences to help you achieve your size goal.
- Not So Small After All
- Shrinking an Empty Unity Project
- My game is still huge!
- Case Study: Rubix
While browsing the Play Store, I came across a section called No space left? Small sized games (for some reason, my Google Play app is in Spanglish). I was surprised to see that all the games were over 50 MB and, more than a couple, over 100.
Google Play Store: Small Games
Seeing this, specially on the Play Store, was somewhat shocking to me because Google has always given me the impression that they take a lot of care in prioritizing size optimization (particularly on SEO and web page's download size).
Even more surprising because they, not so long ago, released a new feature called Google Play Instant, that allows users to try a game without installing or buying it beforehand. Great technique for exposure and marketing, but it needs to be under 15 MB.
Naturally, as Unity mobile developers, we would like to provide a smaller, faster and better game for our players. This article will focus on how to provide a smaller one.
Problem: if you create an empty Unity project, insert a super simple UI and then build for Android (without changing the default settings), you'll get an APK of around 17 MB. Do you see the problem? That's 2 MB above the maximum size allowed by Google Play! And we haven't even added a single script, texture, image or audio clip.
Everything that is included in my test Unity environment
I'll show you how I was able to shrink that same APK from 17 MB to 6 MB.
Note: I am using Unity 2019.3.3f1, and what comes next might vary from previous or future versions.
First of all, if you haven't already, switch from Mono to IL2CPP. If you don't know what I am talking about, check this Unity's forum thread.
If you are not using any .NET 4 feature, make sure the Api Compatibility Level is set to .NET Standard 2.0 (documentation). Don't forget to enable both ARMv7 and ARM64 architectures.
For production releases, you should also set the C++ Compiler Configuration to Master
Once you do this, you'll want to enable Strip Engine Code, and set Managed Stripping Level to High.
Managed code stripping removes unused code from a build, which can significantly decrease the final build size.
There are some considerations for you to take when enabling code stripping. You can read about it in Unity's Documentation. Briefly, if your code or plugins use Reflection, you'll probably need a link.xml, or alternatively the Preserve Attribute, to custom manage what gets stripped out and what doesn't.
If you are building for Android, I recommend that you disable Auto Graphics API, and set OpenGLES2 as the sole Graphics API. This API is lighter than OpenGLES3 and Vulkan. For iOS, both OpenGL APIs are deprecated, so you'll have to stick with Metal.
Note: this can cause some shaders to stop being compatible! Read more about it in the documentation.
Finally, you should be building your releases using the Compression Method: LZ4HC. However, if you are just developing, you can set this option to Default for faster build times.
Additional options that come from the Build Settings (Cmd/Ctrl+Shitf+B)
Since version 2018.1, Unity offers its own Package Manager and allows you to manually exclude or include default packages (like Physics, UnityWebRequest or AI). This is great news for people like me who like to have complete control over the dependencies.
With this feature, you can exclude all the packages that your project doesn't use. Although Code Stripping takes care of removing most of these packages' code, it is still a good practice to simplify it and avoid the mistake of using a package when you didn't want to (like accidental Visual Studio auto-imports).
To use this feature, you can go under
Window > Package Manager and manually remove each package you don't need. This is quite tedious, though. Each time you remove a package, your project will recompile.
As an alternative, you can edit the package manifest, located under your project's root folder and then followed by
Packages/manifest.json. To remove packages, simply delete them from the JSON object. Once you go back to Unity, it will automatically recompile.
How my manifest.json ended up
The real shrinker: Android App Bundles. From what I understand, it basically delivers a device-specific app. In other words, if your device is of type ARM64 architecture, you'll get only that. Before App Bundles, you had to download both ARMv7, ARM64 and even x86 architectures, making the app's size huge.
You can upload App Bundles (
.aab) to the Play Store. Google will do all the hard work so your players get an app specific to their device. Drawback is, you can't install an App Bundle directly to your device, so for development this can seem as an impediment. However, Android has a CLI utility called
bundletool that lets you do this on your local computer. Learn about the bundletool!
The alternative to App Bundles for iOS is app thinning and slicing. It does practically the same: deliver device-specific assets. Because of how Apple apps work, this is mostly done by them automatically (from iOS 9 and up). Read more about it:
- App thinning, Bitcode, Slicing: tutorial (iOS app) by Ankur Srivastava
- App thinning, Unity Documentation
That's it! Those were all the steps I took to reduce the build size. Now, the following table summarizes the settings I used to shrink the initial 17 MB app to 6 MB:
|Size (KB)||Compression||Graphics API||Scripting Backend||Api Compatibility||C++ Compiler Config||Code Stripping||Architectures||Unity Default Packages||App Bundle|
|0||17,013||Default||Vulkan,OpenGLES3||Mono||.NET Standard 2.0||-||-||ARMv7||Yes||No|
|1||13,987||LZ4HC||OpenGLES2||IL2CPP||.NET Standard 2.0||Master||High||ARMv7,ARM64||Yes||No|
|2||12,367||LZ4HC||OpenGLES2||IL2CPP||.NET Standard 2.0||Master||High||ARMv7,ARM64||No||No|
|3||6,264||LZ4HC||OpenGLES2||IL2CPP||.NET Standard 2.0||Master||High||ARMv7,ARM64||No||Yes|
When I wrote this article, I wanted to list all the hard-to-find tips & tricks related to Unity's build size optimizations. I believe I've done that in the last section (as far as my knowledge goes).
Common tips and tricks found over the internet won't be addressed in this article, so if you are still struggling reducing the size of your game, I recommend these great tutorials and articles that specifically cover Unity's built-in asset compression:
- Reduce your Unity Build size by Jason Weimann (YouTube): profile build size, texture and audio compression
- Reducing the file size of your build, Unity Documentation: Unity's version of the previous tutorial
- Optimizing the size of the built iOS Player, Unity Documentation: iOS specific build optimizations
Here are some more specific tips & tricks.
Fortunately, Unity provides a built-in way to create Sprite Atlases (documentation). Unfortunately, Unity doesn't provide a built-in way to create Texture Atlases.
If you followed Jason Weimann's tutorial, you shouldn't have much to worry about because Unity's built-in compression for images & audio clips works pretty well. If your game uses a lot of textures, then Asset Bundles might be what you are looking for.
To be honest, I haven't used Asset Bundles, but I know they can greatly reduce your game's build size by downloading content after installation, at runtime. I won't say more about it, as I don't have the necessary knowledge! Read more about Asset Bundles.
Long answer short: don't use it, unless you need to. The Resources folder provides an easy way to retrieve various types of assets at runtime, but these assets won't get compressed or filtered out from the build by Unity. Read more about the Resources folder:
Anecdote: Firebase vs. Unity Services. When we started developing Rubix, we decided to use Firebase instead of Unity's built-in Analytics and Crash Reports. Besides experiencing a lot of instability, I was frustrated that the Firebase SDK added some features and dependencies that I didn't need and caused some other sort of issue. Here's an overview of what I had to deal with:
- Increasing the build size by around 3 MB on Android (I didn't measure it on iOS)
- Complicating builds: adding CocoaPods and a ton of Java Plugins to the project (via the Play Resolver plugin)
- Some sort of instance ID for Crashlytics that caused Apple to warn us about missing entitlements
- Crashlytics crashing, ironic enough, because
link.xmldoesn't work within packages (GitHub Issue)
- Crashlytics not reporting iOS crashes (StackOverflow)
- Functions SDK not being really necessary to make HTTP calls to Firebase Functions: Why use Firebase Functions SDK for Unity, if UnityWebRequest exists?
Around the final stage of development for Rubix, I decided to try Unity Services and, although it has its own problems (i.e. analytics reporting is not as insightful as Firebase, crappy dashboard compared to Google's), it was way easier to set up and maintain. Plus, it is lighter than Firebase (1 MB or less, not ~3 MB).
So, what I am trying to say is: beware of how third-party assets and packages affect your project, and always try the "native", built-in solution first (in this case, Unity Services). Or even better: create your own solution ?
With the same Unity project, we achieved making our game available for WebGL, iOS and Android under 20 MB. In total, 4 versions: 2 demos and 2 full games.
Besides all the tips I mentioned in this article, we also had some other tricks under our sleeves. Mainly, we took advantage of Scripting Define Symbols to exclude code blocks from certain platforms, or some custom assemblies from being compiled. For example, code that was only meant for the demo version was wrapped around an
#if DEMO statement, and
#if !DEMO for the opposite case.
But being totally honest, our biggest trick was following the KISS principle (keep it simple, stupid). Sure, plugins are great and all, but do you really need a MySpace native sharer??
I hope you have enjoyed this tutorial! Feel free to contact us if you have any questions.
- Gift vector created by vectorpocket
Written by Santiago Degetau