Getting Started With the MIDP 2.0 Game API

By Qusay Mahmoud, November 2005
The javax.microedition.lcdui.game Package

Version 1.0 of the Mobile Information Device Profile (MIDP) lacked advanced graphical features, and as a result provided limited support for game programming. Some of the device vendors responded to these limitations by adding additional proprietary classes, but using such APIs reduces code portability. MIDP 2.0 has shed many of these limitations by defining a new Game API package, javax.microedition.lcdui.game.

This article reviews MIDP 1.0's limitations for game programming, then presents a quick introduction to the MIDP 2.0 Game API.

Contents
 
Game Programming with MIDP 1.0
Screens and Canvases
Animations
Networked J2ME Games
The Game API Package
The Game Canvas
Layers
The Layer Manager
Sprites
Tiled Layers
Sample Games
Conclusion
For More Information
About the Author


 
Game Programming with MIDP 1.0

MIDP 1.0 makes developing games, including networked games, difficult in several ways:

  • The lack of support for floating-point arithmetic in version 1 of the Connected Limited Device Configuration (CLDC) makes three-dimensional rendering difficult.
  • There is no support for audio, other than simple system beeps.
  • Applications can't read or write to individual pixels and thus can't do basic image manipulation, such as fading an image to black.
  • The Graphics class provides no support for any form of transparency, for using an image mask, or for defining a transparent color.
  • The only network protocol supported is HTTP.
Screens and Canvases

MIDP 1.0 does provide both high-level and low-level GUI APIs, through the Screen and Canvas classes respectively. A typical gaming application will start with a screen of items as a main menu, then branch to screens of other types depending on the user's selections. Screens are very good for navigation, and the Screen class includes easy-to-use standard GUI components like forms, alerts, textboxes, and lists – but the Canvas class is where all the graphical action happens. A typical game that uses a canvas may have a skeleton like the one in Code Sample 1:

Code Sample 1: Canvas-Based Game Skeleton
public class MyCanvas extends Canvas implements Runnable {

   public void run() {
      while(true) {
         repaint(); // update the game state
   }
   
   public void paint(Graphics g) {
      // code for painting
   }
   
   protected void keyPressed(int keyCode) {
      // respond to key events
   }

}
 

The problem here is that the game's logic code is scattered in three different methods, so everything runs in a different thread. When the repaint() method in run() is called, it's hard to predict when the system will call the paint() method. Similarly, when keyPressed() is called, other things may be going on in other parts of the application, and there is no way to know what they are.

Animations

MIDP applications often use double-buffering to make animation smoother. In this approach, you do not draw directly to the display, but instead to a copy of the display, an off-screen buffer that's maintained in memory. When you are done drawing to the buffer, you then copy its contents to the display. The rationale here is that drawing using primitives takes longer than a single memory-copy operation; the same drawing operations are performed, but in the background. The user sees only the net change in the display each time, so the animation flows more smoothly. To implement double-buffering, first create a mutable image the same size as the screen:

...
int width = getWidth();
int height = getHeight();
Image buffer = Image.createImage(width, height);
...
 

Then obtain a graphics context for the buffer:

...
Graphics g = buffer.getGraphics();
...
 

Now draw to the buffer:

...
// animate
// ...
g.drawRexr(20, 20, 25, 30);
...
 

When you need to copy the buffer to the screen, you can override the paint() method to draw the buffer to the device's display:

public void paint(Graphics g) 
   g.drawImage(buffer, 0, 0, 0);
}
 

Note that some MIDP implementations already double-buffer graphics for you. To discover whether a particular implementation does so, invoke the Canvas.isDoubleBuffered() method.

Networked J2ME Games

Multiplayer gaming on wireless devices is an exciting area of development. It's important to be aware that bandwidth limitations in wireless networks can form a bottleneck for a networked game. These limitations will be less of a concern in the future, though, and even now they don't prevent developers from using the network intelligently for game programming. Sharing level downloads can help, or playing against or through a game server that does the heavy lifting. Consider, for example, an implementation of chess in which the device displays the board and animates the moves, but the computations to determine moves are done on the server side.

The Game API Package

The javax.microedition.lcdui.game package, a welcome addition to MIDP in version 2.0, provides classes that enable developers to build rich gaming content for wireless devices. The classes have been implemented in a way that reduces application size and improves performance by minimizing the amount of work done by code written in the Java programming language. The API is based on low-level graphics classes such as Graphics and Image from MIDP 1.0, so you can use this package in conjunction with graphics primitives. A good example would be to use the Game API package to render a complex background, and render visual elements on top of it using graphics primitives like drawLine().

Note that when methods modify the states of the classes in this package, there is no immediate visible effect. The objects store their states, for use in subsequent calls to paint() – which is fine for gaming applications that redraw the entire screen at the end of every game cycle.

The Game API package comprises five classes:

  • GameCanvas is a subclass of javax.microedition.lcdui.Canvas; it provides the basic screen functionality for a game. It includes gaming-oriented methods to query the state of the game keys, and synchronous graphics flushing. Such features simplify building games and improve their performance.
  • Layer is an abstract class that forms the basis for a framework of layered graphics. It represents a visual element in a game, such as a Sprite or a TiledLayer, and has attributes like location, size, and visibility.
  • Sprite is a basic animated layer that can display one of several graphical frames, which are of equal size and are stored in a single Image object. It supports transformations such as flip and rotate, as well as collision-detection methods to simplify implementing the game's logic.
  • TiledLayer represents a visual element composed of a grid of cells that can be filled with a set of tile images. This class enables the developer to create large areas of graphical content without the resource use that a large Image object would require.
  • LayerManager simplifies game development by automating the rendering process. This class is useful for games with multiple layers. It enables the developer to set a view window to represent the user's view of the game, and automatically renders the game's layers to implement the desired view.
The Game Canvas

The GameCanvas class is a subclass of javax.microedition.lcdui.Canvas, but differs in two ways: graphics buffering and the ability to query key states. These features of the GameCanvas class offer you enhanced control to deal with events like keystrokes and screen repainting.

As I mentioned earlier, you'll get smoother animation if you create graphics objects behind the scenes, in a buffer, then update and repaint the screen simply by invoking paint(getGraphics()) and flushGraphics(). If your graphics are simple, you may not notice much difference if you call repaint() and serviceRepaints() instead, but in games with complex graphics, using the GameCanvas methods will make an appreciable difference.

The GameCanvas class makes game programming much easier. You can extend the Canvas class directly to develop your game if you want, but if you do you'll need to implement the Canvas.keyPressed() method, which is called whenever the user presses a button. Errors can arise if the call occurs when multiple threads are doing different things and the code is not properly synchronized. By contrast, if you use GameCanvas you can get the keystroke information whenever you want it, from the getKeyStates() method – and in a more useful form: The keyCode passed to Canvas.keyPressed() gives you information about only a single key, but GameCanvas. getKeyStates() tells you when multiple keys are being pressed simultaneously.

The GameCanvas class simplifies game programming in another way. Instead of spreading the logic across multiple methods running on separate threads, as Canvas does, GameCanvas lets you combine all the game functionality in a single loop, under the control of a single thread. Code Sample 2 is a rewrite of Code Sample 1 that uses GameCanvas, and encloses the game's logic in a single loop:

Code Sample 2: GameCanvas-Based Game Skeleton
public class MyCanvas extends GameCanvas implements Runnable {

   public void run() {
      Graphics g = getGraphics();
      while(true) {
         // update the game state
         // ...
         int k = getKeyStates();
         // respond to key events
         flushGraphics();
      }
   }
}
 
Layers

Layer is an abstract class that represents a visual element of a game. Each Layer can be made visible or invisible, and has a position, width, and height. Subclasses of Layer must implement the paint(Graphics) method so that they can be rendered.

The position of a Layer is expressed as the position of the upper-left corner of its visual bounds. Its x-y position is interpreted relative to the coordinate system of the Graphics object passed to the Layer's paint() method; its initial location in that system is 0-0.

The Layer Manager

The LayerManager class allows you to manage and organize the graphical layers in your game, typically instances of Layer and its subclasses. For example, customarily you create background layers from TiledLayer and player characters from Sprite, both of which are subclasses of Layer.

The order in which layers are painted is the reverse of the order you append them to the LayerManager; in other words, the first layer you append to LayerManager will be painted last.

An important feature of LayerManager is that you can create a graphical painting larger than the device screen, then select what section of the painting to display as a game proceeds. The layer manager's paint(Graphics g, int x, int y) method paints the layer on the screen based on the state of the GameCanvas, and LayerManager. setViewWindow(int x, int y, int width, int height) sets the visible rectangle based on LayerManager's coordinate system.

Sprites

A typical two-dimensional game has characters that move around and interact with each other; in the Game API the graphical versions of these characters are called sprites. The Sprite class represents a graphical image at a point in time. Sprites have Cartesian coordinates; the x-coordinate indicates how far across the screen they should be placed, and the y-coordinate indicates how far down. Sprite supports animation: You can define a sequence of frames using setFrameSequence(int sequence[]), and advance the animation from one frame to the next using nextFrame(). In addition to advancing through a frame sequence, you can change the appearance of a sprite further by applying simple transformations such as rotations and mirror images.

You provide the raw frames used to render a Sprite in a single Image object, which may be mutable or immutable. If you use more than one frame, all of them must be of a single width and height you specify. The rendering engine will divide the Image into the frames at runtime. You can store the same set of frames in any of several different arrangements, as shown in Figure 1.

Figure 1: The Same Set of Frames Stored in Different Arrangements
 

Each frame is assigned a unique index number. The frame in the upper-left corner of the Image has an index of zero, and the remaining frames are numbered consecutively in row-major order; that is, indices are first assigned across the first row, then across the second row, and so on.

A Sprite's frame sequence is a simple array of ints that defines an ordered list of frames to be displayed. The default frame sequence mirrors the list of available frames. Thus the length of the default frame sequence is equal to the number of raw frames. The sprite in Figure 2 has four frames, so its default frame sequence is 0, 1, 2, 3.

Figure 2: The Default Frame Sequence
 

You can define longer frame sequences, using the few frames stored in the Image repeatedly to create animations of any desired length, using a minimum of memory. When using a frame sequence, you manually select the current frame, by calling setFrame(int sequenceIndex), prevFrame(), or nextFrame(). Figure 3 shows how you might use a frame sequence to animate a mosquito. In this sequence the mosquito flaps its wings three times, then pauses for a moment before repeating the cycle.

Figure 3: Animating a Mosquito
 

Sprite implements the concept of a reference pixel, a pixel you want to use rather than the upper-left pixel when specifying a sprite's position. For example, you might identify the pixel at the tip of an arrow, or at the intersection point of an X as the reference pixel for that frame. You can override the default reference pixel, the one at 0-0, by calling defineReferencePixel(int x, int y), specifying the desired pixel's coordinates in the sprite's untransformed frame. Then you can specify the reference cell's position on the screen – and thus the sprite's position – by calling setRefPixelLocation(int x, int y). In Figure 4 a monkey appears to be hanging from a tree branch because the position of its reference pixel has been set to a point at the end of the branch.

Figure 4: Using the Reference Pixel
 

You can apply various transformations to a Sprite. For example, you can rotate it in multiples of 90 degrees, and mirror it about the vertical axis of each of the possible rotations. When a transform is applied, the Sprite is redrawn at a location that leaves the position of the reference pixel in the painter's coordinate system unchanged. The reference cell becomes the center of the transform operation, and the sprite flips or rotates around it. In Figure 5 the monkey's reference pixel remains at (48, 22) when a 90-degree rotation is applied, making it appear as if the monkey is swinging from the branch.

Figure 5: Rotation Around the Reference Pixel
 
Tiled Layers

The TiledLayer class is similar to Sprite in some ways, different in others. A TiledLayer may include animated elements, but has no transformations, frame sequence, or reference pixel. A TiledLayer is a grid of cells, each painted with one frame selected from an Image. Much as you can use ceramic tiles decorated in a few different ways to create a large design on your kitchen floor, you can use the frames in an Image over and over again to create large virtual layers – with no need for an expensively large Image. To provide all the tiles you'll use to fill the TiledLayer's cells, you store a number of equally-sized frames in a single small Image, which can be mutable or immutable. As with sprites, you can store the frames that make up a tile set in several different arrangements, as shown in Figure 6. Pick the arrangement that's most convenient for you.

Figure 6: The Same Tile Set Stored in Different Arrangements
 

Each tile in the Image is assigned a unique index number; the tile in the upper-left corner of the Image has an index of 1, and the remaining tiles are numbered consecutively in row-major order. These are known as static tiles because there is a fixed link between the tile and the image associated with it. In addition, you can define several animated tiles, each of which is a virtual tile that you associate with a static tile dynamically.

The TiledLayer's grid is made up of cells, each of which is the size of one tile in the associated tile set. You specify the content of each cell with an index. A positive tile index indicates a static tile, a negative index indicates an animated tile, and an index of zero indicates an empty cell. Multiple cells may contain the same tile, but a single cell cannot contain more than one tile. Figure 7 illustrates how you can create a simple background using a TiledLayer and the tile set you saw in Figure 6.

Figure 7: A Simple Background Created Using a TiledLayer
 

Notice the negative numbers in the bottom row. The area of water is filled with an animated tile having an index of -1, which is initially associated with static tile 5. You can animate the entire area of water simply by calling setAnimatedTile() to change the associated static tile.

To create a TiledLayer, you first define the tile set and design the layer. Then in the code, you instantiate TiledLayer, passing to the constructor the number of rows and the number of columns in the layer, the Image that holds the tile set, and the width and height of the tiles in that set, which will determine the layer's cell size.

Next you fill each cell with the index of a tile, using setCell(int column, int row, int tileIndex). A static tile set is created when the TiledLayer is instantiated, and you can update it at any time using the setStaticTileSet() method. You create animated tiles with the createAnimatedTile() method, which returns the index to be used for the new animated tile. Note that animated tile indices are always negative and consecutive, beginning with -1. You can change the static tile associated with an animated tile with the setAnimatedTile() method.

Finally, you render the TiledLayer, either manually using its paint() method, or automatically using a LayerManager object.

Sample Games

The Sun Java Wireless Toolkit, formerly called the J2ME Wireless Toolkit, contains sample games that use the Game API. To get a flavor of the effort involved in developing games using GameCanvas and other Game API classes, download the toolkit and take a look at the code for the PushPuzzle game, which you'll find in <toolkit>/apps/games/src/example/pushpuzzle . Figure 8 shows the list of games that come with the toolkit, and Figure 9 shows a sample run of PushPuzzle.

Figure 8: Toolkit Games
 
Figure 9: PushPuzzle
Figure 9: PushPuzzle
 
Conclusion

MIDP 2.0's javax.microedition.lcdui.game package is a framework that simplifies the development of games for MIDP-enabled mobile devices. The Game API package provides five powerful classes that enable developers to build rich gaming content for mobile phones and other devices. The classes have been implemented in a way that greatly eases development, reduces application size, and improves performance by minimizing the amount of work done by code written in the Java programming language. This article's fast-track tutorial to the Game API showed you how to use the classes in this package. To get a sense of the effort involved in developing games using the Game API, download the Sun Java Wireless Toolkit, which comes with sample games.

For More Information
About the Author

Qusay H. Mahmoud provides Java consulting and training services. He has published dozens of articles on Java, and is the author of Distributed Programming with Java (Manning Publications, 1999) and Learning Wireless Java (O'Reilly, 2002).

false ,,,,,,,,,,,,,,,,