Table of ContentsSpeed, Glorious SpeedConclusion

Optimization

The best optimization is of course the one you never have to do in the first place. This is where experience and using your head really come into game development. There are plenty of ways to improve your code using a combination of tricks and brute force, but you should make sure you keep your eye on the ball. What's the ball, you ask? It's whether the game is fun. Don't spend all your time optimizing the hell out of something that isn't really adding to the fun anyway.

The easiest way to optimize is to simply not call the code in the first place. This doesn't necessarily mean you have to remove it; maybe you can call it in a different way (or just less). The main game loop is the most heavily pushed area of your game; always consider carefully whether something belongs in this area or whether you can call it less (say, every 100 frames).

With this in mind, techniques for optimizing your code tend to fall into two main areas high-level optimizations, in which you look at the overall algorithm and structure of what you're doing, and low-level optimizations, in which you concentrate on isolated snippets of code, usually inside methods. Take a look at examples of both of these types in the following sections.

High-Level Optimizations

MIDs are fast. No, reallythey are. Related to PCs they are, of course, dogs, but in general terms your average MID is still capable of executing millions of instructions per second. What makes them slow is your codespecifically, you trying to do too much too fast.

The reason I'm telling you this is to make sure you don't start a game project with the impression that things are already slow. It's not true; you need to write slow code to create a slow game. It won't happen right away, but you should keep in mind that at some point you'll end up writing the code that kills your application's performance. All the optimization tools I talked about earlier simply help you find out where your own code is slowing you down.

With this in mind, consider some of the high-level aspects of MID game programming that can affect performance.

Perception Is Reality

If you've ever had the opportunity to visit a movie set, you've seen how much of an illusion the moviemakers create. Through the eye of the camera everything looks perfect, but step behind the set and things are all wood, foam, and Gaffer Tape. For movies, perception really is reality.

Games are the same wayyou only need to go as far as what's required for the game. This can apply to all aspects of your game development. Concentrate on what's fun and what plays well. Always keep in mind what you really need to do to carry this out, and dump the rest.

A good example of this is the collision detection in Star Assaultone of the most resource-intensive areas in the game. I took a few shortcuts in the interest of performance. None of these, however, really make much of a difference to game play. For example, for enemy ships and bullets you don't bother to check whether any hit each otheronly whether they hit the player. This yields a dramatic savings because checking to see whether 20 ships and another 20 bullets hit any of the others (more than 1,000 collision checks) is quite slow. Does it really make a difference in the game if enemy ships don't collide with each other? It's only the player who is really important for the game.

Don't Create Objects

As I mentioned previously, creating objects is about as fast as unraveling barbed wire with a pair of tweezers. In Chapter 11 you saw how to use object pools to reduce the total number and frequency of object constructions. The end result was a dramatic increase in the performance of your game. The important lesson here is that you can optimize simply by not creating an object.

One problem with object construction is that profilers won't give you the whole picture. Although actually doing the construction process might not appear to create significant performance load, the aftereffects of increased garbage collection and reduced memory can have dramatic performance consequences.

I recommend you do a global search in your project for the new and clone keywords, and then carefully consider the implications of each object construction.

You should also be careful of inadvertently creating String objects. For example:

graphics.drawString(0,0, "Score: " + score);

This code will construct a new String object on every call, which in this case is every frame. It might be better to construct this string only when the score changes.

Drawing to the Screen

Usually by the time you're finished doing a whole heap of optimization on your game, you're rewarded with your game spending most of its time drawing the images on the screen. This is because the most time-consuming thing a game does is actually rendering the image (or any other drawing primitive call). Therefore, it's always an optimization if you can avoid doing that draw in the first place. If this requires even extensive checking (such as complex state checks or clipping setups) it's still almost always worth it to avoid or minimize how much screen rendering you do.

Methods of reducing screen drawing usually revolve around detecting whether something on the screen has changed, and if it hasn't, not updating that part of the screen. Another method is to increase the size of images being rendered in order to reduce the total number of individual render calls.

The Algorithm

The biggest and best high-level optimization you can make is always in the algorithm or method you're using. You're not breaking any new ground with J2ME game programming. Most of what you do is basic 2D game programming, so almost everything has been done (and optimized to death) already. Regularly spending time on sites such as Gamasutra (http://www.gamasutra.com), GameDev.net (http://www.gamedev.net) and flipCode (http://www.flipcode.com) will provide you with an abundance of game development material.

Low-Level Optimizations

Now that you've covered the general high-level optimizations, take a look at some lower-level stuff, mostly relating to code techniques.

Pre-Render Complex Graphics

As you already know, drawing anything using the LCDUI is slow going, so it's best to avoid it whenever possible. One method for doing this is to reduce complex rendering to a single pre-built image.

A good example of this is the code to draw the energy bar in Chapter 12 "The Game". Because the fillRect and drawRect methods are relatively slow, the energy bar was updated only if the values changed.

A further optimization along these lines would be to merge all your game status information into a single panel (score, lives, energy, and so on) and then update and render it all in one go.

Balance Classes Versus Memory

Creating new classes can be costly in terms of JAR space, so try to avoid it whenever possible. Instead of creating a new class to handle the specific logic of a sub-type, you can use conditional code such as switch statements. This is fine, but conditional sub-typing has one big issueyou can't use it for fields as well. If a particular type of object needs a field, then every object represented by your class is going to have a copy of that field as well. For example, suppose you have two types of carsan automatic and a manualboth represented using the same Vehicle class. The manual car has an extra boolean field to represent whether the clutch is pressed. The class would look something like this:

public class Vehicle
{
    int speed;
    int gear;
    boolean isAutomatic;
    boolean isClutchEngaged;

    ...
}

Now suppose you create 1,000 automatic cars and one manual car. Do you see the problem yet? The 1,000 automatic car objects have an unnecessary field representing the clutch state. The automatic car type never uses this field so it's a waste of memory. Typically this isn't a problem at first (especially if you're only talking about a single boolean field), but as you add features to the game, you'll find yourself adding more and more type-specific fields to the same class, so the size of all instances of that class will continue to grow.

The solution is to just bite the bullet, create a new class specifically for that type (or branch), and move the fields into the new class. Although this costs in terms of an extra class, it can be worth it to save the extra memory. Try to consider beforehand whether a type is going to need a lot of specific fields, and create a sub-class if you think it's worth it. Unraveling complex type code can be a real pain if you let it go too far before you realize this.

However, there is a special case that you should keep in mind. If there can only be a single instance of a particular type, then make the fields for that type static. Because those fields will no longer be created for every instance, you don't need to sub-class to solve the memory issue. A good example of this is the player's ship in Star Assault.Ifyou wanted to add a secondary weapon, you would need extra fields to track the firing rate and damage level. However, since there is only ever one of these objects in existence, you can just make the extra fields static.

Pre-Compute Complex Values

A great way to save on performance is to pre-calculate values so you don't need to call expensive methods. A good example of this is figuring out all the results for sine and cosine for all 360 degrees. To do this, you can simply initialize a static array for all the results. For example:

// lookup table for cos and sin value (0 to 359)
static int[] lookupCosFP = new int[360];
static int[] lookupSinFP = new int[360];

// static init
{
    for (int i = 0; i < 360; i++)
        lookupCosFP[i] = MathFP.cos(getRadiansFromAngle(i));
    for (int i = 0; i < 360; i++)
        lookupSinFP[i] = MathFP.sin(getRadiansFromAngle(i));
}

Now you can just look up the corresponding array value relating to the degrees you want. For example, this is how you use it in Star Assault:

xAccFP = MathFP.mul(thrustFP, lookupCosFP[alignedDir]);
yAccFP = MathFP.mul(thrustFP, lookupSinFP[alignedDir]);

In a similar vein, you can cache the results of other commonly called methods. The height and width of the main canvas are good candidates for this. For example, instead of always calling the getHeight and getWidth methods on a canvas, you can call them once and cache the results.

public class GameScreen extends Canvas implements Runnable, CommandListener
{
    ...

    private int screenWidth;
    private int screenHeight;

    public GameScreen(StarAssault midlet)
    {
        ...

        screenWidth = getWidth();
        screenHeight = getHeight();

Use Arrays

Whenever possible you should use an array instead of something like a vector. Arrays are significantly faster. The only issue you'll typically encounter is expanding the size of the array if the original allocation wasn't large enough. You can do that, but it requires reconstructing the entire array. For example here's a method from the Tools class:

public final static int[] expandArray(int[] oldArray, int expandBy)
{
    int[] newArray = new int[oldArray.length + expandBy];
    System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
    return newArray;
}

You should also try to use one-dimensional arrays whenever possible. Accessing elements in a two-dimensional array is twice as slow. Typically, you can still access the objects; you just need to do a little calculation yourself. For example, instead of doing this:

world[y][x] = 0;

It's much faster to do this:

world[y * tilesWide + x] = 0;

This code accesses the same location by multiplying the row count by the number of tiles wide in order to convert it to a one dimensional number.

Don't Use Arrays

Even though arrays are faster than vectors they're still slower than just accessing direct variables, so if possible remove array access altogether or look for opportunities to shortcut array access for common cases.

A good example of this is the revised ImageSet in the Chapter 14 source code directory. You'll find that most of the time states are reflected by a single image, rather than an array of images. In the revised version I've added code to use a single image frame, without accessing an array. Since this is a very commonly called method (to draw the ship frames), it can result in a nice time saving.

Use Quick Methods

Not all method calls in Java are equal in performance. The way you declare the method can have a dramatic effect on performance. The fastest type of method you can use is a static one, so try to move as much as you can into static method calls. You'll find this much easier with single-instance objects because there is only one copy of the object's data anyway, so you can easily make those static fields (and hence, static methods that manipulate those fields).

The second fastest are methods declared final. If you do this, you're telling the compiler there's no chance of the method being overridden by any sub-class. This means the call won't need to involve a dynamic reference; therefore, it's much faster. Declaring methods as final costs you very little. If you later find you need to sub-class the method, you can just remove the final declaration.

The two slowest methods to call are those in interfaces and anything declared with the synchronized keyword. You should try to avoid these method types as much as possible. For complete details on the different method types, refer to Appendix A "Java 2 Primer".

NOTE

Tip

Contrary to what you might have been told, you don't need to use the final keyword to encourage inlining. HotSpot VMs (including J2ME VMs) are capable of detecting an inlining opportunity automatically, regardless of whether you declared the method final.

Turn Off Debug

Including debug symbols in your compiled application slows down performance and has a dramatic effect on JAR size. To disable it, you can simply modify the javac build task.

<javac destdir="${bd}/classes" srcdir="${bd}/psrc"
        bootclasspath="${midplib}" debug="false" target="1.1"/>

Other Tips

If you're looking for every ounce of speed, you should also consider all of the following general tips.

  • Exceptions are horribly slow; don't use them for any normal part of the game's logic. Use them only for reporting real error states.

  • Be careful using the synchronize keyword; it makes code extremely slow.

  • The number of parameters in a method call affects the calling speed. You can gain a little by reducing the size and number of parameters.

  • switch statements are much faster than if blocks.

  • Avoid operations on String objects as much as you can; use a StringBuffer.

  • Inner classes are slow; try to avoid them.

  • Set references to null as soon as you're finished with them. Don't wait for finaliza-tion of an object to do this for your object's fields; if you're finished with something, set it (and all its fields) to null immediately.

  • Don't waste time initializing an object to null or zero; the JVM will do this for you.

  • Eat your greens; they make your brain work faster.

  • Use statics wherever you can; they're extremely fast. This applies to both methods and fields; the rule is if it can be static, make it static.

  • Avoid type casting.

    Table of ContentsSpeed, Glorious SpeedConclusion