Reducing the size of your iOS or Android game (Unreal Engine 4)

Looking to reduce the size of your UE4 iOS or Android app? Here is a handy list of the things I've done to reduce the size of Garden Wars and get under those pesky size limits (this is applicable to UE 4.13).

Measuring the app size


First of all, the way I check out the size is using 3 free programs: 7-zip, UnrealPak and WinDirStat (optional).

  • 7-zip will happily open .ipa & .apk packages, and in there you can see the packed sizes of things.
  • UnrealPak can be used to extract your pak file content and see what got packaged. Make a .bat file with this line in it:

    %~dp0\..\..\UE4\4.11\Engine\Binaries\Win64\UnrealPak.exe %1 -extract %~dp1\extracted

    ...then just make a shortcut to the bat file on your desktop - after that you can drag any pak file onto it, then find the extracted contents right next to your pak file.

  • WinDirStat is a handy filesize visualiser that gives you a colourful view of a folders contents, so you can very quickly see what the biggest users are. Once you have extracted your pak file, you can tell WinDirStat to analyze the extracted pak contents - you will most likely see huge music files, huge font files and some large engine materials.
     

Reducing the app size


Now here is what I do to reduce size:

  1. In my experience the single biggest win you can get is having a good PakBlacklist.txt in your Project\Build\IOS folder (or Android). Here is mine if it helps. You could probably add a lot more to it if you wanted to be more meticulous and daring. Note that you can save a large amount by removing certain engine fonts as well (LastResort, NanumGothic, DroidSansFallback), but you need them if you want to display Asian/Russian languages.
  2. Make sure in your packaging settings, you are packaging for shipping/distribution, you are using package compression, and you are using a pak file.
  3. Make sure you are compressing your PNGs (splash screens, icons) appropriately.
  4. Music files are huge in the big scheme of things - try to compress the imported sound wavs as heavily as you can (you can make that number pretty small before you notice a difference on a phone speaker), or even reduce the number of music tracks you are using.
  5. Make sure you're not including the armv7s binary on iOS, not really necessary (and I think it defaults to off now anyway).
  6. There are also some specific shader permutations you can disable at the bottom of the rendering section in project settings, to maybe save a little (assuming you're not using them), and probably other settings you could experiment with too.
  7. Go through all plugins and disable all the ones you don't need. e.g. for iOS you don't need the Android media player and vice versa
  8. Remember to do a fixupredirects on your content before shipping, leftover references can add up.
  9. I also deleted all unused maps and assets manually - for some reason, certain materials were getting cooked & packaged even though they were never used/referenced by anything.
  10. I opened up WorldGridMaterial and DefaultMaterial, and replaced their shader inputs with constants to reduce the size of those large materials (I also tried changing them to unlit but it seemed to cause crashes).
  11. Lastly, if you're compiling (i.e. you have the github version of the engine), you can change some build flags to reduce the code size - in your Project\Config\IOS\IOSEngine.ini:
[/Script/BuildSettings.BuildSettings]
bCompileAPEX=False
bCompileBox2D=False
bCompileICU=False
bCompileSimplygon=False
bIncludeADO=False
bCompileSpeedTree=False
bCompilePhysXVehicle=False
  • WARNING: if you have bCompileICU=false, it appears to break localisation, if you use it
  • I get compile errors if I try to have bCompileRecast=false, so I leave that in
  • I use the UMG widget, so I leave bCompileCEF3 as true
  • I seem to remember either bCompileLeanAndMeanUE=true or bCompileForSize=true actually causing my IPA to be bigger... not sure why. It'd be great it someone from Epic or just someone who knows their way around the engine code could elaborate in more detail as to what those two actually do, and if they have any drawbacks... bCompileForSize sounds like its prioritising size over performance, or something... kinda like bRuntimePhysicsCooking=True

I end up with a "Universal" IPA of ~123MB. Once they're processed by the App Store, the individual sizes for all devices range between 82-89MB, thanks to iOS 9 app thinning, I think (the universal is only large because it has binaries for all phones in it). I'm guessing the universal IPA size doesn't really matter and that most people can download it over the cellular network since the downloads for each device are well under 100MB.

The final ~123MB universal IPA consists of: 82.5MB engine code (this is what gets reduced once you upload) + 12MB engine content + 10MB of my game content + 16.5MB of audio + 2.5MB startup movie / icons / splash screen.

On Android, the APK only comes to about 47MB (without any compiler flag changes).

Overall I'm happy enough with the size I got to. I feel like there is a whole heap of code I could rip out of the engine for this tiny game (if I had lots of time... and knew how to program), and a few more things I could do with my content to reduce size, but it starts to get a bit dodgy and not worth the trouble :)

Fake Shadow Rendering for hundreds of plants on mobile

This article explains how I went about rendering shadows for all of the plants in Garden Wars. Shadows seem like such a small thing but having them there makes a big difference to how polished the game looks.

The unique challenge here:

  • There are technically 762 plants (6 per tile) needing shadows at once
  • The shadows need to be moving as the plants move
  • I needed the game to run on mobile and it’s already pushing GPU limits there
  • The game is using all unlit materials (to get the cartoony look), and unlit materials don’t cast or receive shadows

So I needed to think a little creatively to get some nice crisp dynamic shadows happening. I also needed to leave some room for environment shadows as well (which will be a separate article).

Making things easier first

Step one was actually reducing the number of drawcalls needed for the plants, so I came up with a way to have the plants on a tile appear to be individual, yet be part of the same drawcall. So that brought it down to 127 plants to cast shadows for, which is a lot more reasonable, yet still far too much for mobile if you were using the conventional shadow casting system.

Fortunately, there are a number of things that work in my favour here as well:

  • The game is made up of tiles, and each tile knows about the plants that are on it. It knows the plant’s rotation, what stage of growth it’s at, and when the plant is moving.
  • The game camera is orthographic and never moves
  • The light is only ever coming from the top left direction
  • Plants are only ever going to cast a shadow onto a dirt tile.

All of these things help make the solution simpler.

The solution

What I decided to do was pre-render the foliage shadows into a texture, which I can incorporate into the dirt material. But there’s a little more to it, because the foliage sprouts in sections... so each section of the plant needs it’s own shadow that can be turned on. Starting to get more complicated again.

Now, I don’t want to incorporate SIX shadow textures into the dirt material (one for each section of plant on the dirt). I need to keep what I’m adding here as cheap as possible on GPU, since the dirt will be taking up most screen space for the majority of the game. So I needed to separate the shadows for each piece of plant, and still keep it to one texture somehow...

I decided to do this by separating the plant shadows into different grayscale values in the texture:

Each section of plant has a special grayscale value so they can be faded in one at a time as the plant grows

Each section of plant has a special grayscale value so they can be faded in one at a time as the plant grows

The reasons I did it this way were:

  • It’s cheap (and I can store it in the alpha channel of an existing texture, so I won’t be adding a new texture sample to the dirt material at all - win!)
  • The plant pieces always grow out in the same order (so overlapping shadow values don’t matter)
  • I already had this 0-1 “Animation” parameter in the plant blueprint, that controlled the plant growing stages - so all I needed to do was have the shadow grayscale values for each plant stage MATCH the value that already grows the plant to that stage, if you know what I mean (more on that in a second).

Next came the material logic that adds the shadows as the plant grows. Here is the dirt material, getting the shadow it needs with a custom material function:

 
 

Note the “Anim” input in the function - that is controlled by the plant blueprint and basically specifies which sections of plant have grown. I use that value to separate out the bit of plant shadow I need from the texture, which the function then passes back to the dirt material. Here is what the shadows now like if I scrub the “Anim” value from 0 to 1.

Shadows are coming in one at a time, now to improve them..

Shadows are coming in one at a time, now to improve them..

Improving the shadows

So now I have the shadow coming in piece by piece as the plant animates, but there’s still a couple of things to be taken care of.

The plants can be spawned at any rotation on this tile. So to make the shadows match, I have to rotate our shadow texture to match the rotation of the plant that’s on this tile. To do this, I get the rotation of the plant (in radians) from the plant blueprint:

 
Communicating the plant's rotation to the material

Communicating the plant's rotation to the material

 

And pass that to the dirt material so I can rotate the UVs for the shadow texture sample:

 
Rotating the shadow texture UVs to match the plant's rotation

Rotating the shadow texture UVs to match the plant's rotation

 

Note that I have put the UV rotation logic through one of the Custom UVs inputs. The rotator node is quite expensive and I need to keep these shadows as cheap as possible. So what the Custom UVs input does is transfer the load of rotating UVs onto the vertex shader rather than the pixel shader (so it’s doing these calculations just 6 times - once for each vertex on the tile - instead of god-knows-how-many times for every pixel). This saves 5 pixel shader instructions.

Lastly, I just want to offset the shadow a bit (so that it looks like it’s getting cast from the top left). That’s done by simply adding a set value to the shadow UVs. Then I give it the ability to shake around, so that it can move as the plant moves and keeps the illusion of real shadows:

 
Offsetting shadows, and applying shake with params via the plant blueprint

Offsetting shadows, and applying shake with params via the plant blueprint

 
With the shake applied

With the shake applied

Note the “BounceAmount” and “BounceTime” parameters - those parameters are animated by the plant blueprint at the same time as it animates it’s leaves.

And that’s it! In the end we have achieved some nice crisp, dynamic, animating shadows for all our leaves, for a measly 7 pixel shader instructions total... which I think is well worth it!

The shadows in game

The shadows in game

Using vertex shaders to animate and reduce drawcalls

Today I’m going over how I used vertex shaders in UE4 to reduce draw calls from a potential ~2500 down to ~500, and achieve natural-ish-looking plant animation while I was at it.

The unique challenge

There are a lot of individual plants and fruit etc on screen at once, all animating at different times and stages... and I wanted to be able to run well even on mobile devices, so it required some creative solutions.

Up to 762 plants can sprout individually across 127 tiles and animate at any time, along with individually animated fruit and flowers...

Up to 762 plants can sprout individually across 127 tiles and animate at any time, along with individually animated fruit and flowers...

There are 127 tiles on the screen that can be populated. The tile itself is a single drawcall, but:

  • On each tile you have 6 sections of plant that can sprout individually
  • With flowers that can appear on each plant (and must match the plants’ animation)
  • And up to 6 fruit, that also need the ability to be controlled individually
The six plants that can appear on each tile + flowers/fruit

The six plants that can appear on each tile + flowers/fruit

If I went ahead and just made it without any thought, I’d already be somewhere close to 2500 drawcalls! And that’s for just the populated tiles alone, let alone including environment dressing, special fx and UI. That’s probably 5-6 times what you really want on a lower-end mobile device. However:

  • In this case I’ve used all unlit materials (so that I could get the cartoony look I wanted)
  • Everything is made from static meshes (no skeletal animation)
  • There is no masking or translucency (the leaves and flowers are cut out so that I could use opaque materials, because in my experience the extra vertices spent cutting it out are well worth the saving you get by not having masked materials, unless you went super-crazy on the cutting out)

What this means is (as far as the GPU is concerned at least) I could afford to up the drawcalls a bit from a normal lit environment. So I said to myself that 1000 drawcalls would be a good limit.

Reducing drawcalls thanks to the vertex shader

In order to get those drawcalls down a bit, I thought the first thing to address is the foliage - the foliage on one tile needs to be one drawcall, even though it kinda sprouts up piece by piece. This is where the vertex shader animation, or “world position offset” material input, comes in. I can use the vertex shader to make the plants appear separate, even though they are the same mesh.

First, to do the basic opening animation, I wanted something more than just “expanding from a point”... so I went for a cheap equivalent of a “morph target”. That is, I stored the vertex positions of the plants’ pose in spare UV sets. I wrote a script to get the positions of the animated plants at certain keys and bake them into the UV set. Then in the material, I can lerp very cheaply between the imported pose, and the pose stored in the UVs:

Animating the vertices from the imported pose... to the pose stored in the UVs

Animating the vertices from the imported pose... to the pose stored in the UVs

Note: you could store the pose in vertex color instead of UVs, but I was already using that to store the “pivot positions” of each section of plant, for other shader purposes.

Now, in order to animate them open one by one, I gave each section of plant a unique 0-1 value in a spare UV set:

 
The plant piece with a value of 0.0 sprouts first, 1.0=last

The plant piece with a value of 0.0 sprouts first, 1.0=last

 

This was enough to be able to separate each section out in the plant material:

 
Using those UVs to separate the pieces of plant we want

Using those UVs to separate the pieces of plant we want

 

After that, they could be animated one at a time to match the “population” of that tile:

Sprouting plants one by one

Sprouting plants one by one

SO, now instead of 762 potential drawcalls for the foliage, there is only 127.

Since the flowers and fruit can pop up on any of these sections of foliage, I essentially did the same thing to those too (using the UVs to store the pose and separate each bit) - so each tile ended up having:

  • One drawcall for the tile itself
  • One drawcall for the foliage
  • One drawcall for the flowers
  • One drawcall for the fruit

Bringing 2413 drawcalls down to 508. With the environment dressing, FX and UI on top of that, it would rarely breach the 1000 drawcall limit I’d set. Success!

Improving the animation

So I had the plant, flowers & fruit animating, but it all still looks very rigid.

To make the animation seem more natural, I added some shader math to give the leaves some “bounce” - each leaf rotates back and forth about it’s base in a “random” direction (as I mentioned earlier, the vertex color holds the pivot point of each plant section). The plant blueprint animates the amount of “bounce” at the same time it opens each piece of plant, so the movement can be nicely faded out:

Adding "bounce" that fades in and out

Adding "bounce" that fades in and out

This “bounce” shader math is also reused a lot e.g. when bees touch or leave the plant, or when the plant is poisoned or frozen.

I also added a special directional “sway” to the flower when a bee lands on it. The plant blueprint calculates the direction that the bee is landing from, and sends that direction to the vertex shader. In order for the bee to look like it is attached (the bee is also a static mesh using vertex shader animation), that vertex shader logic was also put into the bee material, and gets animated by the plant blueprint as well:

Bees and flowers with the same vertex shader animation

Bees and flowers with the same vertex shader animation

Conclusion

So in the end, we have some basic animation that is fit for the purpose (it happens very quickly and each plant is quite small on screen), all controlled by animating just the two material parameters, and is done at about 20% of the cost of doing everything individually. Well worth the effort.