The MIDP/2.0-Compatible Game-Engine

Even though MIDP 2.0 is widely supported, some emerging markets still contain a large number of MIDP 1.0 devices. J2ME Polish offers a nifty solution for those developers who want to support both platforms with the advanced MIDP 2.0 technology.
All classes of the javax.microedition.lcdui.game-package can be used normally:

  • GameCanvas: The GameCanvas class provides the basis for a game user interface.
  • Layer: A Layer is an abstract class representing a visual element of a game.
  • LayerManager: The LayerManager manages a series of Layers.
  • Sprite: A Sprite is a basic visual element that can be rendered with one of several frames stored in an Image; different frames can be shown to animate the Sprite.
  • TiledLayer: A TiledLayer is a visual element composed of a grid of cells that can be filled with a set of tile images.

With this game-engine it is possible to program a game with one source code for MIDP/2.0 as well as MIDP/1.0 devices without changing anything within the source code!

How It Works

The J2ME Polish build-tool identifies the usage of the MIDP/2.0 game API and "weaves" the necessary wrapper classes into the code. The obfuscation step then removes all unnecessary functionality to ensure that only the needed parts are actually included into the application. The J2ME Polish game-engine needs about 6 kb extra space in the resulting JAR package.

Limitations

For technical reasons the collision detection works only with collision rectangles, which should be set tightly for Sprites therefore. A pixel-level collision detection is not supported.

When a class extends the GameCanvas class, it should call the super-implementations, when one of the methods keyPressed(int), keyReleased(int) or keyRepeated(int) is overridden. When the "menu" fullscreen mode is used and the J2ME Polish GUI is used, subclasses of GameCanvas cannot override the paint()-method.

The transformation of Sprites is currently only supported for Nokia devices, otherwise the transformation-setting will be ignored.

Optimizations

The TiledLayer and GameCanvas implementations have several optional optimizations, which can be triggered by defining specific variables in the "build.xml" file.

Using a Fullscreen-GameCanvas

The GameCanvas normally uses the "fullscreen"-attribute of the <build> element to determine whether it should use a fullscreen-canvas. This behavior can be overridden with defining the "polish.GameCanvas.useFullScreen" variable. Allowed value are "yes", "no" and "menu". Please note that the "menu" mode (which should be used when commands are added to the GameCanvas) is only supported when the J2ME Polish GUI is used. Another limitation of the "menu" mode is that the paint()-method of the GameCanvas cannot be overridden.

  • polish.GameCanvas.useFullScreen: Defines whether the fullscreen mode is used, overrides the "fullscreen"-attribute of the <build> element. Possible values are "yes", "no" or "menu".

The following example enables the fullscreen mode for the GameCanvas:

<variable name="polish.GameCanvas.useFullScreen" value="yes" />

This example shows how to override the paint()-method, even though the J2ME Polish GUI and the "menu" fullscreen mode is used:

public class MyGameCanvas extends GameCanvas {
  ...
	//#if polish.usePolishGui && polish.classes.fullscreen:defined
		//# public void paintScreen( Graphics g)
	//#else
		public void paint( Graphics g)
	//#endif
	{
	   // implement the paint method
	   ...
	}
}

Backbuffer Optimization for the TiledLayer

The backbuffer optimization uses a buffer, to which the tiles are only painted when they have changed. This can result in a significant higher frames per second throughput, since the complete layer needs to be painted only seldomly.
The drawback of the backbuffer optimization is that it uses more memory and that no transparent tiles can be used. So when memory is an issue or when the tiled layer is not used as the background, it is recommended not to use the backbuffer optimization.

  • polish.TiledLayer.useBackBuffer: Defines whether the backbuffer optimization should be used, needs to be "true" to enable the optimization.
  • polish.TiledLayer.TransparentTileColor: The color which is used for tiles which should transparent. This defaults to black.

The following example enables the backbuffer optimization:

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

Splitting a TiledLayer-Image into Single Tiles

A tiled layer can be drawn significantly faster when the base image is split into single tiles. This optimization needs a bit more memory compared to a basic tiled layer. Also the transparency of tiles is lost when the device does not support the Nokia UI API (all Nokia devices do support this API).

  • polish.TiledLayer.splitImage: Defines whether the base image of a TiledLayer should be split into single tiles. Needs to be "true" to enable this optimization.

The following example enables the single tiles optimization:

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

Defining the Grid Type of a TiledLayer

The J2ME Polish implementation uses a byte-grid, which significantly decrease the memory footprint, but which also limits the number of different tiles to 128. For cases where more different tiles are used, one can define the type to either "int", "short" or "byte".

  • polish.TiledLayer.GridType: Defines the type of the used grid in TiledLayer. This defaults to "byte". Possible values are "int", "short" or "byte".

The following example uses a short-grid, which allows the usage of 32767 different tiles instead of 128 different tiles:

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

Using the Game-Engine for MIDP/2.0 Devices

You can use the J2ME Polish implementation for MIDP/2.0 devices as well. This can make sense because some vendor implementations are buggy or have sluggish performance. When the game-engine should be used for MIDP/2.0 devices as well, you need to define the preprocessing variable "polish.usePolishGameApi":

  • polish.usePolishGameApi: Defines whether the J2ME Polish game-engine should be used on MIDP/2.0 devices as well. Possible values are "true" or "false".

The following example uses the game-engine for Nokia MIDP/2.0 devices as well:

<variable name="polish.usePolishGameApi" value="true" if="polish.vendor == Nokia" />

Porting a MIDP/2.0 Game to MIDP/1.0 Platforms

Building the Existing Application

In the first step we use J2ME Polish for building the game. We need to copy and adjust the build.xml file from the provided sample-application of J2ME Polish and we might need to adjust the handling of resources.

Adjusting the build.xml

To build the existing game we need to copy the sample-build.xml file, which resides in the "sample" subdirectory of the J2ME Polish installation, into the the root directory of the game.

The build.xml includes all the necessary information about the project like the MIDlet-classes, the target-devices, the version of the game and so on. Many IDEs support syntax highlighting and auto-completion for this file, but it can also be edited with any decent text-editor.

The J2ME Polish task is divided in 3 subsections: <info>, <deviceRequirements> and <build>. The <info> section contains general information about the project:

<info
  license="GPL"
  name="J2ME Polish"
  version="1.3.4"
  description="A sample project"
  vendorName="Enough Software"
  infoUrl="http://www.j2mepolish.org"
  icon="dot.png"
  jarName="${polish.vendor}-${polish.name}-example.jar" 
  jarUrl="${deploy-url}${polish.jarName}"
  copyright="Copyright 2004 Enough Software. All rights reserved."
  deleteConfirm="Do you really want to kill me?" 
/>

This information can be changed arbitrarily. The name of the application can also contain the name and vendor of the target-device. The above example "${polish.vendor}-${polish.name}-example.jar" results in the name "Nokia-6600-example.jar", when the application is build for the Nokia/N95 device for example. The above property "${deploy-url}" is defined above in the sample build.xml script and is usually empty.

In the following <deviceRequirements> section the target-devices are selected. Since we want to build the application for a MIDP/2.0 device first, we enter any MIDP/2.0 capable device into the requirements, e.g. "Nokia/N95 " or "Nokia/Series60Midp2". The "Series60Midp2" device is a virtual device representing a typical Series 60 phone with MIDP/2.0 support. Such is phone supports additionally the Mobile Media API (mmapi) and the Wireless Messaging API (wmapi). The device selection is very flexible and can select all MIDP/2.0 devices which support the Mobile Media API as well, for example. For keeping this example easy to manage, we use only the selection by the device's identifier:

<deviceRequirements>
  <requirement name="Identifier" value="Nokia/Series60Midp2" />
</deviceRequirements>

The <build> section controls the actual build process. For keeping this example easy, we choose not to use the GUI of J2ME Polish and set the "usePolishGui" attribute to "false". We also need to adjust the <midlet> subelement and enter the full name of our game-class:

<build
  usePolishGui="false"
  resDir="resources"
  workDir="${dir.work}"
>
  <!-- midlets definition -->
  <midlet class="de.enough.polish.example.MenuMidlet" name="Example" />
</build>

The <build> section has many subelements and can automate many tasks from the inclusion of the debugging framework to the signing of the application. These settings are outside of the scope or this article, though.

Managing the Resources

J2ME Polish manages the inclusion of resources automatically for you. Just copy all resources which are needed in any case into the "resources" folder of your project (create it first if necessary).

You can then use the subfolders for including resources only for devices which can use these resources. For example you can place MIDI soundfiles into the "resources/midi" folder and 3gpp video files into the "resources/3gpp" folder. This results in smaller application bundles, since unnecessary resources will not be included at all.

More specific resources are included instead of general resources. You could keep low-color images into the general "resources" folder and use the "resources/BitsPerColor.16+" folder for using high-color versions of these images for devices which support at least 16 bits per pixel (65.536 colors). The same principle can be used for including specific resources for all devices of a specific vendor (e.g. "resources/Sony-Ericsson" or "resources/Nokia") or even for specific devices (e.e. "resources/Nokia/N95 ").

This powerful automatic resource assembling of J2ME Polish is the reason why no subfolders can be used within the application code. All resources are copied into the base-folder of the application. So instead of Image.createImage("images/sprite.png") you need to use Image.createImage("sprite.png") within the source code. This has the extra benefit of saving space within the final jar-file as well as heap-size in the game.

Building the Game

Now all settings necessary for building the game with J2ME Polish have been made, so now call "ant" on the command-line from within the base folder or we can right-click the build.xml file within the IDE and select "Run Ant", "make" or similar.

J2ME Polish will now preprocess, compile, obfuscate, preverify and package the game automatically. When the above settings will be used, we will find the files "Nokia-Series60Midp2-example.jar" and "Nokia-Series60Midp2-example.jad" in the "dist" folder of the project after the build.

The game should now be tested with an emulator or a a real device, to make sure everything works as expected.

Porting the Game to MIDP/1.0

After the successful transition to J2ME Polish, porting a game to MIDP/1.0 is very easy.

Necessary Source Code Changes

Subclasses of GameCanvas need to call the super-implementations of any overridden keyPressed(int) and keyReleased(int)-methods, so that the J2ME Polish game-engine knows about these keys.

When there is a compile error stating that a javax.microedition.lcdui.game-class cannot be found, please ensure that you use import-statements rather than fully qualified class-names in your source-code:

Instead of MyGameCanvas extends javax.microedition.lcdui.game.GameCanvas the import-statement is needed:

import javax.microedition.lcdui.game.GameCanvas;
MyGameCanvas extends GameCanvas

Working Around the Limitations of the Game-Engine

Pixel-Level Collision Detection

Pixel-level collision detection cannot be used on MIDP/1.0 platforms, so we need to set tight collision rectangles for the sprites.

Sprite Transformations

Another limitation of J2ME Polish is that sprite-transformations are only supported for Nokia devices so far. This means that for other devices transformed sprite-images are needed. These can be easily included by putting them in the "resources/NoSpriteTransformations" folder. We can adjust the handling of sprite-transformations by using the preprocessing of J2ME Polish:

//#if !(polish.midp2 || polish.supportSpriteTransformation)
	private final static int MIRROR_SEQUENCE = new int[]{3,4,7,5};
//#endif
[...]
//#if polish.midp2 || polish.supportSpriteTransformation
	this.sprite.setTransform( Sprite.TRANS_MIRROR );
//#else
	this.sprite.setFrameSequence( MIRROR_SEQUENCE );
//#endif

In the above example we use call the transform(int)-method of the sprite only when the device either uses the MIDP/2.0 platform or supports sprite transformation. Otherwise the frame-sequence MIRROR_SEQUENCE will be used instead.

A similar adjustment can be needed during the instantiation of the sprite, when the transformed image has different frame-dimensions:

//#if polish.midp2 || polish.supportSpriteTransformation
	int frameWidth = 10;
	int frameHeight = 8;
//#else
	//# int frameWidth = 6;
	//# int frameHeight = 8;
//#endif
this.sprite = new Sprite( spriteImage, frameWidth, frameHeight );

For an even more flexible approach you can use preprocessing variables. These variables can be defined in the <variables> subelement of the build.xml file:

<variables>
  <variable 
	name="player.frameDimensions" 
	value="10, 8" 
	if="polish.supportSpriteTransformation || polish.midp2"  
  />
  <variable 
	name="player.frameDimensions" 
	value="6, 8" 
	unless="polish.supportSpriteTransformation || polish.midp2"  
  />
  <variable 
	name="player.frameDimensions" 
	value="6, 6" 
	if="polish.identifier == Nokia/7210"  
  />
</variables>

In the above example the frame-dimensions are set to 10x8 for devices which support either the MIDP/2.0 or the sprite transformation. The dimension 6x8 are used for all other devices. For the Nokia/7210 phone the special value of 6x6 is used.

In the source-code a default-value should always be used for cases in which the variables are not set:

//#if player.frameDimensions:defined
	//#= this.sprite = new Sprite( spriteImage, ${player.frameDimensions} );
//#else
	this.sprite = new Sprite( spriteImage, 10, 8 );
//#endif

By using this approach the resources can later be changed without needing to change the source-code.

Using Preprocessing for Device Specific Adjustments

You can use J2ME Polish's preprocessing capabilities for further adjustments of the game. A typical situation is that the MMAPI is used for sound playback. Not all MIDP/1.0 phones do support this API however. In these cases the MMAPI code needs to be hidden from such devices:

//#if polish.midp2 || polish.api.mmapi
	// ...
	Player audioPlayer = Manager.createPlayer	
			( inputStream, "audio/midi" );
	// ....
//#elif polish.api.nokia-ui
	// ...
	Sound sound = new Sound( data,
				 Sound.FORMAT_WAV );
	// ...
//#endif

You might need to adjust the GUI as well. When you are using J2ME Polish's advanced GUI, you can even use MIDP/2.0 features like CustomItems and ItemCommandListener on MIDP/1.0 devices. When the J2ME Polish's GUI is not used, however, any MIDP/2.0 GUI code needs to be hidden from the MIDP/1.0 game:

//#if polish.midp2 || polish.usePolishGui 
	MyCustomItem item = new MyCustomItem();
	this.form.append( item );
//#endif

Another common issue is the usage of a fullscreen-mode. The setFullScreenMode(boolean)-method of the Canvas-class is only available on MIDP/2.0 devices. For MIDP/1.0 devices, the fullscreen-mode can be activated by defining the preprocessing-variable "polish.GameCanvas.useFullScreen" (see below), but the setFullScreenMode(boolean)-method can still be used for MIDP/2.0 devices:

//#if polish.midp2
	setFullScreenMode( true );
//#endif

You can use a multitude of preprocessing symbols and variables. Which of these are available depends on the device and the current setup. Please compare J2ME Polish's device-database and the preprocessing documentation for further information.

Useful Preprocessing Symbols
  • polish.supportSpriteTransformation indicates that MIDP/1.0 Sprite transformations are supported for the current device.
  • polish.api.mmapi is defined when the Mobile Media API is supported by the current device.
  • polish.midp1 / polish.midp2 indicates the current MIDP platform.
  • polish.cldc1.0 / polish.cldc1.1 indicates the current configuration.
  • polish.usePolishGui is enabled when the GUI of J2ME Polish is used.

Checklist for Porting

Use this checklist for a quick overview of the necessary steps for porting a MIDP/2.0 game to MIDP/1.0 devices.

  • All resources needs to be loaded from the base directory, e.g. Image.createImage("/sprite.png")
  • Use import-statements instead of full-qualified class-names in the source code
  • Use preprocessing for adjusting the source code to specific devices, e.g. //#if polish.midp2 || polish.api.mmapi
  • Use the automatic resource assembling for using specific resources for the target-devices.
  • Set tight collision rectangles, since pixel-level collision detection cannot be used on MIDP/1.0 devices
  • Sprite-transformations are only supported for devices which support Nokia's UI-API, so transformed PNGs need to be used for other devices.
  • The super-implementation of keyPressed() and keyReleased() should always be called first, when such methods are overridden.