Академический Документы
Профессиональный Документы
Культура Документы
1/20
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.
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
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.
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.
APIIT /UCTI
SDMD
11/20
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.
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.
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
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.
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
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