Sun Java Solaris Communities My SDN Account Join SDN
 
Article

Game Canvas Basics

 
By Eric Giguere, December 2004  
Download the source

Many, perhaps most, of the early commercial applications developed on the J2ME platform were games. With all the obvious interest in gaming - and the many device-specific gaming extensions developed by different handset vendors - it was no surprise to anyone that the Java Community Process group responsible for defining MIDP 2.0 introduced basic gaming capabilities. Let's take a look at the core of those capabilities, the GameCanvas class.

The GameCanvas class, found in the javax.microedition.lcdui.game package, extends the venerable Canvas class. As you know, Canvas lets an application draw screens using the low-level user-interface API, and also receive key and pointer events directly. The shortcoming of Canvas is that it gives the application no control over when a canvas repaints itself - all it can do is request a repaint - or over how quickly key and pointer events get delivered to the canvas. This lack of control can cause action games and others that are speed-sensitive to feel sluggish and unresponsive. GameCanvas was designed specifically to fix these weak points.

The first thing to remember, though, is that a game canvas is still a canvas, so all the usual behavior is there. The showNotify() and hideNotify() methods are still called when the canvas is shown and hidden. Key and pointer events are still delivered, except for certain key events which optionally can be suppressed. You still have to do all the drawing yourself, and you can still attach Command objects to the canvas. Here's a very simple test canvas you can use inside a MIDlet you run with the J2ME Wireless Toolkit 2.0 or higher:

import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.GameCanvas;

public class DummyGameCanvas extends GameCanvas {

	public DummyGameCanvas( boolean suppress ){
		super( suppress );
	}

	private String getAction( int key ){
		int action = getGameAction( key );
		switch( action ){
		case DOWN:
			return "DOWN";
		case UP:
			return "UP";
		case LEFT:
			return "LEFT";
		case RIGHT:
			return "RIGHT";
		case FIRE:
			return "FIRE";
		case GAME_A:
			return "GAME_A";
		case GAME_B:
			return "GAME_B";
		case GAME_C:
			return "GAME_C";
		case GAME_D:
			return "GAME_D";
		}

		return "";
	}

	protected void hideNotify(){
		System.out.println( "hideNotify" );
	}

	protected void keyPressed( int key ){
		System.out.println( "keyPressed " + key + " "
			+ getAction( key ) );
	}

	protected void showNotify(){
		System.out.println( "showNotify" );
	}
}

Two differences appear when you compare this class to a standard canvas. First, the constructor takes a boolean to indicate whether certain key events are to be suppressed or not. Second, no paint() method is needed; GameCanvas supplies its own implementation. But the most significant difference - the game loop - doesn't show up in DummyGameCanvas.

In general, MIDP user-interface components are event-driven - the system invokes methods directly, in response to device events. These events are queued and delivered to the application one at a time, and there may be a delay between the time the event occurs and the time the application receives it, which particularly affects painting events. The GameCanvas is different: It lets the application poll for key events quickly and repaint the canvas in a timely fashion. This polling and repainting is normally done in a loop on a separate thread, hence the term game loop.

To poll for key events, use getKeyStates(). It returns a bit mask representing the change in state of the action keys - defined by the Canvas class - since the last call to getKeyStates(). Each key's bit value in the mask will be 1 if it's currently down, or has been pressed since the last call, otherwise it will be 0. For example:

	int state = getKeyStates();

	if( ( state & FIRE_PRESSED ) != 0 ){
		// user has pressed the FIRE key
	}

This lets the application check the key state in a tight loop and respond quickly to any changes. Note that key events are still received by the game canvas as usual, except that you can suppress events involving the action keys - and customarily you do. By contrast, you never suppress key events for triggering commands.

To paint itself smoothly, a game canvas uses a technique called double buffering: You perform drawing operations in an off-screen buffer, then quickly copy from the buffer to the visible area of the canvas. The canvas automatically creates and manages the off-screen buffer. The application draws into the buffer using a Graphics instance obtained from getGraphics(). (Note that each call returns a new instance, so you should call the method once outside the game loop and save the reference.) To update the display after drawing, a call to flushGraphics() will force an immediate repaint that's based on the current contents of the off-screen buffer.

The second example is a game canvas that scrolls a randomly generated star field:

import java.util.Random;
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.GameCanvas;

// A simple example of a game canvas that displays
// a scrolling star field. The UP and DOWN keys
// speed up or slow down the rate of scrolling.

public class StarField extends GameCanvas
	implements Runnable {

	private static final int SLEEP_INCREMENT = 10;
	private static final int SLEEP_INITIAL = 150;
	private static final int SLEEP_MAX = 300;

	private Graphics        graphics;
	private Random          random;
	private int             sleepTime = SLEEP_INITIAL;
	private volatile Thread thread;

	public StarField(){
		super( true );

		graphics = getGraphics();
		graphics.setColor( 0,  0, 0 );
		graphics.fillRect( 0, 0, getWidth(), getHeight() );
	}

	// When the game canvas is hidden, stop the thread.

	protected void hideNotify(){
		thread = null;
	}

	// The game loop.

	public void run(){
		int w = getWidth();
		int h = getHeight() - 1;

		while( thread == Thread.currentThread() ){

			// Increment or decrement the scrolling interval
			// based on key presses

			int state = getKeyStates();

			if( ( state & DOWN_PRESSED ) != 0 ){
				sleepTime += SLEEP_INCREMENT;
				if( sleepTime > SLEEP_MAX ) 
					sleepTime = SLEEP_MAX;
			} else if( ( state & UP_PRESSED ) != 0 ){
				sleepTime -= SLEEP_INCREMENT;
				if( sleepTime < 0 ) sleepTime = 0;
			}

			// Repaint the screen by first scrolling the
			// existing starfield down one and painting in
			// new stars...

			graphics.copyArea( 0, 0, w, h, 0, 1,
			Graphics.TOP | Graphics.LEFT );

			graphics.setColor( 0, 0, 0 );
			graphics.drawLine( 0, 0, w, 1 );
			graphics.setColor( 255, 255, 255 );

			for( int i = 0; i < w; ++i ){
				int test = Math.abs( random.nextInt() ) % 100;
				if( test < 4 ){
					graphics.drawLine( i, 0, i, 0 );
				}
			}

			flushGraphics();

			// Now wait...

			try {
				Thread.currentThread().sleep( sleepTime );
			}
			catch( InterruptedException e ){
			}
		}
	}

	// When the canvas is shown, start a thread to
	// run the game loop.

	protected void showNotify(){
		random = new Random();

		thread = new Thread( this );
		thread.start();
	}
}

The user presses the UP and DOWN action keys to increase and decrease the rate of scrolling. Not very exciting, but this canvas could easily form the heart of a simple "shoot-em-up" game. The game canvas gives you the flexibility you need to create simple but responsive games - have fun using it.