Вы находитесь на странице: 1из 21

SDMD

1/20

J2ME Gaming API: An Overview


J2ME is a popular platform for developing games for wireless devices. With MIDP
2.0, a new package is called javax.microedition.lcdui.game, has been introduced that
provides several gaming constructs that would only have been possible in MIDP 1.0
with a great deal of repetitive code. There are only five classes in the
javax.microedition.lcdui.game
package:
GameCanvas,
Layer,
Sprite,
TiledLayer,
and
LayerManager. These five classes are enough to provide a platform for the development of games with a wide
range of capabilities.
The Layer class is the superclass of the Sprite and TiledLayer classes. This class abstracts the behavior of a
visual element in a game. This element can be a sprite, which represents an independent graphic (or a collection of
graphics for animation) that can be moved around the game screen, or a tiled layer, which represents a graphic that
can be used to create vast game backgrounds with only a handful of images. You use the Layer classes for
positioning and visibility. The subclasses override the paint(Graphicsg) method, which has the task of rendering
the elements on the screen.
The LayerManager class provides a convenient mechanism to manage the various visual elements of a game
(sprites and tiled layers) by rendering the proper layer in the proper order.
The GameCanvas class is made useful by extending the functionality of the Canvas class. It provides an off-screen
buffer, to which all rendering operations are done before flushing them on the device screen. It also provides an easyto-use mechanism to query the current keys being pressed by the user.

A Very Short Primer on Game Building


A game or animation is built according to the principle of repetitively executing a piece of code. This piece of code
tracks the value of instance variables and updates the game state accordingly. Based on the game state, the code
then draws/paints/repaints the game screen with the elements that make up the game. The values of the instance
variables may change because of either user interaction or internal game behavior.
The repetitive execution is effected by putting the repetitive code in an infinite loop. Before entering the loop, an
instance variable may be checked to see if the game should still be running, and if not, the loop may be exited. The
code in the loop should allow the current thread of execution to sleep every few milliseconds to control the rate at
which the update to the instance variables is done (in effect, how fast the game screen should be refreshed).
To put it in coding terms:
//mainclass
publicMainClass{
privateGameCanvascanvas=newMyGameCanvas();

publicMainClass(){
//startathreadthatwillruninfinitely
canvas.start();
}

//restofthecode
}

//theactualdrawingclass
publicMyGameCanvasextendsGameCanvasimplementsRunnable{

APIIT /UCTI

SDMD

2/20

publicMyGameCanvas(){
//instantiationcode
}

publicvoidstart(){
//doinitialization
//andthenstartathread
Threadrunner=newThread(this);
runner.start();
}

privatevoidrun(){

//orwhile(keeprunning=true)
//wherekeeprunningisaninstancevariable
while(true){

//checksifthegamehasreached
//someboundarystatesorspecialconditions
verifyGameState();

//getsinputfromuserand
//updatesinstancevariables
//thatdescribethegameselements

checkUserInput();

//paintselementsonscreentoreflect
//thecurrentgamestateusingthecurrent
//graphicsobject
updateGameScreen(getGraphics());

//controltherateatwhichscreenupdatesaredone
Thread.sleep(milliseconds);
}
}
}
We will use this structure to develop a game in the following sections.

Building a J2ME Game: Start with the GameCanvas


A GameCanvas class is a specialized subclass of the Canvas class.. 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.

APIIT /UCTI

SDMD

3/20

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.

importjavax.microedition.lcdui.Image;
importjavax.microedition.lcdui.Graphics;
importjavax.microedition.lcdui.game.GameCanvas;
importjava.io.IOException;
publicclassMyGameCanvas
extendsGameCanvasimplementsRunnable{
publicMyGameCanvas(){
super(true);
}
publicvoidstart(){
try{
//createandloadthecoupleimage
//andthencenteritonscreenwhen
//theMIDletstarts
coupleImg=Image.createImage("/couple.png");
coupleX=CENTER_X;
coupleY=CENTER_Y;
}catch(IOExceptionioex){System.err.println(ioex);}
Threadrunner=newThread(this);
runner.start();
}
publicvoidrun(){
//thegraphicsobjectforthiscanvas
Graphicsg=getGraphics();
while(true){//infiniteloop

//basedonthestructure

//firstverifygamestate
verifyGameState();

//checkuser'sinput
checkUserInput();

//updatescreen

APIIT /UCTI

SDMD

4/20

updateGameScreen(getGraphics());
//andsleep,thiscontrols
//howfastrefreshisdone
try{
Thread.currentThread().sleep(30);
}catch(Exceptione){}
}
}
privatevoidverifyGameState(){
//doesn'tdoanythingyet
}
privatevoidcheckUserInput(){
//getthestateofkeys
intkeyState=getKeyStates();
//calculatethepositionforxaxis
calculateCoupleX(keyState);
}
privatevoidupdateGameScreen(Graphicsg){
//thenexttwolinesclearthebackground
g.setColor(0xffffff);
g.fillRect(0,0,getWidth(),getHeight());

buildGameScreen(g);
//drawsthecoupleimageaccordingtocurrent
//desiredpositions
g.drawImage(
coupleImg,coupleX,
coupleY,Graphics.HCENTER|Graphics.BOTTOM);
//thiscallpaintsoffscreenbuffertoscreen
flushGraphics();
}
privatevoidcalculateCoupleX(intkeyState){
//determineswhichwaytomoveandchangesthe
//xcoordinateaccordingly
if((keyState&LEFT_PRESSED)!=0){
coupleX=dx;
}
elseif((keyState&RIGHT_PRESSED)!=0){
coupleX+=dx;
}
}
//thecoupleimage
privateImagecoupleImg;

APIIT /UCTI

SDMD

5/20

//thecoupleimagecoordinates
privateintcoupleX;
privateintcoupleY;
//thedistancetomoveinthexaxis
privateintdx=1;
//thecenterofthescreen
publicfinalintCENTER_X=getWidth()/2;
publicfinalintCENTER_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:

importjavax.microedition.midlet.MIDlet;
importjavax.microedition.lcdui.Display;
publicclassGameMIDletextendsMIDlet{
MyGameCanvasgCanvas;
publicGameMIDlet(){
gCanvas=newMyGameCanvas();
}
publicvoidstartApp(){
Displaydisplay=Display.getDisplay(this);
gCanvas.start();
display.setCurrent(gCanvas);
}
publicvoidpauseApp(){
}
publicvoiddestroyApp(booleanunconditional){
}
}
Listing 2. MIDlet class to run the game examples
Using both of these classes, create a project with your Toolkit and then build and run the project. You will need this
image file: , named couple.png, in the res folder of your project, or you can use a similar-sized image. Figure 1
shows the expected output.

APIIT /UCTI

SDMD

6/20

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.

Defining Game Characteristics


A game where all you have to do is to move the central character left and right is not very fun. Let's make some
modifications to the game skeleton in Listing 1 to define this game a little better. To start with, specify a boundary for

APIIT /UCTI

SDMD

7/20

your game. It is essential to do this, because it helps to make your game consistently sized across different devices.
To do this, start by defining some constants that are shown in the code here:
//thegameboundary
publicstaticfinalintGAME_WIDTH=160;
publicstaticfinalintGAME_HEIGHT=160;
//theshiftedx,yoriginofthegame
publicfinalintGAME_ORIGIN_X=(getWidth()GAME_WIDTH)/2;
publicfinalintGAME_ORIGIN_Y=(getHeight()GAME_HEIGHT)/2;
//theheightofsectionsbelowandabovethecouple
publicfinalintSECTION_HEIGHT=64;
//thebaseonwhichthecouplewillmove
publicfinalintBASE=GAME_ORIGIN_Y+GAME_HEIGHTSECTION_HEIGHT;
//themaxheightthecouplescanjump
publicfinalintMAX_HEIGHT=32;
(Note that I have introduced a game characteristic that indicates that this couple may soon be jumping on the screen,
with the help of the MAX_HEIGHT constant.) On the screen, these constants help define the boundary of the game
and its sole element (the couple), as shown in Figure 2.

Figure 2. Defining the game boundaries using the game constants


Of course, now you need to modify the rest of the code to use these constants. Add a new method to Listing 1 called
buildGameScreen(Graphicsg), as shown in code here:
privatevoidbuildGameScreen(Graphicsg){
//setthedrawingcolortoblack

APIIT /UCTI

SDMD

8/20

g.setColor(0x000000);
//drawthesurroundingrectangle
g.drawRect(GAME_ORIGIN_X,GAME_ORIGIN_Y,GAME_WIDTH,GAME_HEIGHT);
//drawthebaseline
g.drawLine(GAME_ORIGIN_X,BASE,GAME_ORIGIN_X+GAME_WIDTH,BASE);
//drawthemaximumlinetowherethecouplecanjumpto
g.drawLine(GAME_ORIGIN_X,BASEMAX_HEIGHT,
GAME_ORIGIN_X+GAME_WIDTH,BASEMAX_HEIGHT);
}
Also add a call to this method in the updateGameScreen() method, before the couple image is drawn. The game
boundaries have been defined and the only thing left to do is to make the starting position for the couple image as the
BASE and not CENTER_Y. Change this in the start() method by setting coupleY=BASE;.
The couple image can move left and right with the left and right game keys, but now we ensure that it does not move
past the game boundary. This was a problem in Listing 1, too, but in that case, the image simply vanished off the
screen, as the boundary was the edge of the screen. It will look very odd if the image went past the boundaries now.
Therefore, modify the left and right press actions in the calculateCoupleX() method to restrict movement beyond
the boundaries. This modified method is listed here:
privatevoidcalculateCoupleX(intkeyState){
//determineswhichwaytomoveandchangesthe
//xcoordinateaccordingly
if((keyState&LEFT_PRESSED)!=0){
coupleX=
Math.max(
GAME_ORIGIN_X+coupleImg.getWidth()/2,
coupleXdx);
}
elseif((keyState&RIGHT_PRESSED)!=0){
coupleX=
Math.min(
GAME_ORIGIN_X+GAME_WIDTHcoupleImg.getWidth()/2,
coupleX+dx);
}
}
This method now uses Math.max() and Math.min() methods to restrict the couple image within the game
boundaries. Notice that it also incorporates the width of the image in these calculations.
I spoke earlier about making the couple image jump around on the screen. Let's see how this can be achieved by
adding a method to move the image along the Y axis, independently of the user playing the game.
Import java.util.Random class to Listing 1 as shown here:
//Randomnumberwouldbegeneratedusingthisclass
importjava.util.Random;

APIIT /UCTI

SDMD

9/20

Add four new instance variables to Listing 1, called up, jumpHeight, and random, as shown here:
//aflagtoindicatewhichdirectionthecouplearemoving
privatebooleanup=true;
//thedistancetomoveintheyaxis
Privateintdy=1;
//indicatestherandomjumpheight,calculatedforeveryjump
privateintjumpHeight=MAX_HEIGHT;

//randomnumbergenerator
publicRandomrandom=newRandom();
As you can see, jumpHeight is initialized to MAX_HEIGHT. This jumpHeight variable will be calculated for each
jump that the couple make and it will be set to a random value each time. This is shown in the
calculateCoupleY() method shown here:

privatevoidcalculateCoupleY(intkeyState){
//checkifthecouplewereonthewayup
if(up){
//ifyes,seeiftheyhavereachedthecurrentjumpheight
if((coupleY>(BASEjumpHeight+coupleImg.getHeight()))){
//ifnot,continuemovingthemup
coupleY=dy;
}elseif(coupleY==(BASEjumpHeight+coupleImg.getHeight())){
//ifyes,startmovingthemdown
coupleY+=dy;
//andchangetheflag
up=false;
}
}else{
//thecoupleareontheirwaydown,havetheyreachedbase?
if(coupleY<BASE){
//no,sokeepmovingthemdown
coupleY+=dy;
}elseif(coupleY==BASE){
//havereachedbase,socalculateanew
//jumpheightwhichisnotmorethanMAX_HEIGHT
inthyper=random.nextInt(MAX_HEIGHT+1);
//butmakesurethatthisitisatleastgreaterthantheimageheight
if(hyper>coupleImg.getHeight())jumpHeight=hyper;

APIIT /UCTI

SDMD

10/20

//movetheimageup
coupleY=dy;
//andresettheflag
up=true;
}
}
}
Also add a call to this method in the checkUserInput() method . Note that since this method doesn't depend on
the user pressing the up or down game keys, it has no use for the keyState information. But this value is passed to
it nonetheless, in order to maintain conformity with the calculateCoupleX() method. This method starts moving
the couple image by changing the coupleY variable in the upwards direction until it reaches the current jump height
(which is the MAX_HEIGHT at starting). Once it reaches this jump height, it starts moving it in the opposite direction
until it reaches BASE. At this point, a new jump height value, between the MAX_HEIGHT and couple image heights, is
randomly calculated and the couple start jumping again.
The overall effect is of a randomly jumping couple who can be moved left and right by the user playing the game. A
snapshot is shown in Figure 3.

Figure 3. Game snapshot

Building a J2ME Game: Creating Backgrounds Using the TiledLayer Class


In this section, you will add some color to the game by providing a background using the TiledLayer class. The
game is divided into three sections: the top section can be thought of as the sky, the middle section in which the
couple jump is the earth, and the bottom section is the sea. These three sections can be filled easily using three
colored images of the size 32 by 32 pixels each, one for each section. However, each section is bigger than 32 by 32
pixels, and the TiledLayer class is used to define large areas like these with small images.
To start, divide the game screen into squares of 32 by 32 each and number each row and column, starting with an
index of 0. This is shown in Figure 4 and results in a 5-by-5-cell background.

APIIT /UCTI

SDMD

11/20

Figure 4. Divide the game screen into individual cells


Thus, cells (0, 0) to (1, 4) are to painted with a sky image; cells (2, 0) to (2, 4) are to be painted with an earth image,
and cells (3, 0) to (4, 4) are to be painted with a sea image. You will do this with the image shown in Figure 5.

Figure 5. Background image


The first 32 by 32 cell represents the earth image, the second represents the sea, and the last represents the sky.
When you use the TiledLayer class, these images are numbered starting from index 1 (not 0; therefore, earth is 1,
sea is 2, and sky is 3). The TiledLayer class will take this one image and divide it into three separate images used
for rendering the game background. In our case, we want the TiledLayer class to render a 5-by-5-cell background
using cells of 32 by 32 pixels each. This is achieved by the following code:
//loadtheimage
backgroundImg=Image.createImage("/tiledLayer1.png");
//createthetiledlayerbackground
background=newTiledLayer(5,5,backgroundImg,32,32);
As you can see, the first two parameters to the TiledLayer constructor represent the total background size, the
next parameter represents the image, and the last two parameters represents the size of each cell. This size will be
used by the TiledLayer class to carve the image into its individual background cells.
All that is now left is to set each cell with its respective image. The full code to create the background is listed below
in a method called createBackground(). You will need to add a call to this method from the start() method of
the MyGameCanvas class. Once this is done, add a call to paint this background using background.paint(g) at
the end of the buildGameScreen() method, which will render it to screen.
Add two new instance variables to Listing 1, called backgroundImg,background as shown here:
//animagereferencetocontainbackgroundimage
privateImagebackgroundImg;
//atiledlayerreferencetosplitbackgroundsections
privateTiledLayerbackground;

APIIT /UCTI

SDMD

12/20

//createsthebackgroundusingTiledLayer
privatevoidcreateBackground()throwsIOException{
//loadtheimage
backgroundImg=Image.createImage("/tiledlayer1.png");
//createthetiledlayerbackground
background=newTiledLayer(5,5,backgroundImg,32,32);
//arraythatspecifieswhatimagegoeswhere
int[]cells={
3,3,3,3,3,//sky
3,3,3,3,3,//sky
1,1,1,1,1,//earth
2,2,2,2,2,//sea
2,2,2,2,2//sea
};
//setthebackgroundwiththeimages
for(inti=0;i<cells.length;i++){
intcolumn=i%5;
introw=(icolumn)/5;
background.setCell(column,row,cells[i]);
}
//setthelocationofthebackground
background.setPosition(GAME_ORIGIN_X,GAME_ORIGIN_Y);

}
The final result will look like Figure 6.

Figure 6. Game with a background added

Building a J2ME Game: Sprites and LayerManager


So far, I have used an image of a jumping couple to showcase a game element. This element is currently rendered as
an image; but it makes sense to render it as a sprite instead. A sprite is a term in gaming parlance that refers to any
visual element, usually an animated image, that can be moved around the screen independently of the other
elements. The Sprite class is used to represent sprites in the MIDP 2.0 gaming API. This class provides methods to
animate the sprite based on a handful of images, similar to the way backgrounds are created using the TiledLayer
class. More importantly, it provides methods to check collisions with other game elements, including images, sprites,
or tiled layers.

APIIT /UCTI

SDMD

13/20

Let's start by converting the existing couple image into a sprite. To showcase animation, I will use the image showed
in Figure 7, which is the couple image duplicated over with a different color into two different frames, each 10 by 10
pixels each.

Figure 7. Frames for couple Sprite animation


Similar to the TiledLayer class, the Sprite class requires that the size of each frame be passed in to its
constructor. This is shown here:
coupleSprite=newSprite(coupleImg,10,10);
This code, added after the creation of the couple image in thestart() method, creates a couple sprite with two
frames of 10 by 10 pixels each, numbered from 0 onwards. Thus, to alternate between the sprite images, you can call
the nextFrame() method, which gets the next image in the current sequence. Since there are only two images in
this sprite sequence, they will be shown one after another. If you want to make a particular frame/image the current
displayable image for a sprite in a longer frame sequence, you can do so by using the method setFrame(int
sequenceNo). In this case, add coupleSprite.nextFrame() in the updateGameScreen() method.
You now don't want the couple image to be painted on the screen. Before the couple sprite can be painted on the
screen, you need to define a reference pixel for it. Think of this as an origin around which all painting operations are
done. By default, a sprite is painted with its upper left corner as its origin. Similar to the way you set the reference of
the couple image using the Graphics.HCENTER|Graphics.BOTTOM code, you need to define a reference pixel
for the sprite. This is shown here:
coupleSprite.defineReferencePixel(coupleSprite.getWidth()/2,
coupleSprite.getHeight());
Add this snippet after the creation of the sprite as described earlier. Now, instead of positioning the sprite based on its
original origin, you will position it based on this reference pixel as, shown here:
coupleSprite.setRefPixelPosition(coupleX,coupleY);
coupleSprite.paint(g);
The last line in this code snippet paints the sprite on the graphics object that is passed to it. As expected, you will
need to insert these lines in the updateGameScreen() method in lieu of the lines that painted the couple image.
The final result will look exactly the same as before, except the jumping couple will be replaced with a flickering
jumping couple!
Before going forward, make sure that you change all references to the coupleImg variable to coupleSprite in
thecalculateX() and calculateY() methods.

Managing Layers Using the LayerManager


Recall that both the Sprite and TiledLayer classes extend the Layer class. A game may contain at least one
TiledLayer and several Sprite classes. With so many layers to control, the LayerManager class comes in
handy. This class provides methods to add, remove, or insert layers from a game, and also provides a single method
to paint all of these layers to the underlying Graphics object. This means that you don't need to individually call the
paint() method of each of the layers of a game.
An instance of LayerManager is created using its no-args constructor. Layers are then added, removed, or inserted
into it by using the methods append(Layerlayer), remove(Layerlayer), and insert(Layerl,int
index), respectively. The order in which layers are added is important, because this order determines which layer is
painted first, as this becomes the z-order index. The layer at index 0 is painted on top of all the other layers, and
hence, is closest to the user, and so on.

APIIT /UCTI

SDMD

14/20

In our game, the start() method now needs to be modified, as shown here:
//createsthelayermanager
manager=newLayerManager();
//andaddslayerstoit
manager.append(coupleSprite);
//createsthegamebackground
createBackground();
manager.append(background);
As you can see, the coupleSprite layer will be closest to the user and the background layer will be farthest back,
based on their indices. The buildGameScreen() method now does not need to paint the background (as the
LayerManager will paint the background now), and therefore the background.paint(g) line needs to be
removed from this method. Finally, in the previous section, you used the coupleSprite to paint it on the screen
instead of the coupleImage. Now even that is not required, as the LayerManager will do this for you. Remove
coupleSprite.paint(g) from the updateGameScreen() method and replace it with manager.paint(g,0,
0). As you can see, all calls to individual layers' paint() methods have been replaced with a single call to the
LayerManager's paint() method. The last two parameters represent the location at which the manager should
paint. Since the background and carSprite are responsible for their own positioning, you can leave these
parameters as it is (that is, paint from the device origin).
Listing 3 shows the revised updateGameScreen(). The lines that are to be removed are retained as comments to
make it easy to locate the changes.
privatevoidupdateGameScreen(Graphicsg){
//thenexttwolinesclearthebackground
g.setColor(0xffffff);
g.fillRect(0,0,getWidth(),getHeight());
//createsthegameborders
buildGameScreen(g);
//drawsthecoupleimageaccordingtocurrent
//desiredpositions
/*g.drawImage(
coupleImg,coupleX,
coupleY,Graphics.HCENTER|Graphics.BOTTOM);*/
//animatesthesprite
coupleSprite.nextFrame();
//movesthespritebasedonitsreferencepixel
coupleSprite.setRefPixelPosition(coupleX,coupleY);
//paintsitonthebuffer
//coupleSprite.paint(g);
//themanagerpaintsallthelayers
manager.paint(g,0,0);
//thiscallpaintsoffscreenbuffertoscreen
flushGraphics();
}

APIIT /UCTI

SDMD

15/20

Listing 3. Updated updateGameScreen() method

Adding More Sprites and Detecting Collisions


What fun is a single lonely sprite jumping around for no obvious purpose? It's time to introduce another sprite, in the
form a car sprite that will randomly appear at several locations across the game screen. The jumping/shining couple
will need to bump into these evil car manifestations to defeat them! The more cars that are hit by the couple in a fixed
time, the higher the score.
With the game objectives now clear, let's first create a class that will keep track of the time so that the game can be
stopped once the time has expired. Listing 4 shows the code for the Clock class.
importjava.util.TimerTask;
publicclassClockextendsTimerTask{
inttimeLeft;
publicClock(intmaxTime){
timeLeft=maxTime;
}
publicvoidrun(){
timeLeft;
}
publicintgetTimeLeft(){returnthis.timeLeft;}
}
Listing 4. The Clock class that will keep track of the game time
The Clock class extends the TimerTask class, whose run() method gets executed after a predefined time. Here,
it reduces the maxTime variable every second, which helps us keep track of the time. To use the Clock class, create
and start it just before the infinite loop inside of the run() method of the MyGameCanvas class is executed, as
shown here:
//beforegoingintheloop,startthetimerclockwitha
//30secondscountdown
clock=newClock(30);
newTimer().schedule(clock,0,1000);
Of course, now the infinite loop must be preempted with a flag that stops the loop from running when the time has
expired. To do this, define a Boolean flag called stop, as shown here:
//theflagthattellsthegametostop
privatebooleanstop=false;
Use it in the while loop as while(!stop) and enter the first lines of code in the verifyGameState() method:
privatevoidverifyGameState(){
if(clock.getTimeLeft()==0){
stop=true;
return;
}
}

APIIT /UCTI

SDMD

16/20

Finally, the user needs to be informed of the time left in the game. To do this, add a method called
showTimeLeft(Graphicsg), as shown here:
privatevoidshowTimeLeft(Graphicsg){
//whatdoestheclocksay
inttimeLeft=clock.getTimeLeft();
//iflessthan6secondsleft
//flickertimewithredandblack
if(timeLeft<6){
//if((timeLeft%2)==0)
g.setColor(0xff0000);
//else
//g.setColor(0x000000);
}
//drawthetimeleftstring
g.drawString("TimeLeft:"+timeLeft+"seconds",0,0,0);
//resetthecolor
g.setColor(0x000000);
}
This is called at the end of the buildGameScreen() method. Figure 8 shows a snapshot of the game as it looks
now.

Figure 8. Game with time left showing


It is time to add a new (actually several new) sprites into this game. Listing 5 shows the code for the car sprite in a
separate class called CarSprite. This code uses the image of a car shown in Figure 9.

Figure 9. Image for car sprite

importjava.util.Random;

APIIT /UCTI

SDMD

17/20

importjavax.microedition.lcdui.Image;
importjavax.microedition.lcdui.game.Sprite;
importjavax.microedition.lcdui.game.LayerManager;
publicclassCarSpriteimplementsRunnable{
//publicLayerManagergetManager(){returnmanager;}
//publicRandomgetRandom(){returnrandom;}
publicCarSprite(MyGameCanvasparent){
this.parent=parent;
this.manager=parent.getManager();
}
publicvoidstart(){
//firstloadthecarimage
try{
carImage=Image.createImage("/car.png");
}catch(Exceptione){System.err.println(e);return;}
//nextstartthethreadthatwilldisplaycars
//arerandomlocations
runner=newThread(this);
runner.start();
}
publicvoidrun(){
try{
while(true){
//createarandomcar
randomCar();
//waitbeforecreatinganotherone
Thread.currentThread().sleep(500);
}
}catch(Exceptione){System.err.println(e);}
}
//createsanddisplaysacaratarandomlocation
privatevoidrandomCar(){
//ifmaximumcarsarebeingshownreturn
if(currentCars==MAX_CARS)return;
//createanewcarsprite
carSprite=newSprite(carImage,10,10);
//generatetherandomplaceswherecarsmayappear
intrandomCarX=parent.getRandom().nextInt(parent.GAME_WIDTH);
intrandomCarY=
(parent.BASE
parent.getRandom().nextInt(parent.MAX_HEIGHT+1)
carSprite.getHeight());

APIIT /UCTI

SDMD

18/20
//makesurethattheseplacesarewithinbounds
if(randomCarX<parent.GAME_ORIGIN_X)randomCarX=parent.CENTER_X;
if(randomCarY<(parent.BASEparent.MAX_HEIGHT))
randomCarY=parent.CENTER_Y;
//setthisnewlycreatedcarspriteinitsrandomposition
carSprite.setPosition(randomCarX,randomCarY);
//addittothemanageratindex0
manager.insert(carSprite,0);
//increasethenoofcarscreated
currentCars++;

}
publicvoidcheckForCollision(){
//iftherearenocarsbeingshown(onlybackgroundandcouple)
if(manager.getSize()==2)return;
//iteratethroughthelayers,rememberdon'tworryabout
//thelasttwobecausetheyarethecoupleandbackground
for(inti=0;i<(manager.getSize()2);i++){
//ifcollisionoccurs
if(parent.getCoupleSprite().collidesWith(
(Sprite)manager.getLayerAt(i),true)){
//removetheoffendingcar
manager.remove(manager.getLayerAt(i));
//reducethenoofcarsondisplay
currentCars;
//andincreasethenoofcarshit
carsHit++;
}
}
}
//thenoofcarshit
publicintgetCarsHit(){
returncarsHit;
}
//thecarsprite
privateSpritecarSprite;
//thecarimage
privateImagecarImage;
//thenoofcurrentcarsindisplay
privateintcurrentCars;
//theparentcanvas
privateMyGameCanvasparent;

APIIT /UCTI

SDMD

19/20

//theparentcanvas'slayermanager
privateLayerManagermanager;
//runner
privateThreadrunner;
//tracksthenoofcarshit
privateintcarsHit;
//themaximumnoofcarstocreate
privatestaticfinalintMAX_CARS=20;
}
Listing 5. Code to create several car sprites
The CarSprite class implements Runnable, as it needs to spawn several new car sprites every half a second. The
run() method calls the randomCar() method after sleeping for 500 milliseconds. The randomCar() method
checks if the number of existing car sprites hasn't exceeded the limit, then creates a new sprite using the car image
loaded earlier. It then calculates a random position for this sprite to appear at, making sure that this random position
is within the game bounds. It sets this newly created sprite in this random position and adds the sprite to the
LayerManager at index 0, so that it becomes the most recent (and closest to the user) sprite.
This class also provides a method to check for collision of the couple with the random cars. The
checkForCollision() method iterates through the current car sprites being shown by the LayerManager, and
uses the collidesWith() method of the Sprite class to check for collision. This method returns a Boolean true
when collision has occurred, and accepts a layer, an image, or another with which sprite to check collision. It also
accepts a flag to indicate if collision detection should take into account the transparent pixels around an image, or
only opaque pixels. When a collision is detected, the number of cars hit is incremented and the number of cars visible
is decremented.
To use the CarSprite class, append the following lines of code at the end of the start() method of the
MyGameCanvas class.
//createthecarspritethreadandstartit
carSprite=newCarSprite(this);
carSprite.start();
Also add the following line of code at the end of the verifyGameState() method.
carSprite.checkForCollision();
Thus, the CarSprite thread starts spawning new cars, up to a maximum number of cars. Once the user hits a car
by moving the jumping/shining couple with an unpredictable bounce, the car disappears. This is checked in the
verifyGameState() method by calling the checkForCollision() method on the CarSprite thread. More
cars keep appearing till the time runs out. Figure 10 shows a typical game in progress.

APIIT /UCTI

SDMD

20/20

Figure 10. A typical game in progress after adding the car sprites
All that is left now is to inform the user about the number of cars that he has hit. After the while() loop has exited,
add a call to a new method called showGameScore(getGraphics()), and add this new method as shown here:
//attheendofthegameshowthescore
privatevoidshowGameScore(Graphicsg){
//createabaserectangle
g.setColor(0xffffff);
g.fillRect(0,CENTER_Y20,getWidth(),40);
g.setColor(0x000000);
//andshowthescore
g.drawString("Youhit"+
carSprite.getCarsHit()+"cars.",
CENTER_X,CENTER_Y,
Graphics.HCENTER|Graphics.BASELINE);
flushGraphics();
}
This draws a small rectangle in the middle of the screen at the end of the game showing the number of cars hit by the
player. A typical game ending is shown in Figure 11.

APIIT /UCTI

SDMD

21/20

Figure 11. A typical game ending and the message displayed


You can, of course, display this information in any format or location that you want.

Conclusion
You learned how to use the classes of this API using a full-fledged example and built a game successfully. You also
learned the basics of game building through this example.

APIIT /UCTI

Вам также может понравиться