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.


Content


Not So Small After All

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.


Shrinking an Empty Unity Project

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.

Empty Unity Project Build Size
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.


Improving Unity’s Default Player Settings

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.

Unity Player Build Size Configuration
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.

Code Stripping: Smaller 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.

Unity Graphics API
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.

Unity Build Settings: Compression Method
Additional options that come from the Build Settings (Cmd/Ctrl+Shitf+B)

Unity Default Packages

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.

New Unity Package Manager

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.

Unity Package Manifest
How my manifest.json ended up

Android App Bundles

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!


iOS App Thinning & Slicing

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:


Summary

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:

i Compression Graphics API Scripting Backend Api Compatibility C++ Compiler Config Code Stripping Architectures Unity Default Packages App Bundle Size (KB)
0 Default Vulkan,OpenGLES3 Mono .NET Standard 2.0 ARMv7 Yes No 17,013
1 LZ4HC OpenGLES2 IL2CPP .NET Standard 2.0 Master High ARMv7,ARM64 Yes No 13,987
2 LZ4HC OpenGLES2 IL2CPP .NET Standard 2.0 Master High ARMv7,ARM64 No No 12,367
3 LZ4HC OpenGLES2 IL2CPP .NET Standard 2.0 Master High ARMv7,ARM64 No Yes 6,264

My game is still huge!

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:


Here are some more specific tips & tricks.

Sprite & Texture Atlases

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.


Unity’s Asset Bundles

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.


The Resources Folder

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:


Beware of Third-Party Assets & Packages

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.xml doesn’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 😉


Case Study: Rubix Game

Case Study: Rubix

In June, 2020, we released Rubix on the App Store and Google Play. We offer it as an Instant Play game, and as a WebGL demo on its website and over itch.io.

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.


Attributions