Go Fishing with the New MIDP 2.0 Game APIs

ames and mobile devices seem to be a natural fit. Since MIDP 1.0 first became available, the majority of applications written for the platform have been games. As MIDP grew in popularity, the need for APIs specific to gaming also increased. This article discusses some of the enhancements of MIDP 2.0 that assist developers in writing games.

It’s All About Rendering
A large part of writing games has to do with rendering images to the screen. Near real-time access to video memory is critical to ensure smooth, even display of images. If you can’t control when the screen refreshes with graphical updates, there is a good chance that movements and on-screen animation may be jerky, or the game event may make two or three passes through the primary game loop before a single screen refresh takes place, interfering with the user’s experience.

MIDP 2.0 introduced a few new classes that can assist you tremendously in creating rich and smoothly-animated graphics. You can find them in the javax.microedition.lcdui.game package:

  • GameCanvas
  • Layer
  • LayerManager
  • Sprite
  • TiledLayer

This article discusses just what each of these classes does and how they can make your life easier.

GameCanvas
GameCanvas extends the MIDP Canvas class and provides a few tools for managing the state of a game. An off-screen buffer is supported along with a getGraphics() method. The getGraphics() method allows you to access to the Graphics instance at any point and time to render images to the screen. This is different than the Canvas class, where all painting must be done within the call to paint(), as this is the only context in which the Graphics object is exposed. Constant access to the Graphics instance allows visuals to be updated directly rather than waiting until the paint() method is called during a screen refresh. This is a big advantage when you’re writing games.

Any manipulation of the Graphics object in GameCanvas automatically writes to the off-screen buffer. Changes are flushed to the screen with a call to flushGraphics(), which transfers pixel data from the off-screen buffer into video memory. This transfer is done at a low level in the device code and is very fast. The following example shows a square being drawn using the off-screen buffer.

Graphics g = getGraphics();g.drawRect(10, 10, 100, 100);flushGraphics();

The Graphics object is the same as what is passed to the Canvas.paint() method, however, any updates to the video display are written to the off-screen buffer. flushGraphics() then causes the square to actually appear on the screen.

Monitoring Key States
Another important aspect of writing games is having immediate access to the current state of a given key, as opposed to merely being given a notification that a key press has taken place at some point in the past. Games states often rely on finite control over the state of a key, such as the up, down, left, right, and fire keys.

GameCanvas offers some help here as well. The getKeyStates() method allows you to query specific key states directly. Although the return value for this method is an int, the value can represent multiple key states. Specific key states are discovered using the bitwise & operator. The following example checks for several key states:

int keyState = getKeyStates();if ((keyState & LEFT_PRESSED) != 0) {  System.out.println("Left Key Pressed");}if ((keyState & RIGHT_PRESSED) != 0) {  System.out.println("Right Key Pressed");}if ((keyState & UP_PRESSED) != 0) {  System.out.println("Up Key Pressed");}if ((keyState & DOWN_PRESSED) != 0) {  System.out.println("Down Key Pressed");}if ((keyState & FIRE_PRESSED) != 0) {  System.out.println("Fire Key Pressed");}

Ode to the Etch-a-sketch
Using the GameCanvas tools discussed thus far, it’s possible to create a simple sketch game can be created. Listing 1 shows the game loop that monitors key states and draws lines in the direction of the current key state.

Figure 1 shows an example of what the sketch game looks like when played.


Figure 1. The Sketch Game: Select The image shows an example of running the Sketch game.
 
Figure 2. Sprite Fish: This is the Fish imageused to create a Sprite.

Use of Layering
The game APIs are built with the concept of graphics layering in mind. This helps them support more complex interactions of visual elements on a single display. MIDP 2.0’s Layer is an abstract class that defines a layer as being a visual element with a position and size that is movable. Layers can also be shown and hidden. There are two types of Layers implemented by the APIs, Sprite and TiledLayer. There is also a LayerManager class that provides a way to manage relationships between layers.

Using Sprites to Animate Images
This next section builds on the sketch game but replaces the line drawing with a graphic of a fish. The arrow keys are used to move the fish around the screen.

The Sprite class provides tools for manipulating images and rendering animated graphics. You can create a Sprite with an image containing one or more frames. Only one frame of a Sprite is visible at a given time. However, a game may sequence through the frames by calling nextFrame() or previousFrame(), causing the Sprite image to change and to animate the image.

Sprite offers other tools for animation as well, such as flipping and rotating about a specified reference point. Define this reference point by calling Sprite.defineReferencePixel() and providing an x, y pixel location. Then simply rotate the Sprite about this point by making calls to Sprite.setTransform() and passing one of the following values:

TRANS_MIRROR

 

Reflects the Sprite about its vertical center

TRANS_MIRROR_ROT180

 

Reflects the Sprite about its vertical center and rotates it by 180 degrees.

TRANS_MIRROR_ROT270

 

Reflects the Sprite about its vertical center and rotates it by 270 degrees.

TRANS_MIRROR_ROT90

 

Reflects the Sprite about its vertical center and rotates it by 90 degrees.

TRANS_NONE

 

Applies no transform to the Sprite. The Sprite returns to its original position.

TRANS_ROT180

 

Rotates the Sprite by 180 degrees.

TRANS_ROT270

 

Rotates the Sprite by 270 degrees.

TRANS_ROT90

 

Rotates the Sprite by 90 degrees.

Something Fishy
Sprite handles images of arbitrary shape. However, a height and width need to be specified, in pixels, when the image is loaded into the Sprite.

For this example, I created a 32 x 32 pixel image of a fish using Microsoft Paint and saving it as a PNG file. The image is shown in Figure 2.

Figure 3. Running the Fish Loop: Fish moving and responding to the user pressing the up, down, right, and left keys.

You can make the fish image accessible to a MIDlet by placing the PNG file within the res directory of Wireless Toolkit app structure.

Next, use the image to create a Sprite:

Image image = Image.createImage("/fish.png");Sprite sprite = new Sprite(image, 32, 32);

Defining a reference pixel within the Sprite (actually, you can even define the pixel outside the bounds of the Sprite) allows you to manipulate the Sprite about the specified point. The following code defines areference pixel, relative to the dimensions of the Sprite, and then sets the Sprite’s reference pixel to position 20, 20 within the screen.

fish.defineReferencePixel(3,3);fish.setPosition(20,20);

Calling the Sprite supports a method called paint(), which is called when a game wishes to render the Sprite to the screen after changing various states. If GameCanvas is used, a call to flushGraphics() is also required to actually render the image from the off-screen buffer onto the screen.

Listing 2 modifies the Sketch game created earlier. Instead of continually drawing lines, the screen is repainted during the game cycle to show the fish Sprite moving in response to the user pressing the left, right, up, and down keys.

The effect of running the fish loop is shown in Figure 3.

Create Backgrounds Using TiledLayer
Although the TiledLayer layer can be used for a number of things, it lends itself quite well to background images. As with Sprite, TiledLayer is loaded with an image. The image is broken into tiles based on parameters provided to the constructor specifying the number of columns and rows that are desired. The result is a grid of tiles with each tile representing a piece of the provided image. An example image is shown in Figure 4.

Figure 4. Loaded TiledLayer: This example image is divided into four tiles.

The image in Figure 4 can be loaded into a TiledLayer using the following code:

Image bkgrndImage = Image.createImage("/bkgnd.png");int rows = getHeight() / 16;int cols = getWidth() / 16;TiledLayer bkgnd =     new TiledLayer(cols, rows, bkgrndImage, 16, 16);

In this code example, the number of columns and rows are calculated based on the screen height and width and the desired size of each tile. This allows tiles to be rendered onto the screen in a predictable manner regardless of screen size.

At this point, an instance of TiledLayer has been created and the image has been partitioned into what are called static tiles. Static tiles are the visual elements used to create images using TiledLayer. In this example, the image height is 16 pixels and the width is 64 pixels. Since the image is smaller than the number of requested columns and rows, the image is repeated within the TiledLayer to fill all the requested tiles.

Managing Cells in a TiledLayer
To render different images, the static tiles are mapped to cells. A cell represents a specific square on the screen, defined within the TiledLayer boundaries. A cell is assigned a tile, allowing the static tiles to be arranged and repeated as desired. For example, the following code repeats tile 1 four times across the screen. The result is shown in Figure 5.

bkgnd.setCell(0, 0, 1);bkgnd.setCell(1, 0, 1);bkgnd.setCell(2, 0, 1);bkgnd.setCell(3, 0, 1);bkgnd.paint(getGraphics())flushGraphics();

It’s worth noting that in the setCell() method the column and row indexes start at 0, whereas the tile index starts at 1.

In order to produce animated scenery, calls to the setCell() method can be made from within the game loop. Listing 3. shows how to change the scenery to give the appearance of the fish moving in relation to the scenery.

In Listing 3, each pass through the game loop adjusts the TiledLayer based on the key press states. This is captured by the influence variable. If influence is < 0, then the scenery is cycled to the right (giving the appearance of the fish moving to the left). If influence is > 0, the scenery is cycled to the left. Set influence to 0 and the scenery will stop when the user presses the up or down key.In order to adjust the rate at which the scenery is adjusted, a shiftDelayFactor is introduced so the scenery is not updated on every loop. Adjusting the scenery can also take place in a separate loop on another thread to achieve a similar result. Figure 6 shows how the scenery looks underneath the fish.


Figure 5. Repeating Tiles: The image shows the tile in position 1 repeated four times.
 
Figure 6. Animated Scenery: The image shows how the scenery looks running below the fish.

Managing Multiple Layers
As different types of layers are introduced to a game, your ability to manage how those layers interact becomes important, especially when you need to move them in front and behind one another. The LayerManager provides a way to manage the z-order (order of which layers from front to back). Using the fish example, you may want the fish to swim behind the scenery rather than through it. Introducing a LayerManager allows you to achieve this with a few lines of code.

First, create the LayerManager and add the background and fish layers to the manager. The order in which you add the layers is important because it determines the z-order. The first item you add (in this case the background) is the closest to the user. The second item you add (the fish) is the furthest from the user, causing the fish to swim behind the scenery when the two layers come to occupy the same physical space.

LayerManager manager = new LayerManager();manager.append(bkgnd);manager.append(fish);

In order for the LayerManager to be successful in managing the z-order, it calls the LayerManager.paint() method instead of the individual Sprite and TiledLayer paint() methods. The LayerManager then calls each layer’s paint() method in the proper order. To enhance the fish example, clear the screen and call the manager’s paint() method:

clearScreen(getGraphics());manager.paint(getGraphics(), 0, 0);

Note that an x, y coordinate is also specified in LayerManager.paint() method. This allows the location of the paint context to change relative to the screen. In this example, the coordinates of the LayerManager and the screen are set to be equal.

Greater Rendering Flexibility
MIDP 2.0 offers flexibility in rendering visual elements to the screen. This is critical to ensure smooth performance for games. Since objects such as Sprite and TiledLayer perform the actual rendering at a lower level, the performance is much better than if the same operations were implemented in Java code within the application. The bottom line is Layer and GameCanvas provide fine-grained control over the user interface while making image rendering easier and efficient.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

The Latest

microsoft careers

Top Careers at Microsoft

Microsoft has gained its position as one of the top companies in the world, and Microsoft careers are flourishing. This multinational company is efficiently developing popular software and computers with other consumer electronics. It is a dream come true for so many people to acquire a high paid, high-prestige job

your company's audio

4 Areas of Your Company Where Your Audio Really Matters

Your company probably relies on audio more than you realize. Whether you’re creating a spoken text message to a colleague or giving a speech, you want your audio to shine. Otherwise, you could cause avoidable friction points and potentially hurt your brand reputation. For example, let’s say you create a

chrome os developer mode

How to Turn on Chrome OS Developer Mode

Google’s Chrome OS is a popular operating system that is widely used on Chromebooks and other devices. While it is designed to be simple and user-friendly, there are times when users may want to access additional features and functionality. One way to do this is by turning on Chrome OS