One of the main reasons I took a break from developing JUMP! over a year ago was the performance issues that plagued the game at the time. It can be devastating to have something that feels so close to done, yet feel so far from victory because of one major flaw that can’t be overcome. So that’s the main problem I took head-on when I got back into it.
TL;DR: There were a good number of issues. I’ve had some wins and the game is much smoother than before. I don’t think I’ll get it perfect, but for now I’m calling it close enough 🙂 Feel free to read on for the geeky nitty gritty.
The first thing I did when I started working on JUMP! again in early October was to create a new project in Android Studio, the new development environment for creating Android apps. After working with it for a few weeks, Android Studio feels more natural to me than Eclipse ever did, and it has cool tools like graphs of CPU and Memory Usage over time that help me see when the game is behaving poorly. Unfortunately, getting the game up and running in Android Studio alone didn’t help — when the ball was rolling on a track, it still felt… stuttery. Every few seconds there would be some delay preventing the appearance of smooth motion.
Luckily, I had implemented a custom Stats tracker to help me determine how much time is spent updating and rendering each frame of the game, so I could see my bottleneck was definitely in the rendering code used to draw the game. When a game refreshes 60 times each second (60FPS), each frame only has 16 milliseconds to update and render, but in many of my frames the rendering alone was taking over 20ms! I’m using Android’s Canvas API because I don’t have a large number of images, and it turns out the system-level method lockCanvas was taking up around 10-15ms. Ironically, telling the game thread to sleep for around 10ms at the end of each loop helped reduce the lockCanvas call to 0-2ms; I guess it just needs some time to rest.
I also noticed the frame times seemed to increase whenever I made the ball jump. Through trial and error I learned that the SoundPool object’s play method I’m using to play sound effects was causing the delay, blocking the rest of my update logic, and I was able to resolve this by triggering an asynchronous handler to play the sound effect — basically telling Android “go ahead and play this whenever you’re ready, but I’m not waiting for it.” The code doesn’t have to get blocked waiting for the sound, and it still plays near immediately.
Unfortunately, there are times when the Canvas API’s drawBitmap method to draw images just takes longer than expected. On the exact same frame, with the exact same images and no animation happening at all, rendering will take 8ms on one frame and take 28ms on the next. I can’t really explain this, so I’ve done a couple things to work around it. One is telling my renderer to use Android’s compressed RGB_565 (16-byte) format for images and the drawing surface, which should be faster than the default RGB_8888 (32-byte) format. Another is changing JUMP!’s frame rate to 30FPS instead of 60FPS; this gives each frame 32ms (instead of 16ms) to do its work, and still has a good visual appearance.
I feel like I’ve done about all I can do to squeeze the best performance possible out of Android’s Canvas API for this game. Numerous times I’ve considered changing the render code to use OpenGL, a much faster rendering technique. But due to the steep learning curve, I’ll probably save that for JUMP! 2.0.
If you happen to be an Android game developer struggling with (or having overcome) some performance issues, let me know in the comments. I’d love to hear any feedback on these tips or any advice you have from your own experience!