Optimising your Android game
Sometimes – especially when you write your game testing in only on a desktop computer – you discover that it runs slow on some devices. It’s the best time to start optimising. This article is based mostly on my experience with using libGDX and box2D so it will concentrate on that libraries but most tips here are useful with any Java/Android game engine.
Where to start?
First off, don’t optimise until you notice slowdowns. Check your game on the slowest devices you have and only optimise if they run slow – or on fast devices you get less than 60 FPS.
Until you begin use profiling – measure what is the slowest part and fix that first. You can use my TimeLogger class – but it was written in a hurry and is quite ugly (but works!). Make sure you check averaged times from many frames and not judge what is slow by one frame results.
When you find what is causing slowdowns you are ready.
Since you are most likely to write Android games in Java make sure you understand the implications of Garbage Collector. Because every time it cleans unused objects you have to avoid basically two things: creating new objects, disposing of old objects in the game loop. Of course you can create new objects from time to time but make sure you do it in natural game breaks (for example when loading new level – not in the middle of it). And don’t overreact – occasional String created once a second won’t affect performance (although avoid creating one each frame – for example by converting some numbers to strings).
When possible reuse objects – I keep them in an Array and give each one a “reset” method that allows me to easily reuse them for example for particles or score text showing up over game elements. I go through such Array circularly. Since the size of the Array dictates how many particles in total can be shown on screen I can also lower it for slower devices to have an easy way to avoid slowdowns (due to too much sprites to draw).
Graphics and drawing
Good thing would be to provide high and low resolution versions and swap them depending on resolution of the device the game is running on. It might increase the size of the APK significantly though.
Always pack all your images into a neat pack (TexturePacker in libGDX) like that:
When drawing using SpriteBatches don’t use more than one or two unless you really need to. In libGDX there is a neat feature for checking how many renderCalls are you making – the less the better. When your TextureAtlas contains more than one page during drawing there will probably be a lot of texture swaps – it’s better (in my opinion) to make more than one TextureAtlas but each with only one page. Put images for menus in one atlas, images for in-game graphics in second, images for selecting level in third – that way there will be less texture swaps and in result less render calls.
Use culling – make sure you don’t draw outside of the screen. It only slows things down and is invisible anyway, so why do it? Again – libGDX has some culling implemented in Group (But keep in mind it is very simple and won’t work well for rotated objects – so use with caution. In my case I setup the are for culling to be a little bigger than the screen.) Also semitransparency can be very slow on some devices – use it with consideration, when drawing images make sure you don’t have some semitransparent pixels in the background that you left by mistake. Also don’t draw too many sprites with alpha set to something other than 1. And don’t draw sprites that have alpha 0.
Out of screen – out of calculations
Since you already check if your sprites are out of screen for culling you can take it the next step and avoid animating stuff that is outside of the screen (if possible in your game). It is especially important if you are using Spine2D because it makes a lot of calculating when updating the animation. If you need some animation going even when it’s outside of the screen, consider slowing it down (for example updating every 4th frame).
Box2D is harder to optimise because it’s a black box really – you can’t change much in how it works. But avoid creating too many dynamic bodies, it can have a huge impact on FPS, especially when they move around a lot and hit other bodies causing a lot of contacts. Bullet bodies are also to be avoided – if you don’t need them, don’t use them. You can recreate/create bodies in box2D quite fast (since it’s native code, no GC here), so you can consider even creating the box2D world on the fly as the screen moves instead of keeping (and calculating) the whole level.
If you have slowdowns caused by box2D and don’t know what is causing them – count the number of collisions per frame you get and check what collides with what. Too many collisions especially on stacked objects that have low ability to move is very detrimental to speed.
Loops are evil
Well, not really, but remember that your render method (in libGDX) is inside a game loop, so it invoked (ideally) 60 times per second! Avoid complex calculations, loading stuff every frame, creating new objects, too many complex loops inside this one… I also like to add watchdog to any loop I put in my game – it’s an old habit from programming for servers, with a watchdog even if you make a mistake in while condition or make a path that never breaks your game won’t hang the whole device crashing the system. My while loops usually look like that because of that:
int watchdog = 100;
while(watchdog>0) // instead of while(true)
In worst case scenario the loop will execute 100 times and break. Can save your ass sometime in some random situation that you forgot to take into consideration.
This is all for now. I will get back to this topic though, since I love optimising.
Source for some of the tips: this thread on badlogic forum.