Gaming API

Embed Size (px)

Citation preview

  • 8/10/2019 Gaming API

    1/21

    SDMD 1/20

    J2ME Gaming API: An Overview

    J2ME is a popular platform for developing games for wireless devices. With MD!2.0" a new pac#age is called javax.microedition.lcdui.game" has $een introduced thatprovides several gaming constructs that would onl% have $een possi$le in MD! 1.0

    with a great deal of repetitive code.There are only five classes in thejavax.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 Layerclass is the superclass of the Spriteand TiledLayerclasses. 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 ofgraphics for animation) that can be moved around the game screen, or a tiled layer, which represents a graphic thatcan 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(Graphics g)method, which has the task of rendering

    the elements on the screen.

    The LayerManagerclass 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 GameCanvasclass is made useful by extending the functionality of the Canvasclass. t provides an off!screen

    buffer, to which all rendering operations are done before flushing them on the device screen. t also provides an easy!to!use mechanism to "uery the current keys being pressed by the user.

    A Very Short Primer on Game Building

    # game or animation is built according to the principle of repetitively executing a piece of code. This piece of codetracks the value of instance variables and updates the game state accordingly. $ased on the game state, the codethen draws%paints%repaints the game screen with the elements that make up the game. The values of the instancevariables 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. $efore entering the loop, aninstance variable may be checked to see if the game should still be running, and if not, the loop may be exited. Thecode in the loop should allow the current thread of execution to sleep every few milliseconds to control the rate atwhich the update to the instance variables is done (in effect, how fast the game screen should be refreshed).

    To put it in coding terms:

    // main class

    public MainClass {

    private GameCanvas canvas = new MyGameCanvas();

    public MainClass() {

    // start a thread that will run infinitely

    canvas.start(); }

    // rest of the code

    }

    // the actual drawing class

    public MyGameCanvas extends GameCanvas implements Runnable {

    &!' /()'

  • 8/10/2019 Gaming API

    2/21

    SDMD 2/20

    public MyGameCanvas() {

    // instantiation code

    }

    public void start() {

    // do initialization

    // and then start a thread

    Thread runner = new Thread(this);

    runner.start();

    }

    private void run() {

    // or while(keeprunning = true)

    // where keeprunning is an instance variable

    while(true) {

    // checks if the game has reached// some boundary states or special conditions

    verifyGameState();

    // gets input from user and

    // updates instance variables

    // that describe the games elements

    checkUserInput();

    // paints elements on screen to reflect

    // the current game state using the current

    // graphics object

    updateGameScreen(getGraphics());

    // control the rate at which screen updates are done

    Thread.sleep(milliseconds);

    }

    }

    }

    &e will use this structure to develop a game in the following sections.

    Building a J2ME Game: Start with the GameCanvas

    # GameCanvasclass is a speciali'ed subclass of the Canvasclass.. GameCanvasis optimi'ed for gaming because

    it provides a special off!screen buffer in which all painting operations are done. &hen all painting on this buffer iscomplete, 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 flickeringthat might happen if no buffering were provided. The si'e of the buffer is e"ual to the si'e of the device screen, andthere is only one buffer per GameCanvasinstance.

    &!' /()'

  • 8/10/2019 Gaming API

    3/21

    SDMD */20

    The GameCanvasclass provides a storage mechanism for the state of game keys, which is a useful way to "uery

    user interaction with the game. This provides a simple way of keeping track of the number of times the user haspressed a particular key. alling the method getKeyStates()returns a bitwise representation of all of the physical

    game keys, expressed as 1for pressed and 0for unpressed, since the last time the method was called. nly the

    following game states are identified, which is what you would expect, keeping in mind the game keys defined by theCanvas class: DOWN_PRESSED, UP_PRESSED, RIGHT_PRESSED, LEFT_PRESSED, FIRE_PRESSED,

    GAME_A_PRESSED, GAME_B_PRESSED, GAME_C_PRESSED, and GAME_D_PRESSED.

    *et+s build a game canvas by extending the GameCanvasclass. *isting shows the first attempt, while *isting -

    shows the /let that will be used to run the examples.

    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.png");

    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

    &!' /()'

  • 8/10/2019 Gaming API

    4/21

    SDMD +/20

    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());

    buildGameScreen(g);

    // 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;

    &!' /()'

  • 8/10/2019 Gaming API

    5/21

    SDMD ,/20

    // 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

    *isting - shows the /let that will use this gaming canvas:

    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 eamples

    0sing both of these classes, create a pro1ect with your Toolkit and then build and run the pro1ect. You will need thisimage file: , named couple.png, in the res folder of your pro1ect, or you can use a similar!si'ed image. 2igure shows the expected output.

    &!' /()'

  • 8/10/2019 Gaming API

    6/21

    SDMD -/20

    !igure 1. "uilding 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 gamekeys, respectively. n the code shown in *isting , this is achieved by "uerying the game states in thecheckUserInput()method and then calling the calculateCoupleX()method with this game state. #s you can

    see, by bit!wise ORing the state with the supplied Constants in the GameCanvasclass, 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

    Graphicsob1ect. This ob1ect is created for you by the GameCanvasclass, and there is only one such ob1ect per

    GameCanvas. The method clears this graphics buffer of any previous renderings, draws the couple image based on

    the current coupleXvariable (and the currently unchanging coupleYvariable) and then flushes this buffer on the

    device screen.

    The infinite loop in the run()method follows the game structure that described in the sidebar earlier. This loop

    sleeps for 34 milliseconds before going on another cycle to determine the user input and refresh the buffer. You canexperiment with this value to slow down or speed up the refresh rate.

    2inally, notice that the MyGameCanvasconstructor calls its superclass GameCanvas+s constructor with a parameter

    value of true. This indicates that the normal key event mechanism, inherited from the Canvasclass, should be

    suppressed, as this code does not re"uire these notifications. The game state is ade"uately handled by the key stateinformation, which is fetched from the getKeyStates()method. $y suppressing the notification mechanism for

    5key pressed,5 5key released,5 and 5key repeated,5 the game performance is improved.

    Deining Game !hara"teri#ti"#

    # game where all you have to do is to move the central character left and right is not very fun. *et+s make somemodifications to the game skeleton in *isting to define this game a little better. To start with, specify a boundary for

    &!' /()'

  • 8/10/2019 Gaming API

    7/21

    SDMD /20

    your game. t is essential to do this, because it helps to make your game consistently si'ed across different devices.To do this, start by defining some constants that are shown in the code here:

    // the game boundary

    public static final int GAME_WIDTH = 160;

    public static final int GAME_HEIGHT = 160;

    // the shifted x,y origin of the game

    public final int GAME_ORIGIN_X = (getWidth() - GAME_WIDTH)/2;

    public final int GAME_ORIGIN_Y = (getHeight() - GAME_HEIGHT)/2;

    // the height of sections below and above the couple

    public final int SECTION_HEIGHT = 64;

    // the base on which the couple will move

    public final int BASE = GAME_ORIGIN_Y + GAME_HEIGHT - SECTION_HEIGHT;

    // the max height the couples can jump

    public final int MAX_HEIGHT = 32;

    (6ote that have introduced a game characteristic that indicates that this couple may soon be 1umping on the screen,with the help of the MAX_HEIGHTconstant.) n the screen, these constants help define the boundary of the game

    and its sole element (the couple), as shown in 2igure -.

    !igure 2. Defining the game boundaries using the game constants

    f course, now you need to modify the rest of the code to use these constants. #dd a new method to *isting calledbuildGameScreen(Graphics g), as shown in code here:

    private void buildGameScreen(Graphics g) {

    // set the drawing color to black

    &!' /()'

  • 8/10/2019 Gaming API

    8/21

    SDMD /20

    g.setColor(0x000000);

    // draw the surrounding rectangle

    g.drawRect(GAME_ORIGIN_X, GAME_ORIGIN_Y, GAME_WIDTH, GAME_HEIGHT);

    // draw the base line

    g.drawLine(GAME_ORIGIN_X, BASE, GAME_ORIGIN_X + GAME_WIDTH, BASE);

    // draw the maximum line to where the couple can jump to

    g.drawLine(GAME_ORIGIN_X, BASE - MAX_HEIGHT,

    GAME_ORIGIN_X + GAME_WIDTH, BASE - MAX_HEIGHT);

    }

    #lso 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 theBASEand not CENTER_Y. hange 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 movepast the game boundary. This was a problem in *isting , too, but in that case, the image simply vanished off thescreen, as the boundary was the edge of the screen. t 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:

    private void calculateCoupleX(int keyState) {

    // determines which way to move and changes the

    // x coordinate accordingly

    if((keyState & LEFT_PRESSED) != 0) {

    coupleX =

    Math.max(

    GAME_ORIGIN_X + coupleImg.getWidth()/2,

    coupleX - dx);

    }

    else if((keyState & RIGHT_PRESSED) != 0) {

    coupleX =

    Math.min(

    GAME_ORIGIN_X + GAME_WIDTH - coupleImg.getWidth()/2,

    coupleX + dx);

    }

    }

    This method now uses Math.max() and Math.min() methods to restrict the couple image within the game

    boundaries. 6otice that it also incorporates the width of the image in these calculations.

    spoke earlier about making the couple image 1ump around on the screen. *et+s see how this can be achieved byadding a method to move the image along the Y axis, independently of the user playing the game.

    mport 1ava.util.7andom class to *isting as shown here:

    // Random number would be generated using this class

    import java.util.Random;

    &!' /()'

  • 8/10/2019 Gaming API

    9/21

    SDMD /20

    #dd four new instance variables to *isting , called up, 1ump8eight, and random, as shown here:

    // a flag to indicate which direction the couple are moving

    private boolean up = true;

    // the distance to move in the y axis

    Private int dy=1;

    // indicates the random jump height, calculated for every jump

    private int jumpHeight = MAX_HEIGHT;

    // random number generator

    public Random random = new Random();

    #s you can see, jumpHeightis initiali'ed to MAX_HEIGHT. This jumpHeightvariable will be calculated for each

    1ump that the couple make and it will be set to a random value each time. This is shown in thecalculateCoupleY()method shown here:

    private void calculateCoupleY(int keyState) {

    // check if the couple were on the way up

    if(up) {

    // if yes, see if they have reached the current jump height

    if((coupleY > (BASE - jumpHeight + coupleImg.getHeight()))) {

    // if not, continue moving them up

    coupleY -= dy;

    } else if(coupleY == (BASE - jumpHeight + coupleImg.getHeight())) {

    // if yes, start moving them down coupleY += dy;

    // and change the flag

    up = false;

    }

    } else {

    // the couple are on their way down, have they reached base?

    if(coupleY < BASE) {

    // no, so keep moving them down

    coupleY += dy;

    } else if(coupleY == BASE) {

    // have reached base, so calculate a new

    // jump height which is not more than MAX_HEIGHT

    int hyper = random.nextInt(MAX_HEIGHT + 1);

    // but make sure that this it is atleast greater than the image height

    if(hyper > coupleImg.getHeight()) jumpHeight = hyper;

    &!' /()'

  • 8/10/2019 Gaming API

    10/21

    SDMD 10/20

    // move the image up

    coupleY -= dy;

    // and reset the flag

    up = true;

    } }

    }

    #lso add a call to this method in the checkUserInput()method . 6ote that since this method doesn+t depend on

    the user pressing the up or down game keys, it has no use for the keyStateinformation. $ut 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 coupleYvariable in the upwards direction until it reaches the current 1ump height

    (which is the MAX_HEIGHTat starting). nce it reaches this 1ump height, it starts moving it in the opposite direction

    until it reaches BASE. #t this point, a new 1ump height value, between the MAX_HEIGHTand couple image heights, is

    randomly calculated and the couple start 1umping again.

    The overall effect is of a randomly 1umping couple who can be moved left and right by the user playing the game. #

    snapshot is shown in 2igure 3.

    !igure #. $ame snapshot

    Building a J2ME Game: !reating Ba"$ground# %#ing the TiledLayer!la##

    n this section, you will add some color to the game by providing a background using the TiledLayerclass. The

    game is divided into three sections: the top section can be thought of as the sky, the middle section in which thecouple 1ump is the earth, and the bottom section is the sea. These three sections can be filled easily using threecolored images of the si'e 3- by 3- pixels each, one for each section. 8owever, each section is bigger than 3- by 3-pixels, and the TiledLayerclass is used to define large areas like these with small images.

    To start, divide the game screen into s"uares of 3- by 3- each and number each row and column, starting with anindex of 4. This is shown in 2igure 9 and results in a !by!!cell background.

    &!' /()'

  • 8/10/2019 Gaming API

    11/21

    SDMD 11/20

    !igure %. Divide the game screen into individual cells

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

    !igure &. "ac'ground image

    The first 3- by 3- cell represents the earth image, the second represents the sea, and the last represents the sky.&hen you use the TiledLayerclass, these images are numbered starting from index (not 4; therefore, earth is ,

    sea is -, and sky is 3). The TiledLayerclass will take this one image and divide it into three separate images used

    for rendering the game background. n our case, we want the TiledLayerclass to render a !by!!cell background

    using cells of 3- by 3- pixels each. This is achieved by the following code:

    // load the image

    backgroundImg = Image.createImage("/tiledLayer1.png");

    // create the tiledlayer background

    background = new TiledLayer(5, 5, backgroundImg, 32, 32);

    #s you can see, the first two parameters to the TiledLayerconstructor represent the total background si'e, the

    next parameter represents the image, and the last two parameters represents the si'e of each cell. This si'e will beused by the TiledLayerclass to carve the image into its individual background cells.

    #ll that is now left is to set each cell with its respective image. The full code to create the background is listed belowin a method called createBackground(). You will need to add a call to this method from the start()method of

    the MyGameCanvasclass. nce 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.

    #dd two new instance variables to *isting , called backgroundImg, backgroundas shown here:

    // an image reference to contain background image

    private Image backgroundImg;

    // a tiled layer reference to split background sections

    private TiledLayer background;

    &!' /()'

  • 8/10/2019 Gaming API

    12/21

    SDMD 12/20

    // creates the background using TiledLayer

    private void createBackground() throws IOException {

    // load the image

    backgroundImg = Image.createImage("/tiledlayer1.png");

    // create the tiledlayer background

    background = new TiledLayer(5, 5, backgroundImg, 32, 32);

    // array that specifies what image goes where

    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

    };

    // set the background with the images

    for (int i = 0; i < cells.length; i++) {

    int column = i % 5;

    int row = (i - column)/5; background.setCell(column, row, cells[i]);

    }

    // set the location of the background

    background.setPosition(GAME_ORIGIN_X, GAME_ORIGIN_Y);

    }

    The final result will look like 2igure

  • 8/10/2019 Gaming API

    13/21

    SDMD 1*/20

    *et+s start by converting the existing couple image into a sprite. To showcase animation, will use the image showedin 2igure ?, which is the couple image duplicated over with a different color into two different frames, each 4 by 4pixels each.

    !igure ). !rames for couple Spriteanimation

    =imilar to the TiledLayerclass, the Spriteclass re"uires that the si'e of each frame be passed in to its

    constructor. This is shown here:

    coupleSprite = new Sprite(coupleImg, 10, 10);

    This code, added after the creation of the couple image in thestart()method, creates a couple sprite with two

    frames of 4 by 4 pixels each, numbered from 4 onwards. Thus, to alternate between the sprite images, you can callthe nextFrame()method, which gets the next image in the current se"uence. =ince there are only two images in

    this sprite se"uence, they will be shown one after another. f you want to make a particular frame%image the currentdisplayable image for a sprite in a longer frame se"uence, you can do so by using the method setFrame(int

    sequenceNo). n this case, add coupleSprite.nextFrame()in the updateGameScreen()method.

    You now don+t want the couple image to be painted on the screen. $efore 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 aredone. $y default, a sprite is painted with its upper left corner as its origin. =imilar to the way you set the reference ofthe couple image using the Graphics.HCENTER | Graphics.BOTTOMcode, you need to define a reference pixel

    for the sprite. This is shown here:

    coupleSprite.defineReferencePixel(coupleSprite.getWidth()/2,

    coupleSprite.getHeight());

    #dd this snippet after the creation of the sprite as described earlier. 6ow, instead of positioning the sprite based on itsoriginal 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 ob1ect that is passed to it. #s expected, you willneed 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 1umping couple will be replaced with a flickering1umping couple@

    $efore going forward, make sure that you change all references to the coupleImgvariable to coupleSpritein

    thecalculateX()and calculateY()methods.

    Managing a%ers (sing the LayerManager

    7ecall that both the Spriteand TiledLayerclasses extend the Layerclass. # game may contain at least one

    TiledLayerand several Spriteclasses. &ith 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 methodto paint all of these layers to the underlying Graphicsob1ect. This means that you don+t need to individually call the

    paint()method of each of the layers of a game.

    #n instance of LayerManageris created using its no!args constructor. *ayers are then added, removed, or inserted

    into it by using the methods append(Layer layer), remove(Layer layer), and insert(Layer l, 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 '!order index. The layer at index 4 is painted on top of all the other layers, andhence, is closestto the user, and so on.

    &!' /()'

  • 8/10/2019 Gaming API

    14/21

    SDMD 1+/20

    n our game, the start()method now needs to be modified, as shown here:

    // creates the layermanager

    manager = new LayerManager();

    // and adds layers to it

    manager.append(coupleSprite);

    // creates the game background

    createBackground();

    manager.append(background);

    #s you can see, the coupleSpritelayer will be closest to the user and the backgroundlayer 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. 2inally, in the previous section, you used the coupleSpriteto paint it on the screen

    instead of the coupleImage. 6ow even that is not re"uired, as the LayerManagerwill do this for you. 7emove

    coupleSprite.paint(g)from the updateGameScreen()method and replace it with manager.paint(g, 0,

    0). #s 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 shouldpaint. =ince 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).

    *isting 3 shows the revised updateGameScreen(). The lines that are to be removed are retained as comments to

    make it easy to locate the changes.

    private void updateGameScreen(Graphics g) {

    // the next two lines clear the background

    g.setColor(0xffffff);

    g.fillRect(0, 0, getWidth(), getHeight());

    // creates the game borders

    buildGameScreen(g);

    // draws the couple image according to current

    // desired positions

    /*g.drawImage(

    coupleImg, coupleX,

    coupleY, Graphics.HCENTER | Graphics.BOTTOM);*/

    // animates the sprite

    coupleSprite.nextFrame();

    // moves the sprite based on its reference pixel

    coupleSprite.setRefPixelPosition(coupleX, coupleY);

    // paints it on the buffer // coupleSprite.paint(g);

    // the manager paints all the layers

    manager.paint(g, 0, 0);

    // this call paints off screen buffer to screen

    flushGraphics();

    }

    &!' /()'

  • 8/10/2019 Gaming API

    15/21

    SDMD 1,/20

    Listing #. *pdated updateGameScreen()method

    Adding More S&rite# and Dete"ting !olli#ion#

    &hat fun is a single lonely sprite 1umping around for no obvious purposeA t+s time to introduce another sprite, in theform a car sprite that will randomly appear at several locations across the game screen. The 1umping%shining couple

    will need to bumpinto these evil car manifestations to defeat them@ The more cars that are hit by the couple in a fixedtime, the higher the score.

    &ith the game ob1ectives now clear, let+s first create a class that will keep track of the time so that the game can bestopped once the time has expired. *isting 9 shows the code for the Clockclass.

    import java.util.TimerTask;

    public class Clock extends TimerTask {

    int timeLeft;

    public Clock(int maxTime) {

    timeLeft = maxTime;

    }

    public void run() {

    timeLeft--;

    }

    public int getTimeLeft() { return this.timeLeft; }

    }

    Listing %. +he Clockclass that will 'eep trac' of the game time

    The Clockclass extends the TimerTaskclass, whose run()method gets executed after a predefined time. 8ere,

    it reduces the maxTimevariable every second, which helps us keep track of the time. To use the Clockclass, create

    and start it 1ust before the infinite loop inside of the run()method of the MyGameCanvasclass is executed, asshown here:

    // before going in the loop, start the timer clock with a

    // 30 seconds countdown

    clock = new Clock(30);

    new Timer().schedule(clock, 0, 1000);

    f course, now the infinite loop must be preempted with a flag that stops the loop from running when the time hasexpired. To do this, define a $oolean flag called stop, as shown here:

    // the flag that tells the game to stop

    private boolean stop = false;

    0se it in the whileloop as while(!stop)and enter the first lines of code in the verifyGameState()method:

    private void verifyGameState() {

    if(clock.getTimeLeft() == 0) {

    stop = true;

    return;

    }

    }

    &!' /()'

  • 8/10/2019 Gaming API

    16/21

    SDMD 1-/20

    2inally, the user needs to be informed of the time left in the game. To do this, add a method calledshowTimeLeft(Graphics g), as shown here:

    private void showTimeLeft(Graphics g) {

    // what does the clock say

    int timeLeft = clock.getTimeLeft();

    // if less than 6 seconds left

    // flicker time with red and black

    if(timeLeft < 6) {

    //if((timeLeft % 2) == 0)

    g.setColor(0xff0000);

    //else

    //g.setColor(0x000000);

    }

    // draw the time left string

    g.drawString("Time Left: " + timeLeft + " seconds", 0, 0, 0);

    // reset the color

    g.setColor(0x000000);

    }

    This is called at the end of the buildGameScreen()method. 2igure B shows a snapshot of the game as it looks

    now.

    !igure ,. $ame with time left showing

    t is time to add a new (actually several new) sprites into this game. *isting shows the code for the car sprite in aseparate class called CarSprite. This code uses the image of a car shown in 2igure C.

    !igure -. Image for car sprite

    import java.util.Random;

    &!' /()'

  • 8/10/2019 Gaming API

    17/21

    SDMD 1/20

    import javax.microedition.lcdui.Image;

    import javax.microedition.lcdui.game.Sprite;

    import javax.microedition.lcdui.game.LayerManager;

    public class CarSprite implements Runnable {

    //public LayerManager getManager(){return manager;}

    //public Random getRandom(){return random;}

    public CarSprite(MyGameCanvas parent) {

    this.parent = parent;

    this.manager = parent.getManager();

    }

    public void start() {

    // first load the car image

    try {

    carImage = Image.createImage("/car.png");

    } catch(Exception e) { System.err.println(e); return; }

    // next start the thread that will display cars

    // are random locations

    runner = new Thread(this);

    runner.start();

    }

    public void run() {

    try {

    while(true) {

    // create a random car

    randomCar();

    // wait before creating another one

    Thread.currentThread().sleep(500);

    }

    } catch(Exception e) { System.err.println(e); }

    }

    // creates and displays a car at a random location

    private void randomCar() {

    // if maximum cars are being shown return

    if(currentCars == MAX_CARS) return;

    // create a new car spritecarSprite = new Sprite(carImage, 10, 10);

    // generate the random places where cars may appear

    int randomCarX = parent.getRandom().nextInt(parent.GAME_WIDTH);

    int randomCarY =

    (parent.BASE -

    parent.getRandom().nextInt(parent.MAX_HEIGHT + 1) -

    carSprite.getHeight());

    &!' /()'

  • 8/10/2019 Gaming API

    18/21

    SDMD 1/20

    // make sure that these places are within bounds

    if(randomCarX < parent.GAME_ORIGIN_X) randomCarX = parent.CENTER_X;

    if(randomCarY < (parent.BASE - parent.MAX_HEIGHT))

    randomCarY = parent.CENTER_Y;

    // set this newly created car sprite in its random position

    carSprite.setPosition(randomCarX, randomCarY);

    // add it to the manager at index 0

    manager.insert(carSprite, 0);

    // increase the no of cars created

    currentCars++;

    }

    public void checkForCollision() {

    // if there are no cars being shown (only background and couple)

    if(manager.getSize() == 2) return;

    // iterate through the layers, remember don't worry about// the last two because they are the couple and background

    for(int i = 0; i < (manager.getSize() - 2); i++) {

    // if collision occurs

    if(parent.getCoupleSprite().collidesWith(

    (Sprite)manager.getLayerAt(i), true)) {

    // remove the offending car

    manager.remove(manager.getLayerAt(i));

    // reduce the no of cars on display

    currentCars--;

    // and increase the no of cars hit carsHit++;

    }

    }

    }

    // the no of cars hit

    public int getCarsHit() {

    return carsHit;

    }

    // the car sprite

    private Sprite carSprite;

    // the car image

    private Image carImage;

    // the no of current cars in display

    private int currentCars;

    // the parent canvas

    private MyGameCanvas parent;

    &!' /()'

  • 8/10/2019 Gaming API

    19/21

    SDMD 1/20

    // the parent canvas's layer manager

    private LayerManager manager;

    // runner

    private Thread runner;

    // tracks the no of cars hit

    private int carsHit;

    // the maximum no of cars to create

    private static final int MAX_CARS = 20;

    }

    Listing &. ode to create several car sprites

    The CarSpriteclass 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 44 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 imageloaded earlier. t then calculates a random position for this sprite to appear at, making sure that this random positionis within the game bounds. t sets this newly created sprite in this random position and adds the sprite to the

    LayerManagerat index 4, 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. ThecheckForCollision()method iterates through the current car sprites being shown by the LayerManager, and

    uses the collidesWith()method of the Spriteclass to check for collision. This method returns a $oolean true

    when collision has occurred, and accepts a layer, an image, or another with which sprite to check collision. t alsoaccepts a flag to indicate if collision detection should take into account the transparent pixels around an image, oronly opa"ue pixels. &hen a collision is detected, the number of cars hit is incremented and the number of cars visibleis decremented.

    To use the CarSpriteclass, append the following lines of code at the end of the start()method of the

    MyGameCanvasclass.

    // create the car sprite thread and start itcarSprite = new CarSprite(this);

    carSprite.start();

    #lso add the following line of code at the end of the verifyGameState()method.

    carSprite.checkForCollision();

    Thus, the CarSpritethread starts spawning new cars, up to a maximum number of cars. nce the user hitsa car

    by moving the 1umping%shining couple with an unpredictable bounce, the car disappears. This is checked in theverifyGameState() method by calling the checkForCollision()method on the CarSprite thread. ore

    cars keep appearing till the time runs out. 2igure 4 shows a typical game in progress.

    &!' /()'

  • 8/10/2019 Gaming API

    20/21

    SDMD 20/20

    !igure 1/. A typical game in progress after adding the car sprites

    #ll that is left now is to inform the user about the number of cars that he has hit. #fter the while()loop has exited,

    add a call to a new method called showGameScore(getGraphics()), and add this new method as shown here:

    // at the end of the game show the score

    private void showGameScore(Graphics g) {

    // create a base rectangle

    g.setColor(0xffffff);

    g.fillRect(0, CENTER_Y - 20, getWidth(), 40);

    g.setColor(0x000000);

    // and show the score

    g.drawString("You hit " +

    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 theplayer. # typical game ending is shown in 2igure .

    &!' /()'

  • 8/10/2019 Gaming API

    21/21

    SDMD 21/20

    !igure 11. A typical game ending and the message displayed

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

    !on"lu#ion

    You learned how to use the classes of this #> using a full!fledged example and built a game successfully. You alsolearned the basics of game building through this example.