Yesterday I ported The Fantastic Fivesome over to Android. It was just about the most difficult port I have ever worked on, not because of the code, but because I had 457 PNG files with a resolution of about 500×500 which took up 98 MB, and 208 WAV files taking up 188 MB. I was able to take 250 MB off a 300 MB APK file. How did I do it? Read on.
I have been using MonoGame since November of last year. I have used it to port three games to Windows 8, one to iOS and three Android. Porting to Windows 8 was pretty straight forward and painless, porting to Android was a bit more difficult. Here’s what I’ve learnt.
Playing audio can be difficult.
In the defunct world of XNA programming you can play audio using three methods:
1. XACT Audio Engine
2. SoundEffect Objects
3. Song Objects
XACT only works on the Xbox360 and the PC, and isn’t supported at all on MonoGame. Damn. Okay so you’re just going to have to make due with SoundEffects and Song objects. That seems to be perfectly fine for a lot of developers. Except there are a few things you’ll need to be aware of.
1. Song objects always loop. Even if you tell them not to loop, they will loop. If you want your music to loop until you tell it to stop, then no problem. If not, you’re going to have to figure out how long one of your songs is, create a timer, and stop the song manually when the timer runs out. This limitation really annoyed me, so I completely abandoned using Song objects.
2. SoundEffects load asynchronously. That sounds fine, but there is no way of knowing when a SoundEffect has finished loading. If you try to play a SoundEffect that hasn’t finished loading, it simply wont play.
My games were full of instances where I loaded SFXs and then tried to play them straight away. I had to write some extra code to load all my SFXs on start up, play them when needed, and immediately load them once again after playing.
Your APK must be below 50 MB
This seemed very difficult for me. I mostly created adventure games, and they contain a lot of WAV files and a lot of textures. Both of which take up a huge amount of space. This is quite hard because the Mono.Android run time takes up about 12 MB, leaving you with 38 MB for textures and audio.
You can create APK Expansion Packs, but at the moment MonoGame doesn’t have APIs that support loading assets from Expansion Packs. Someone on the Codeplex forums wrote some code to extend the ContentManager and read from an APK Expansion Pack, but I had trouble implementing this myself.
There is no texture compression
Android!!!! Windows Phone and iOS development is a lot simpler, because there are strict hardware requirements for their phones. Those hardware requirements are there so that API designers can be sure things like Texture Compression formats will work on all devices. So there’s no way to reduce the size of your textures on Android. That just makes it that much more difficult to reduce the size of your APK.
Use code to support multiple resolutions
If you create a Windows Phone 7 XNA app you can choose any resolution up to 800×480, and it will run on a 1280×720 or a 1280×736 device. XNA automatically scales up your graphics and touch points for you. This means you can be (and I was) a lazy programmer and target one resolution, and hard code all your positioning.
Not so in MonoGame. If you target one resolution, the game will not be functional on all other resolutions. I was porting my Windows Phone games to Android, so I wrote these functions to resize Vector2 from a 800×480 co-ordinate space to devices current co-ordinate space.
Create different sized fonts for different resolutions.
I didn’t want to scale fonts, even if you scale down a large font it can look really bad. So I decided to create three sets of fonts for the three different resolution sets:
1. Devices with a width of less or equal to 800 px.
2. Devices with a width between 800 px to 1280 px
3, Devices with a width greater than or equal to 1920 px.
This of course is not an ideal solution.
File IO is fucking slow
My oh my, things sure take a long time to load on Android devices. Load times for The Fantastic Fivesome on the Xbox360 took at most three seconds, load times were about the same on my HTC 8X Windows Phone. Load times for my Samsung Galaxy Nexus were about twenty seconds.
In a previous blog post I talked about how I needed to create a pseudo asynchronous loader. I actually needed to do that, because if I didn’t the OS would think my application had hung.
You can work with raw PNG/JPG files
In XNA, every art asset you work with is first converted into an XNB file, which are very large, but can be loaded into memory very quickly. In computer science we often talk about trade offs, so in this case XNA has decided to trade off package size for faster load times. Fair enough.
Let’s say you have two PNG files that have the same resolution. The first file is a 2 MB file, the second file is a 200 KB file. When converted to an XNB file, they will take up the same amount of space (256 KB if you use DXT Compression).
MonoGame on Android lets you work with raw PNG and JPG files directly. This means you can bundle them into your APK, and it also means PNG and JPG compression will have an effect on your APK size. After discovering this, I used TinyPNG to compress all 457 of my PNG file, they took up 60% less space after this, and saved me 70 MB.
You can work with raw MP3 files
Similar to the previous post, you don’t have to convert your WAV/MP3 files to XNB files, and this means you can compress your MP3s to make your APK smaller.
If your game launches under a lock screen it will glitch out
Apparently there is really nothing you can do about this. Luckily this shouldn’t happen too often. Users do not typically launch an application and then immediately lock their phones.