by Robert Virkus

Game programming with J2ME Polish

news
Sep 26, 200515 mins

Optimize J2ME Polish's game engine

The games industry is the biggest player in the J2ME market. When you’re programming J2ME applications, chances are that you are working on games. Gaming is a tremendous success story for mobile Java and belongs to the “3 Gs” that supposedly form the main revenues in the mobile arena: games, gambling, and girls. The gaming market is growing rapidly and has an estimated volume of billion worldwide in 2005.

The industry has reacted by introducing great enhancements for game programming in the Mobile Information Device Profile 2.0 standard through the Java Community Process Website. Unfortunately, the majority of phones out there support only the MIDP 1.0 standard. This is where J2ME Polish’s game engine comes to the rescue. As you’ll learn in this chapter, you can use the game engine of J2ME Polish for quickly porting MIDP 2.0 games to MIDP 1.0 platforms.

Using the game engine

Programming games using the MIDP 2.0 game API is easy. You have sprites, a layer manager with a view window, and much more. These features are sorely missed when you develop your game for MIDP 1.0 platforms. This is why J2ME Polish includes a wrapper API that enables you to use the javax.microedition.lcdui.game.* API, even on MIDP 1.0 devices. The game engine allows you to use the complete MIDP 2.0 game API on MIDP 1.0 devices. Usually, no source code changes are necessary when you use proper import statements. However, there are some principal restrictions that might require adjustments of your code, especially the inability to use the pixel-level collision detection. Also, not all MIDP 1.0 platforms support sprite transformations. Dealing with these limitations is discussed in the “Working around the Limitations of the Game Engine” section later in this article.

Using the game engine is not difficult. J2ME Polish weaves the wrapper classes in your code automatically when you target MIDP 1.0 devices. This is done just by exchanging the import statements, so you must use proper import statements instead of fully qualified class names. Listing 1 demonstrates what you should not do.

Listing 1. How not to use the game engine

 

public class MyGameCanvas extends javax.microedition.lcdui.game.GameCanvas implements Runnable { public MyGameCanvas(boolean supress) { super(supress); }

public void run() { // main game-loop } }

The code in Listing 1 won’t work when you target a MIDP 1.0 device. Listing 2 shows a working example, which uses import statements properly.

Listing 2. Proper use of the game engine with import statements

 

import javax.microedition.lcdui.game.GameCanvas; public class MyGameCanvas extends GameCanvas implements Runnable { public MyGameCanvas(boolean supress) { super(supress); }

public void run() { // main game-loop } }

Optimizing the game engine

You can tweak the game engine by defining several preprocessing variables in the <variables> section of your build.xml script. The following optimizations are available:

  • Enable the full-screen mode for your game
  • Optimize the performance of the TiledLayer by using a back buffer and/or single tile images
  • Increase the number of possible tiles
  • Activate the game engine for MIDP 2.0 devices with a faulty or slow implementation of the game API

Running your game in full-screen mode

Usually, you will want to use the full-screen mode for your game. To enable this mode, use the fullscreen attribute of the <build> element in your project’s build.xml file. Possible values are true, false, or menu. The menu mode allows you to design the menu bar, but it is available only when you use the J2ME Polish GUI.

The fullscreen attribute of the <build> element enables the full-screen mode for the complete application. If you want to use a different setting for the actual game play, define a specific setting for the GameCanvas implementation by setting the polish.GameCanvas.useFullScreen preprocessing variable. Allowed values are again true, false, or menu. Listing 3 demonstrates how you can use the menu full-screen mode for your application, while using the regular full-screen mode for the GameCanvas.

Listing 3. Enabling the full-screen mode for the GameCanvas

 <j2mepolish>
   <info
      license="GPL"
      name="MazeRace"
      vendorName="A reader."
      version="0.0.1"
      jarName="${polish.vendor}-${polish.name}-${polish.locale}-mazerace.jar"
   />
   <deviceRequirements>
      <requirement name="Identifier" value="Nokia/Series60" />
   </deviceRequirements>
   <build
      usePolishGui="true"
      fullscreen="menu"
   >
      <midlet class="com.apress.mazerace.MazeRace" />
      <variables>
         <variable
         name="polish.GameCanvas.useFullScreen"
         value="true"
         />
      </variables>
   </build>
   <emulator />
</j2mepolish> 

You need to enable the menu mode when you add commands to your GameCanvas. The menu mode requires the J2ME Polish GUI, which, in turn, requires that you do not implement the paint( Graphics ) method, because this method is used by the J2ME Polish GUI internally.

In such cases, you should use the flushGraphics() method instead. When this is not possible, you can use some preprocessing for implementing the paintScreen( Graphics ) method instead of the paint( Graphics ) method, as shown in Listing 4.

Listing 4. Using the paintScreen() method instead of paint() in the menu full-screen mode

 

import javax.microedition.lcdui.game.GameCanvas; import javax.microedition.lcdui.Graphics; public class MyGameCanvas extends GameCanvas implements Runnable { public MyGameCanvas(boolean supress) { super(supress); }

public void run() { // Main game loop }

//#if polish.usePolishGui && polish.classes.fullscreen:defined && !polish.midp2 //# public void paintScreen( Graphics g ) //#else public void paint( Graphics g ) //#endif

{ // Implement the paint method } }

Using a back buffer in the TiledLayer

The TiledLayer paints several tiles on the screen and is often used to render the background of a game. The back buffer optimization uses an internal image buffer to which the tiles are painted only when they have changed. Instead of painting all tiles individually, the complete buffer will be painted to the screen. This can quite dramatically increase the speed of your game, especially in cases when the visible tiles are changed only occasionally.

The drawbacks of the back buffer optimization are that it uses more memory and that you can’t use any transparent tiles. Therefore, you should not activate the back buffer optimization when any of the following apply:

  • Memory is an issue
  • The TiledLayer is not used as the background
  • You use several TiledLayers simultaneously

You can activate the back buffer optimization by setting the polish.TiledLayer.useBackBuffer preprocessing variable to true. You can also specify the background color by setting the polish.TiledLayer.TransparentTileColor variable to any integer color value, as shown in Listing 5. This color is then used for previously transparent tiles.

Listing 5. Activating the back buffer optimization for the TiledLayer

 <variables>
   <variable
      name="polish.TiledLayer.useBackBuffer"
      value="true"
   />
   <variable
      name="polish.TiledLayer.TransparentTileColor"
      value="0xCFCFCF"
   />
</variables> 

Splitting an image into single tiles

A TiledLayer can be drawn significantly faster when the base image is split into single tiles. This optimization needs a bit more memory compared with a basic TiledLayer. Also the transparency of the tiles is lost when the device does not support Nokia’s UI API. You can activate this optimization by setting the polish.TiledLayer.splitImage preprocessing variable to true, as shown in Listing 6.

Listing 6. Activating the split image optimization for the TiledLayer

 <variables>
   <variable
      name="polish.TiledLayer.splitImage"
      value="true"
   />
</variables> 

Defining the grid type of a TiledLayer

Each TiledLayer stores the information about the included tiles in an internal array called the grid. By default, J2ME Polish uses a byte grid that significantly decreases the memory footprint, but which limits the number of different tiles to 128. You can change the array type from the default byte to int or short by defining the preprocessing variable polish.TiledLayer.GridType accordingly. Listing 7 shows how to change to the short type, which allows you to use up to 32,767 different tiles—probably enough for even the most demanding mobile game.

Listing 7. Supporting up to 32,767 different tiles

 <variables>
   <variable
      name="polish.TiledLayer.GridType"
      value="short"
   />
</variables> 

Using the game engine for MIDP 2.0 devices

You can use the J2ME Polish implementation for MIDP 2.0 devices as well. You might want to do this because some vendor implementations are buggy or have sluggish performance. To use the game engine on MIDP 2.0 devices, set the preprocessing variable polish.usePolishGameApi to true, as shown in Listing 8.

Listing 8. Using the J2ME Polish game engine for a specific MIDP 2.0 target device

 <variables>
   <variable
      name="polish.usePolishGameApi"
      value="true"
      if="polish.identifier == VendorName/DeviceName"
   />
</variables>

Working around the limitations of the game engine

You can use any classes of the javax.microedition.lcdui.game.* API in your game, but you need to be aware of some technical limitations for the game engine:

  • Pixel-level collision detection: On MIDP 1.0 platforms, you cannot use the pixel-level collision detection for sprites, so you need to set collision rectangles instead—preferably tight ones.
  • Evaluation of user input: When you extend the GameCanvas class, you should call the super implementations when you override one of the methods keyPressed(int), keyReleased(int), or keyRepeated(int), so that the game engine is informed about the events as well. I recommend using the getKeyStates() method in the main game loop for evaluating the user’s input. This guarantees the best performance on both MIDP 2.0 and MIDP 1.0 platforms.
  • Sprite transformations: At the time of this writing, you can use Sprite transformations only for devices that support Nokia’s UI API; otherwise, the transformations will be ignored. You can find out whether the current target device supports Sprite transformations by checking the preprocessing symbol polish.supportSprite, then Transformation as shown in Listing 9. When no transformations are supported, you can just add additional frames that are already transformed to your Sprite image. Instead of transforming the Sprite, you can achieve the same effect by setting another frame sequence that points to the transformed frames.

Listing 9. Checking whether the device supports sprite transformations

 

import javax.microedition.lcdui.game.GameCanvas; import javax.microedition.lcdui.game.Sprite; public class MyGameCanvas extends GameCanvas implements Runnable { //#if !( polish.midp2 || polish.supportSpriteTransformation ) private static final int MIRROR_SEQUENCE = new int[]{ 2, 3 }; //#endif Sprite player;

public MyGameCanvas(boolean supress) { super(supress); }

public void run() { // Main game loop }

public void mirrorPlayer() {

//#if polish.midp2 || polish.supportSpriteTransformation

this.player.setTransform( Sprite.TRANS_MIRROR );

//#else

// Use an additional mirrored frame in the sprite: this.player.setFrameSequence( MIRROR_SEQUENCE );

//#endif

} }

Porting a MIDP 2.0 game to the MIDP 1.0 platform

Thanks to the game engine and the GUI of J2ME Polish, you need to address only specific issues manually to allow a MIDP 2.0 game to run on a MIDP 1.0 platform. As usual, you can use preprocessing to detect the capabilities of your target device.

To port your MIDP 2.0 game to MIDP 1.0 devices, you first need to work around the restrictions of the J2ME Polish game engine, as outlined in the previous section. You might also need to adjust MIDP 2.0-specific code so that your application works on MIDP 1.0 devices.

In general, you cannot use any MIDP 2.0-only functionality in your game, but there are two important exceptions to this rule:

  • You can use everything from the javax.microedition.lcdui.game.* API
  • You can use most MIDP 2.0 high-level GUI functions, like CustomItem, POPUP ChoiceGroups, or Display.setCurrentItem() when you use the J2ME Polish GUI

So, for games, three porting issues commonly arise: usage of MIDP 2.0-only low-level graphics operations, sound playback, and device control (the vibration and display light). The solution is to use vendor-specific libraries.

Tip
You will often need to integrate specific resources in your game depending on the capabilities of your target device. You can use the resource assembly feature of J2ME Polish to ensure that you include the correct files in your jar file.

Porting low-level graphics operations

Porting low-level graphics operations is a challenging task. You can find low-level graphics operations in the javax.microedition.lcdui.Graphics and javax.microedition.lcdui.Image classes.

For example, the MIDP 2.0 platform allows you to draw raw RGB data directly with the Graphics.drawRGB() method. The Image.createRGBImage() method also processes raw RGB data. Whether you can port these functionalities depends on both your actual usage as well as the target device. All Nokia devices support the simple yet powerful Nokia UI API; other vendors often provide their own proprietary extensions.

Table 1 lists the proprietary libraries you can use for porting low-level graphics operations.

Table 1. Proprietary APIs for porting low-level graphics operations

Functionality MIDP 2.0NokiaMotorolaSiemens
RGB-DataGraphics.drawRGB()

com.nokia.mid.ui.

DirectGraphics.

drawPixels()

com.motorola.

game.ImageUtil.

setPixels()

 
Rotation and reflection

Graphics.

drawRegion()

com.nokia.mid.ui.

DirectGraphics.

drawImage()

 

com.siemens.mp.ui.

Image.mirrorImage

Horizontally()

com.siemens.mp.ui.

Image.mirrorImage

Vertically()

Scaling

de.enough.

polish.util.

ImageUtil.scale()

 

com.motorola.game.

ImageUtil.

getScaleImage()

com.siemens.

mp.ui.Image.create

ImageWithScaling()

For example, if you use the Graphics.drawRGB() method for creating a translucent background, you can use the com.nokia.mid.ui.DirectUtils and com.nokia.mid.ui.DirectGraphics APIs for replicating the functionality when Nokia’s UI API is supported by the target device.

Listing 10 shows how you can use preprocessing for determining which APIs can be used. It also demonstrates how the drawRGB() call is adjusted for devices that have the drawRgbOrigin bug, which is the case with some devices.

Listing 10. Porting translucent backgrounds

<code>

import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; //#if polish.api.nokia-ui &amp;&amp; !polish.midp2 import com.nokia.mid.ui.DirectGraphics; import com.nokia.mid.ui.DirectUtils; //#endif

import de.enough.polish.ui.Background;

public class TranslucentSimpleBackground extends Background {

private final int argbColor;

//#ifdef polish.midp2

// int MIDP/2.0 the buffer is always used: private int[] buffer; private int lastWidth;

</code> //#elif polish.api.nokia-ui private Image imageBuffer; //# private int lastWidth; private int lastHeight; <code>//#endif</code>

public TranslucentSimpleBackground( int argbColor ) { super(); this.argbColor = argbColor; }

public void paint(int x, int y, int width, int height, Graphics g) { //#ifdef polish.midp2 //#ifdef polish.Bugs.drawRgbOrigin x += g.getTranslateX(); y += g.getTranslateY(); //#endif

// check if the buffer needs to be created: if (width != this.lastWidth) { this.lastWidth = width; int[] newBuffer = new int[ width ]; for (int i = newBuffer.length - 1; i >= 0 ; i--) { newBuffer[i] = this.argbColor; } this.buffer = newBuffer; } if (x < 0) { width += x; if (width < 0) { return; } x = 0; } if (y < 0) { height += y; if (height < 0) { return; } y = 0; } g.drawRGB(this.buffer, 0, 0, x, y, width, height, true);

//#elif polish.api.nokia-ui

if (width != this.lastWidth || height != this.lastHeight) { this.lastWidth = width; this.lastHeight = height; this.imageBuffer = DirectUtils.createImage( width, height, this.argbColor ); } DirectGraphics dg = DirectUtils.getDirectGraphics(g); dg.drawImage(this.imageBuffer, x, y, Graphics.TOP | Graphics.LEFT, 0 );

//#else

// ignore alpha-value g.setColor( this.argbColor ); g.fillRect(x, y, width, height);

//#endif

} }

You can emulate other RGB functionalities as well, but be aware that Nokia devices use device-specific RGB data types. Check the Java documentation for more details.

Porting sound playback

Playing sounds is quite easy using the Mobile Media API playback functionalities on MIDP 2.0 devices. Some MIDP 1.0 devices also support the MMAPI, so you can just use the same code. Otherwise, you will need to use proprietary vendor-specific APIs, and very likely, you’ll need different sound formats. Table 2 lists the available proprietary APIs for sound playback.

Table 2. Proprietary APIs for playing sounds

PlatformAPI
MIDP 2.0javax.microedition.media.Player
Nokiacom.nokia.mid.sound.Sound
Motorola

com.motorola.game.GameScreen

com.motorola.game.BackgroundMusic

com.motorola.game.SoundEffect

Siemens

com.siemens.mp.media.Player

com.siemens.mp.game.Sound

com.siemens.mp.game.Melody

Listing 11 demonstrates how you can play back a TrueTones sound instead of a MIDI sound on Nokia devices.

Listing 11. Porting the playback of sounds

 //#if polish.audio.midi && (polish.api.mmapi || polish.midp2)
   import javax.microedition.media.Manager;
   import javax.microedition.media.Player;
//#elif polish.api.nokia-ui
   import com.nokia.mid.sound.Sound;
   import java.io.ByteArrayOutputStream;
//#endif
...
public void playMusic() throws Exception {
   //#if polish.audio.midi && (polish.midp2 || polish.api.mmapi)
      Player musicPlayer =
         Manager.createPlayer(
            getClass().getResourceAsStream("/music.mid"), "audio/midi");
      musicPlayer.realize();
      musicPlayer.prefetch();
      musicPlayer.start();
   //#elif polish.api.nokia-ui
      InputStream is = getClass().getResourceAsStream("/music.tt");
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      int read;
      byte[] buffer = new byte[ 1024 ];
      while( ( read = is.read( buffer, 0, 1024 ) ) != -1 ) {
         out.write( buffer, 0, read );
      }
      Sound sound = new Sound( out.getByteArray(), Sound.FORMAT_TONE );
      sound.play( 1 );
   //#endif
} 

Controlling vibration and the display light

Two other functionalities you might want to port are related to device control: controlling the vibration and display light. With MIDP 2.0, you can accomplish this control by using the javax.microedition.lcdui.Display.vibrate() and flashBacklight() methods. However, on MIDP 1.0 devices, you need to use the available proprietary libraries, which are listed in Table 3.

Table 3. Proprietary APIs for device control

FunctionalityMIDP 2.0NokiaMotorolaSiemens
VibrateDisplay.vibrate()

com.nokia.mid.

ui.DeviceControl.

startVibrate()

com.nokia.mid.

ui.DeviceControl.

stopVibrate()

 

com.siemens.mp.

game.Vibrator.

startVibrator()

com.siemens.mp.

game.Vibrator.

stopVibrator()

com.siemens.mp.game.

Vibrator.

triggerVibrator()

Backlight

Display.

flashBacklight()

com.nokia.mid.

ui.DeviceControl.

flashLights()

com.nokia.mid.

ui.DeviceControl.

setLights()

 

com.siemens.mp.game.

Light.setLightOn()

com.siemens.mp.game.

Light.setLightOff()

Listing 12 shows how you can use Nokia’s DeviceControl class to allow for device vibration when the MIDP 2.0 standard is not supported.

Listing 12. Allowing for device vibration

 //#if polish.midp2
   import javax.microedition.lcdui.Display;
//#elif polish.api.nokia-ui
   import com.nokia.mid.ui.DeviceControl;
//#endif
...
//#if polish.midp2
   private Display display;
//#endif
...
public void vibrate() {
   //#if polish.midp2
      this.display.vibrate( 500 );
   //#elif polish.api.nokia-ui
      try {
         DeviceControl.startVibra( 100, 500 );
      } catch (IllegalStateException e) {
         //#debug error
         System.out.println("Device does not support vibration" + e );
      }
   //#endif
}

Summary

In this article, you’ve looked at the game engine of J2ME Polish and how it helps you to port games to MIDP 1.0 platforms. When you use low-level graphics, sound playback, and device control functionalities of the MIDP 2.0 platform, you need to include some manual adjustments, but otherwise J2ME Polish takes care of everything for you. You can adjust the game engine to your needs by specifying various preprocessing variables.

Robert Virkus is the technical and managing architect and programmer for the open source project J2ME Polish. He is an internationally recognized J2ME expert and is a member of Mobile Solutions Group, Bremen, Germany.