J2ME Game Optimization Secrets

This article describes the role that code optimization plays in writing fast games for mobile devices. Using examples I will show how, when and why to optimize your code to squeeze every last drop of performance out of MIDP-compliant handsets. We will discuss why optimization is necessary and why it is often best NOT to optimize. I explain the difference between high-level and low-level optimization and we will see how to use the Profiler utility that ships with the J2ME Wireless Toolkit to discover where to optimize your code. Finally this article reveals lots of techniques for making your MIDlets move.

Why Optimize?

It is possible to divide video games into two broad categories; real-time and input-driven. Input-driven games display the current state of game play and wait indefinitely for user input before moving on. Card games fall into this category, as do most puzzle games, strategy games and text adventures. Real-time games, sometimes referred to as Skill and Action games do not wait for the player - they continue until the game is over.

Skill and Action games are often characterized by a great deal of on-screen movement (think of Galaga or Robotron). Refresh rates must be at least 10fps (frames per second) and there has to be enough action to keep gamers challenged. They require fast reflexes and good hand-eye co-ordination from the player, so compelling S&A games must also be extemely responsive to user input. Providing all that graphical activity at high framerates while responding quickly to key-presses is why code for real-time games has to be fast. The challenges are even greater when developing for J2ME.

Java 2 Micro Edition (J2ME) is a stripped-down version of Java, suitable for small devices with limited capabilities, such as cell phones and PDAs. J2ME devices have:

Writing fast games for the J2ME platform further challenges developers to write code that will perform on CPUs far slower than those found on desktop computers.

When not to Optimize

If you're not writing a Skill and Action game, there's probably no need to optimize. If the player has pondered her next move for several seconds or minutes, she will probably not mind if your game's response to her action takes more than a couple hundred milliseconds. An exception to this rule is if the game needs to perform a great deal of processing in order to determine its next move, such as searching through a million possible combinations of chess pieces. In this case, you might want to optimize your code so that the computer's next move can be calculated in a few seconds, not minutes.

Even if you are writing this type of game, optimization can be perilous. Many of these techniques come with a price tag - they fly in the face of conventional notions of "Good" programming and can make your code harder to read. Some are a trade-off, and require the developer to significantly increase the size of the app in order to gain just a minor improvement in performance. J2ME developers are all too familiar with the challenges of keeping their JAR as small as possible. Here are a few more reasons not to optimize:

optimization is a good way to introduce bugs

some techniques decrease the portability of your code

you can expend a lot of effort for little or no results

optimization is hard

That last point needs some illumination. Optimization is a moving target, only more so on the Java platform, and even more so with J2ME because the execution environments vary so greatly. Your optimized code might run faster on an emulator, but slower on the actual device, or vice versa. Optimizing for one handset might actually decrease performance on another.

But there is hope. There are two passes you can make at optimization, a high-level and a low-level. The first is likely to increase execution performance on every platform, and even improve the overall quality of your code. The second pass is the one more likely to cause you headaches, but these low-level techniques are quite simple to introduce, and even simpler to omit if you don't want to use them. At the very least, they're very interesting to look at.

We will also use the System timer to profile your code on the actual device, which can help you to gauge exactly how effective ( or utterly ineffective ) these techniques can be on the hardware you're targeting for deployment.

And one last bullet-point:

optimizing is fun

A Bad Example

Let's take a look at a simple application that consists of two classes. First, the MIDlet...

For fast games, this loop has to be as tight and fast as possible. Our loop continues for a finite number of iterations (LOOP_COUNT = 100) and uses the System timer to calculate how long in milliseconds the whole exercise took, so we can measure and improve its performance. The time and the results of the work are displayed on a simple Form. Use the Start command to begin the test. Pressing any key will exit the loop prematurely. The Exit command exits the application.

In most games, the work phase of the main game loop involves updating the state of the game world - moving all the actors, testing for and reacting to collisions, updating scores, etc. In this example, we're not doing anything particularly useful. The code simply runs through an array of numbers and performs a few arithmetic operations on each, aggregating the results in a running total.

The run() method also calculates the amount of time it takes to execute each iteration of the loop. Every frame, the OCanvas.paint() method displays this value in milliseconds at 16 random locations on screen. Normally you would be drawing the graphical elements of your game in this method, but our code offers a reasonable facsimile of this process.

Regardless of how pointless this code may seem, it will allow us ample opportunity to improve its performance.