Building a J2ME Game: Start with the GameCanvas
A GameCanvas class is a specialized subclass of the Canvas
class that you encountered in the previous
installment
A D V E R T I S E M E N T
of this series. GameCanvas is optimized for gaming because it
provides a special off-screen buffer in which all painting operations are done.
When all painting on this buffer is complete, the buffer is rendered on the
device screen by calling the flushGraphics() method. This double
buffering has the advantage of producing smooth transitions of moving elements
on a screen to counter the flickering that might happen if no buffering were
provided. The size of the buffer is equal to the size of the device screen, and
there is only one buffer per GameCanvas instance.
The GameCanvas class provides a storage mechanism for the state
of game keys, which is a useful way to query user interaction with the game.
This provides a simple way of keeping track of the number of times the user has
pressed a particular key. Calling the method getKeyStates() returns
a bitwise representation of all of the physical game keys, expressed as 1
for pressed and 0 for unpressed, since the last time the method
was called. Only the following game states are identified, which is what you
would expect, keeping in mind the game keys defined by the
Canvas
class: DOWN_PRESSED, UP_PRESSED, RIGHT_PRESSED,
LEFT_PRESSED, FIRE_PRESSED, GAME_A_PRESSED,
GAME_B_PRESSED, GAME_C_PRESSED, and
GAME_D_PRESSED.
Let's build a game canvas by extending the GameCanvas class.
Listing 1 shows the first attempt, while Listing 2 shows the MIDlet that will be
used to run the examples.
Please follow the instructions given in
part one
of this series, which explain how to create, test, and run a MIDlet using the
Wireless toolkit.
package com.j2me.part3;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.GameCanvas;
import java.io.IOException;
public class MyGameCanvas
extends GameCanvas implements Runnable {
public MyGameCanvas() {
super(true);
}
public void start() {
try {
// create and load the couple image
// and then center it on screen when
// the MIDlet starts
coupleImg = Image.createImage("/couple.gif");
coupleX = CENTER_X;
coupleY = CENTER_Y;
} catch(IOException ioex) { System.err.println(ioex); }
Thread runner = new Thread(this);
runner.start();
}
public void run() {
// the graphics object for this canvas
Graphics g = getGraphics();
while(true) { // infinite loop
// based on the structure
// first verify game state
verifyGameState();
// check user's input
checkUserInput();
// update screen
updateGameScreen(getGraphics());
// and sleep, this controls
// how fast refresh is done
try {
Thread.currentThread().sleep(30);
} catch(Exception e) {}
}
}
private void verifyGameState() {
// doesn't do anything yet
}
private void checkUserInput() {
// get the state of keys
int keyState = getKeyStates();
// calculate the position for x axis
calculateCoupleX(keyState);
}
private void updateGameScreen(Graphics g) {
// the next two lines clear the background
g.setColor(0xffffff);
g.fillRect(0, 0, getWidth(), getHeight());
// draws the couple image according to current
// desired positions
g.drawImage(
coupleImg, coupleX,
coupleY, Graphics.HCENTER | Graphics.BOTTOM);
// this call paints off screen buffer to screen
flushGraphics();
}
private void calculateCoupleX(int keyState) {
// determines which way to move and changes the
// x coordinate accordingly
if((keyState & LEFT_PRESSED) != 0) {
coupleX -= dx;
}
else if((keyState & RIGHT_PRESSED) != 0) {
coupleX += dx;
}
}
// the couple image
private Image coupleImg;
// the couple image coordinates
private int coupleX;
private int coupleY;
// the distance to move in the x axis
private int dx = 1;
// the center of the screen
public final int CENTER_X = getWidth()/2;
public final int CENTER_Y = getHeight()/2;
}
Listing 1. MyGameCanvas: A first attempt at building a gaming
canvas
Listing 2 shows the MIDlet that will use this gaming canvas:
package com.j2me.part3;
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.Display;
public class GameMIDlet extends MIDlet {
MyGameCanvas gCanvas;
public GameMIDlet() {
gCanvas = new MyGameCanvas();
}
public void startApp() {
Display display = Display.getDisplay(this);
gCanvas.start();
display.setCurrent(gCanvas);
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
}
Listing 2. MIDlet class to run the game examples
Using both of these classes, create a project with your Toolkit (I have
called this project "Part 3 Game Examples") and then build and run the project.
You will need this image file:
,
named couple.gif,
in the res folder of your project, or you can use a similar-sized image.
Figure 1 shows the expected output.
Figure 1. Building a game: using GameCanvas
The solitary image in the middle of the screen can be moved left and right
with the help of the left and right game keys, respectively. In the code shown
in Listing 1, this is achieved by querying the game states in the
checkUserInput() method and then calling the calculateCoupleX()
method with this game state. As you can see, by bit-wise ORing the
state with the supplied Constants in the GameCanvas
class, you can easily determine which key the user has pressed and act
accordingly. The x axis position of the image is moved left or right of the
current position by adding or subtracting delta x (dx) from it.
The image is rendered on the screen in the updateGameScreen()
method. This method is passed the current Graphics object. This
object is created for you by the GameCanvas class, and there is
only one such object per GameCanvas. The method clears this
graphics buffer of any previous renderings, draws the couple image based on the
current coupleX variable (and the currently unchanging
coupleY variable) and then flushes this buffer on the device screen.
The infinite loop in the run() method follows the game structure
that I described in the sidebar earlier. This loop sleeps for 30 milliseconds
before going on another cycle to determine the user input and refresh the
buffer. You can experiment with this value to slow down or speed up the refresh
rate.
Finally, notice that the MyGameCanvas constructor calls its
superclass GameCanvas's constructor with a parameter value of
true. This indicates that the normal key event mechanism, inherited from
the Canvas class, should be suppressed, as this code does not
require these notifications. The game state is adequately handled by the key
state information, which is fetched from the getKeyStates() method.
By suppressing the notification mechanism for "key pressed," "key released," and
"key repeated," the game performance is improved.
|