Tag Archives: MIDP 2.0

MIDP 2.0 Games: a Step-by-Step Tutorial with Code Samples (Step 5)

The Sprite Class


A Sprite is a graphical object represented by one image (at a time). The fact that a Sprite is composed of only one image is the principal difference between a Sprite and a TiledLayer, which is a region that is covered with images that can be manipulated. (The Sprite class has a few extra features, but the fact that it uses one image rather than filling an area with images is the most obvious difference.) So a Sprite is generally used for small, active game objects (such as your spaceship and the asteroids that are coming to crash into it) and a TiledLayer would be more likely to be used for an animated background.

One cool feature of Sprite is that even though a Sprite is represented by only one image at a time, it can be easily represented by different images under different circumstances, including by a series of images that make up an animation. In my example game the cowboy has three different images in which he is walking and one in which he is jumping. All of the images used for a given Sprite need to be stored together in a single image file. (To indicate where to find the image file to use, send the address of the image within the jar in exactly the same format that is used to find resources in the method Class.getResource(); see the Putting it Together section for more details.) The fact that multiple frames are stored in a single Image object is a convenience that means you don’t have to manipulate multiple Image objects to determine which face your Sprite is wearing at any given time. Here’s the image file for the cowboy:

 


And here is the image file for the tumbleweed which is animated to appear to be rolling:

 
The way to select which frame is shown at any given time is very intuitive. First, if your image file is composed of multiple images (as these two are), you should construct the Sprite with the constructor that specifies the width and height (in pixels) that you would like your Sprite to be. The width and height of the Image should be integer multiples of the width and height that you send to the constructor. In other words, the computer should be able to divide up your image file evenly into rectangles of the size you specify. As you can see from the examples above, the sub-images may be arranged horizontally or vertically. They can even be arranged in a grid with multiple rows and columns. Then to identify the individual frames, they are numbered starting with zero at the top left corner and continuing to the right and then continuing on to the lower rows, in exactly the same order in which you are reading the letters on this page. To select which frame is currently displayed, use the method setFrame(int sequenceIndex) sending the frame number as an argument.

The Sprite class has some added support for animation by allowing you to define a frame sequence with the method setFrameSequence(int[] sequence). As you can see in the code below, I’ve set a frame sequence of { 1, 2, 3, 2 } for my cowboy and { 0, 1, 2 } for my tumbleweed. To advance your Sprite’s animation from one frame to the next, you use the method nextFrame() (or if you prefer prevFrame()). This is very convenient in cases like my tumbleweed where all of the available frames are used in the animation. It’s slightly less convenient in cases like my cowboy that have an image or images that fall outside of the frame sequence of the animation. This is because once a frame sequence has been set, the argument to the method setFrame(int sequenceIndex) gives the index of an entry in the frame sequence instead of giving the index of the frame itself. What that means is that once I’ve set my cowboy’s frame sequence to { 1, 2, 3, 2 }, if I call setFrame(0) it will show frame number 1, setFrame(1) will show frame number 2, setFrame(2) will show frame number 3, and setFrame(3) will show frame number 2. But when the cowboy is jumping, I would like it to show frame number 0 which is no longer accessible. So when my cowboy jumps, I have to set my frame sequence to null before calling setFrame(0), and then I have to set my frame sequence back to the animation sequence { 1, 2, 3, 2 } afterwards. This is illustrated in the code below in the methods jump() and advance(int tickCount, boolean left).

In addition to changing your Sprite’s appearance by changing frames, you can change it by applying simple transforms such as rotations or mirror images. Both my cowboy and my tumbleweed below can be going either left or right, so of course I need to use the mirror image transform to change from one direction to the other. Once you start applying transforms, you need to keep track of the Sprite’s reference pixel. This is because when you transform your Sprite, the reference pixel is the pixel that does not move. You might expect that if your Sprite’s image is square then after a transformation the Sprite’s image will continue to occupy the same square of area on the screen. This is not the case. The best way to illustrate what happens is to imagine an example Sprite of a standing person facing left whose reference pixel has been defined to be the tip of his toe. Then after applying a 90-degree rotation, your person will be in the spot he would be in if he had tripped and fallen forward. Clearly this has its applications if your Sprite has a special pixel (such as the tip of an arrow) that should stay put after a transformation. But if you want your Sprite to continue to occupy the same space on the screen after a transformation, then you should first call defineReferencePixel(int x, int y) and set your Sprite’s reference pixel to the center of the Sprite, as I did in the Cowboy constructor below. Be aware that the coordinates in defineReferencePixel(int x, int y) are relative to the top corner of the Sprite whereas the coordinates sent to setRefPixelPosition(int x, int y) tell where to place the Sprite’s reference pixel on the screen in terms of the screen’s coordinates. To be more precise, the coordinates sent to setRefPixelPosition(int x, int y) refer to the coordinate system of the Canvas if the Sprite is painted directly onto the Canvas, but if the Sprite is painted by a LayerManager, these coordinates should be given in terms of the LayerManager’s coordinate system. (How these coordinate systems fit together is explained in the LayerManager section above.) The coordinates in the various methods to set and get the position of the reference pixel or to set and get the position of the top left corner pixel refer to the coordinates of the appropriate Canvas or LayerManager. Also note that if you perform multiple transformations, the later trasformations are applied to the original image and not to its current state. In other words, if I apply setTransform(TRANS_MIRROR) twice in a row, the second transform will not mirror my image back to its original position, it will just repeat the action of setting the Image to being a mirror image of the original Image. If you want to set a transformed Sprite back to normal, use setTransform(TRANS_NONE). This is illustrated in the top of the Cowboy.advance(int tickCount, boolean left) method.

Another great feature of the Layer class (including both Sprites and TiledLayers) is the support it gives you for placing your objects in relative terms instead of in absolute terms. If your Sprite needs to move over three pixels regardless of where it currently is, you can just call move(int x, int y) sending it the x and y distances it should move from its current position, as opposed to calling setRefPixelPosition(int x, int y) with the absolute coordinates of the Sprite’s new location. Even more useful is the set of collidesWith() methods. This allows you to check if a Sprite is occupying the same space as another Sprite or TiledLayer or even an Image. It’s easy to see that this saves you quite a number of comparisons, especially since when you send the pixelLevel argument as true, it will only consider the two Layers as having collided if their opaque pixels overlap.

In my “Tumbleweed” game, after advancing all of the Sprites, I check if the cowboy has collided with any tumbleweeds. (This happens in the Cowboy.checkCollision(Tumbleweed tumbleweed) method which is called from JumpManager.advance(int gameTicks).) I check the collisions between the cowboy and all of the tumbleweeds each time because it automatically returns false for any tumbleweeds that aren’t currently visible anyway, so I’m not really being wasteful by checking the cowboy against tunbleweeds that are not currently in use. In many cases, however, you can save some effort by only checking for collisions that you know are possible rather than checking all of the Sprites against each other. Note that in my example I don’t bother to check if the tumbleweeds collide with each other or if anything collides with the background grass because that is irrelevant. If you’re checking for pixel-level collisions, you will want to be sure that your images have a transparent background. (This is also helpful in general so that your Sprite doesn’t paint an ugly rectangle of background color over another Sprite or Image.) Some discussion of creating the image files correctly is included in Appendix B.

Here’s the code for Cowboy.java:

package net.frog_parrot.jump;

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

/**
 * This class represents the player.
 *
 * @author Carol Hamer
 */
public class Cowboy extends Sprite {

  //———————————————————
  //    dimension fields

  /**
   * The width of the cowboy’s bounding rectangle.
   */
  static int WIDTH = 32;

  /**
   * The height of the cowboy’s bounding rectangle.
   */
  static int HEIGHT = 48;

  /**
   * This is the order that the frames should be displayed
   * for the animation.
   */
  static int[] FRAME_SEQUENCE = { 3, 2, 1, 2 };

  //———————————————————
  //    instance fields

  /**
   * the X coordinate of the cowboy where the cowboy starts
   * the game.
   */
  int myInitialX;

  /**
   * the Y coordinate of the cowboy when not jumping.
   */
  int myInitialY;

  /**
   * The jump index that indicates that no jump is
   * currently in progress..
   */
  int myNoJumpInt = -6;

  /**
   * Where the cowboy is in the jump sequence.
   */
  int myIsJumping = myNoJumpInt;

  /**
   * If the cowboy is currently jumping, this keeps track
   * of how many points have been scored so far during
   * the jump.  This helps the calculation of bonus points since
   * the points being scored depend on how many tumbleweeds
   * are jumped in a single jump.
   */
  int myScoreThisJump = 0;

  //———————————————————
  //   initialization

  /**
   * constructor initializes the image and animation.
   */
  public Cowboy(int initialX, int initialY) throws Exception {
    super(Image.createImage(“/icons/cowboy.png”),
   WIDTH, HEIGHT);
    myInitialX = initialX;
    myInitialY = initialY;
    // we define the reference pixel to be in the middle
    // of the cowboy image so that when the cowboy turns
    // from right to left (and vice versa) he does not
    // appear to move to a different location.
    defineReferencePixel(WIDTH/2, 0);
    setRefPixelPosition(myInitialX, myInitialY);
    setFrameSequence(FRAME_SEQUENCE);
  }

  //———————————————————
  //   game methods

  /**
   * If the cowboy has landed on a tumbleweed, we decrease
   * the score.
   */
  int checkCollision(Tumbleweed tumbleweed) {
    int retVal = 0;
    if(collidesWith(tumbleweed, true)) {
      retVal = 1;
      // once the cowboy has collided with the tumbleweed,
      // that tumbleweed is done for now, so we call reset
      // which makes it invisible and ready to be reused.
      tumbleweed.reset();
    }
    return(retVal);
  }

  /**
   * set the cowboy back to its initial position.
   */
  void reset() {
    myIsJumping = myNoJumpInt;
    setRefPixelPosition(myInitialX, myInitialY);
    setFrameSequence(FRAME_SEQUENCE);
    myScoreThisJump = 0;
    // at first the cowboy faces right:
    setTransform(TRANS_NONE);
  }

  //———————————————————
  //   graphics

  /**
   * alter the cowboy image appropriately for this frame..
   */
  void advance(int tickCount, boolean left) {
    if(left) {
      // use the mirror image of the cowboy graphic when
      // the cowboy is going towards the left.
      setTransform(TRANS_MIRROR);
      move(-1, 0);
    } else {
      // use the (normal, untransformed) image of the cowboy
      // graphic when the cowboy is going towards the right.
      setTransform(TRANS_NONE);
      move(1, 0);
    }
    // this section advances the animation:
    // every third time through the loop, the cowboy
    // image is changed to the next image in the walking
    // animation sequence:
    if(tickCount % 3 == 0) { // slow the animation down a little
      if(myIsJumping == myNoJumpInt) {
 // if he’s not jumping, set the image to the next
 // frame in the walking animation:
 nextFrame();
      } else {
 // if he’s jumping, advance the jump:
 // the jump continues for several passes through
 // the main game loop, and myIsJumping keeps track
 // of where we are in the jump:
 myIsJumping++;
 if(myIsJumping < 0) {
   // myIsJumping starts negative, and while it’s
   // still negative, the cowboy is going up. 
   // here we use a shift to make the cowboy go up a
   // lot in the beginning of the jump, and ascend
   // more and more slowly as he reaches his highest
   // position:
   setRefPixelPosition(getRefPixelX(),
         getRefPixelY() – (2 << (-myIsJumping)));
 } else {
   // once myIsJumping is negative, the cowboy starts
   // going back down until he reaches the end of the
   // jump sequence:
   if(myIsJumping != -myNoJumpInt – 1) {
     setRefPixelPosition(getRefPixelX(),
    getRefPixelY() + (2 << myIsJumping));
   } else {
     // once the jump is done, we reset the cowboy to
     // his non-jumping position:
     myIsJumping = myNoJumpInt;
     setRefPixelPosition(getRefPixelX(), myInitialY);
     // we set the image back to being the walking
     // animation sequence rather than the jumping image:
     setFrameSequence(FRAME_SEQUENCE);
     // myScoreThisJump keeps track of how many points
     // were scored during the current jump (to keep
     // track of the bonus points earned for jumping
     // multiple tumbleweeds).  Once the current jump is done,
     // we set it back to zero. 
     myScoreThisJump = 0;
   }
 }
      }
    }
  }

  /**
   * makes the cowboy jump.
   */
  void jump() {
    if(myIsJumping == myNoJumpInt) {
      myIsJumping++;
      // switch the cowboy to use the jumping image
      // rather than the walking animation images:
      setFrameSequence(null);
      setFrame(0);
    }
  }

  /**
   * This is called whenever the cowboy clears a tumbleweed
   * so that more points are scored when more tumbleweeds
   * are cleared in a single jump.
   */
  int increaseScoreThisJump() {
    if(myScoreThisJump == 0) {
      myScoreThisJump++;
    } else {
      myScoreThisJump *= 2;
    }
    return(myScoreThisJump);
  }

}

 

MIDP 2.0 Games: a Step-by-Step Tutorial with Code Samples (Step 6)

And here’s the code for Tumbleweed.java:


package net.frog_parrot.jump;


import java.util.Random;


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


/**
 * This class represents the tumbleweeds that the player
 * must jump over.
 *
 * @author Carol Hamer
 */
public class Tumbleweed extends Sprite {


  //———————————————————
  //   dimension fields


  /**
   * The width of the tumbleweed’s bounding square.
   */
  static int WIDTH = 16;


  //———————————————————
  //    instance fields


  /**
   * Random number generator to randomly decide when to appear.
   */
  Random myRandom = new Random();


  /**
   * whether or not this tumbleweed has been jumped over.
   * This is used to calculate the score.
   */
  boolean myJumpedOver;


  /**
   * whether or not this tumbleweed enters from the left.
   */
  boolean myLeft;


  /**
   * the Y coordinate of the tumbleweed.
   */
  int myY;


  //———————————————————
  //   initialization


  /**
   * constructor initializes the image and animation.
   * @param left whether or not this tumbleweed enters from the left.
   */
  public Tumbleweed(boolean left) throws Exception {
    super(Image.createImage(“/icons/tumbleweed.png”),
   WIDTH, WIDTH);
    myY = JumpManager.DISP_HEIGHT – WIDTH – 2;
    myLeft = left;
    if(!myLeft) {
      setTransform(TRANS_MIRROR);
    }
    myJumpedOver = false;
    setVisible(false);
  }


  //———————————————————
  //   graphics


  /**
   * move the tumbleweed back to its initial (inactive) state.
   */
  void reset() {
    setVisible(false);
    myJumpedOver = false;
  }


  /**
   * alter the tumbleweed image appropriately for this frame..
   * @param left whether or not the player is moving left
   * @return how much the score should change by after this
   *         advance.
   */
  int advance(Cowboy cowboy, int tickCount, boolean left,
       int currentLeftBound, int currentRightBound) {
    int retVal = 0;
    // if the tumbleweed goes outside of the display
    // region, set it to invisible since it is
    // no longer in use.
    if((getRefPixelX() + WIDTH <= currentLeftBound) ||
       (getRefPixelX() – WIDTH >= currentRightBound)) {
      setVisible(false);
    }
    // If the tumbleweed is no longer in use (i.e. invisible)
    // it is given a 1 in 100 chance (per game loop)
    // of coming back into play:
    if(!isVisible()) {
      int rand = getRandomInt(100);
      if(rand == 3) {
 // when the tumbleweed comes back into play,
 // we reset the values to what they should
 // be in the active state:
 myJumpedOver = false;
 setVisible(true);
 // set the tumbleweed’s position to the point
 // where it just barely appears on the screen
 // to that it can start approaching the cowboy:
 if(myLeft) {
   setRefPixelPosition(currentRightBound, myY);
   move(-1, 0);
 } else {
   setRefPixelPosition(currentLeftBound, myY);
   move(1, 0);
 }
      }
    } else {
      // when the tumbleweed is active, we advance the
      // rolling animation to the next frame and then
      // move the tumbleweed in the right direction across
      // the screen.
      if(tickCount % 2 == 0) { // slow the animation down a little
 nextFrame();
      }
      if(myLeft) {
 move(-3, 0);
 // if the cowboy just passed the tumbleweed
 // (without colliding with it) we increase the
 // cowboy’s score and set myJumpedOver to true
 // so that no further points will be awarded
 // for this tumbleweed until it goes offscreen
 // and then is later reactivated:
 if((! myJumpedOver) &&
    (getRefPixelX() < cowboy.getRefPixelX())) {
   myJumpedOver = true;
   retVal = cowboy.increaseScoreThisJump();
 }
      } else {
 move(3, 0);
 if((! myJumpedOver) &&
    (getRefPixelX() > cowboy.getRefPixelX() + Cowboy.WIDTH)) {
   myJumpedOver = true;
   retVal = cowboy.increaseScoreThisJump();
 }
      }
    }
    return(retVal);
  }


  /**
   * Gets a random int between
   * zero and the param upper.
   */
  public int getRandomInt(int upper) {
    int retVal = myRandom.nextInt() % upper;
    if(retVal < 0) {
      retVal += upper;
    }
    return(retVal);
  }


}


The TiledLayer Class


As mentioned above, the TiledLayer class is similar to the Sprite class except that a TiledLayer can be composed of multiple cells, each of which is painted with an individually set image frame. The other differences between TiledLayer and Sprite are mostly related to extra functionality missing from TiledLayer: TiledLayer has no transforms, reference pixel, or frame sequence.


Of course the mere fact that you’re simultaneously managing multiple images complicates things a bit. I’ll explain it by going over my subclass of TiledLayer which I have called Grass. This class represents a row of grass in the background which waves back and forth as the game is being played. To make it more interesting, some of the cells in my TiledLayer have animated grasses and others have no tall grasses and hence just consist of a green line representing the ground at the bottom of the cell. Here’s the corresponding image file:


 
WARNING: the tile index for Sprite starts with 0, but the tile index for TiledLayer starts with 1! This is a little confusing (it caused me to get an IndexOutOfBoundsException the first time I made a Sprite because I had assumed that the Sprite images were numbered like the TiledLayer images…). Yet the system is completely logical. In a TiledLayer, the tile index 0 indicates a blank tile (i.e. paint nothing in the cell if the cell’s tile index is set to zero). A Sprite, however, is composed of only one cell, so if you want that cell to be blank, then you can just call setVisible(false), meaning that Sprite doesn’t need to reserve a special index to indicate a blank tile. This little confusion in the indices should not pose a big problem, but it’s something to keep in mind if you can’t figure out why your animation appears to be displaying the wrong images.. Aside from this point, the image file is divided into individual frames or tiles in TiledLayer just as in Sprite, explained above.


The first step in creating your TiledLayer is to decide how many rows and columns of cells you will need. If you don’t want your layer to be rectangular it isn’t a problem because any unused cells are by default set to being blank which prevents them from getting in the way of other images. In my example I have only one row, and I calculate the number of columns based on the width of the screen.


Once you’ve set how many rows and columns you’ll be using, you can fill each cell with a tile using the method setCell(int col, int row, int tileIndex). The tileIndex argument is as explained in the Sprite section and the warning paragraph above. If you would like some of the cells to be filled with animated images, you need to create an animated tile by calling createAnimatedTile(int staticTileIndex), which returns the tile index that has been allotted to your new animated tile. You can make as many animated tiles as you want, but remember that each animated tile can be used in multiple cells if you want the cells to display the same animation simultaneously. In my case, I create only one animated tile and reuse it because I want all of my animated grass to be waving in sync. The cells are set in the constructor of Grass below. To advance the animation you don’t get built-in frame-sequence functionality as in Sprite, so you have to set the frames with the method setAnimatedTile(int animatedTileIndex, int staticTileIndex). This sets the current frame of the given animated tile. Thus all of the cells that have been set to contain the animated tile corresponding to animatedTileIndex will change to the image given by the argument staticTileIndex. To simplify the animation updates, it’s easy to add your own frame-sequence functionality: see the method Grass.advance(int tickCount) for an idea of how to do it.


Here’s the code for the last class, Grass.java:


package net.frog_parrot.jump;


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


/**
 * This class draws the background grass.
 *
 * @author Carol Hamer
 */
public class Grass extends TiledLayer {


  //———————————————————
  //    dimension fields
  //  (constant after initialization)


  /**
   * The width of the square tiles that make up this layer..
   */
  static int TILE_WIDTH = 20;


  /**
   * This is the order that the frames should be displayed
   * for the animation.
   */
  static int[] FRAME_SEQUENCE = { 2, 3, 2, 4 };


  /**
   * This gives the number of squares of grass to put along
   * the bottom of the screen.
   */
  static int COLUMNS;


  /**
   * After how many tiles does the background repeat.
   */
  static int CYCLE = 5;


  /**
   * the fixed Y coordinate of the strip of grass.
   */
  static int TOP_Y;


  //———————————————————
  //    instance fields


  /**
   * Which tile we are currently on in the frame sequence.
   */
  int mySequenceIndex = 0;


  /**
   * The index to use in the static tiles array to get the
   * animated tile..
   */
  int myAnimatedTileIndex;


  //———————————————————
  //   gets / sets


  /**
   * Takes the width of the screen and sets my columns
   * to the correct corresponding number
   */
  static int setColumns(int screenWidth) {
    COLUMNS = ((screenWidth / 20) + 1)*3;
    return(COLUMNS);
  }


  //———————————————————
  //   initialization


  /**
   * constructor initializes the image and animation.
   */
  public Grass() throws Exception {
    super(setColumns(JumpCanvas.DISP_WIDTH), 1,
   Image.createImage(“/icons/grass.png”),
   TILE_WIDTH, TILE_WIDTH);
    TOP_Y = JumpManager.DISP_HEIGHT – TILE_WIDTH;
    setPosition(0, TOP_Y);
    myAnimatedTileIndex = createAnimatedTile(2);
    for(int i = 0; i < COLUMNS; i++) {
      if((i % CYCLE == 0) || (i % CYCLE == 2)) {
 setCell(i, 0, myAnimatedTileIndex);
      } else {
 setCell(i, 0, 1);
      }
    }
  }


  //———————————————————
  //   graphics


  /**
   * sets the grass back to its initial position..
   */
  void reset() {
    setPosition(-(TILE_WIDTH*CYCLE), TOP_Y);
    mySequenceIndex = 0;
    setAnimatedTile(myAnimatedTileIndex, FRAME_SEQUENCE[mySequenceIndex]);
  }


  /**
   * alter the background image appropriately for this frame..
   */
  void advance(int tickCount) {
    if(tickCount % 2 == 0) { // slow the animation down a little
      mySequenceIndex++;
      mySequenceIndex %= 4;
      setAnimatedTile(myAnimatedTileIndex, FRAME_SEQUENCE[mySequenceIndex]);
    }
  }


}


Putting it Together


Now that you have all to the relevant classes, the example needs to be built just as in the “Hello World” example. The classes should be compiled, preverified, and jarred. (Preverification is an extra step that makes bytecode verification easier for J2ME applications; it’s a necessary step but one that is performed by standard utility programs so you don’t need to worry too much about it.) Be sure that all of your resources have been correctly placed in the jar. In my example I need to place the files cowboy.png, tumbleweed.png, and grass.png in a top-level folder called icons in my jar since I constructed my Layers with images created by calls to Image.createImage(“/icons/grass.png”), etc. Also I need to place the correct MANIFEST.MF file in the jar. Here’s the one I used:


MIDlet-1: Hello World, /icons/hello.png, net.frog_parrot.hello.Hello
MIDlet-2: Tumbleweed, /icons/boot.png, net.frog_parrot.jump.Jump
MMIDlet-Description: Example games for MIDP
MIDlet-Name: Example Games
MIDlet-Permissions:
MIDlet-Vendor: frog-parrot.net
MIDlet-Version: 2.0
MicroEdition-Configuration: CLDC-1.0
MicroEdition-Profile: MIDP-2.0


Keep in mind that if you want to put a custom MANIFEST.MF file in your jar instead of an auto-generated one, you need to use the m option when creating your jar.


Once you have your jar file, you need a jad file. Here’s the one I used, which I called jump.jad:


MIDlet-1: Hello, /icons/hello.png, net.frog_parrot.hello.Hello
MIDlet-2: Tumbleweed, /icons/boot.png, net.frog_parrot.jump.Jump
MMIDlet-Description: Example games for MIDP
MIDlet-Jar-URL: jump.jar
MIDlet-Name: Example Games
MIDlet-Permissions:
MIDlet-Vendor: frog-parrot.net
MIDlet-Version: 2.0
MicroEdition-Configuration: CLDC-1.0
MicroEdition-Profile: MIDP-2.0
MIDlet-Jar-Size: 17259


Don’t forget to verify that the MIDlet-Jar-Size is correct if you’re creating the jad file by hand. Also note that this jad file indicates that your jar file needs to contain the icon /icons/boot.png which is used in the game selection menu and looks like this:


 
Once the jar and jad files are ready, you can run the program. I ran it from the command line with the runmidlet script that came with the WTK2.0 toolkit.


Good luck with your own game!


Appendix A: Script Hints


Running the midlet in the emulator from the command line is very simple. All I did was go to the bin directory under the toolkit’s WTK2.0 directory. From there I typed ./bin/emulator followed by an option giving the name of the jad descriptor file corresponding to the MIDlet I wanted to run. For example in my case this was the following line:


./bin/emulator -Xdescriptor:/home/carol/j2me/book/ch02/bin/games.jad


Other emulator options are listed in the toolkit documentation in section D. Command Line Utilities under the heading of Running the Emulator.


The following build script will automatically fix the MIDlet-Jar-Size property in your jad file after rebuilding as long as the MIDlet-Jar-Size property is the last line of the jad file, as it is in the examples I provided.


Note that this script assumes that you have a file tree configured as follows: Under a main directory you must have four subdirectories: bin (which contains this script as well as the jad and MANIFEST.MF files), a tmpclasses directory (which may be empty), a classes directory (containing a subdirectory called images which contains all of your image files), and a src directory which contains the directory tree for the source code of the MIDlet you would like to compile.


# This script builds and preverifies the code
# for the tumbleweed game.


# reset this variable to the path to the correct javac
# command on your system:
JAVA4_HOME=/usr/java/j2sdk1.4.0_01/bin
# reset this variable to the corresct path to the WTK2.0
# directory of the WTK2.0 toolkit that you downloaded:
WTK2_HOME=../../../../WTK2.0


echo “clear directories”
# it’s better not to leave old class files lying
# around where they might accidentally get picked up
# and create errors…
rm ../tmpclasses/net/frog_parrot/hello/*.class
rm ../classes/net/frog_parrot/hello/*.class
rm ../tmpclasses/net/frog_parrot/jump/*.class
rm ../classes/net/frog_parrot/jump/*.class


echo “Compiling source files”


$JAVA4_HOME/javac -bootclasspath $WTK2_HOME/lib/midpapi.zip –
d ../tmpclasses -classpath ../tmpclasses ../src/net/frog_parrot/hello/*.java
../src/net/frog_parrot/jump/*.java


echo “Preverifying class files”


$WTK2_HOME/bin/preverify -classpath $WTK2_HOME/lib/midpapi.zip:
../tmpclasses -d ../classes ../tmpclasses


echo “Jarring preverified class files”
$JAVA4_HOME/jar cmf MANIFEST.MF jump.jar -C ../classes .


echo “Updating JAR size info in JAD file…”


NB=`wc -l jump.jad | awk ‘{print $1}’`
head –lines=$(($NB-1)) jump.jad > jump.jad1
echo “MIDlet-Jar-Size:” `stat -c ‘%s’ jump.jar`>> jump.jad1
cp jump.jad1 jump.jad


There’s one more little hint I would like to pass along to you. I noticed that at the end of the runmidlet script all of the messages generated by the runmidlet program are redirected to /dev/null. If you would like to see your messages for debugging purposes, you should remove the > /dev/null from the end of the script.


Appendix B: Images and the Gimp


If you’re wondering where all of the image files in this example came from, I drew them myself with a free program called the gimp, which you can download from http://www.gimp.org/download.html. If you decide to draw your image files with the gimp and you would like them to have transparent backgrounds, be sure to select a transparent background when you first create a new image file. Then make sure that you select “save background color” when you save the file. Also, you should save it with the .png extension so that the gimp will save it in the right format to be used by a J2ME game.


MIDP 2.0 Games: a Step-by-Step Tutorial with Code Samples (Step 1)

Writing games for small devices is easy and fun with the new MIDP 2.0 API. The biggest difference for the game developer between MIDP 1.0 and MIDP 2.0 is enhanced graphics capabilities. With MIDP 1.0 you can certainly write some fun games–those of us who remember the Atari 2600 will remember that the games were fun even if they weren’t always beautiful. The 2.0 version of the Mobile Information Device Profile doesn’t bring us quite to the level of the latest game cube, but it brings your cell phone at least to the realm of Super Mario Brothers or thereabouts.


In this tutorial I will go over the steps involved in writing a simple game MIDlet. (A MIDlet is like an applet except that it runs on a mobile device rather than in a browser.) I will assume that you know some Java but that you’re new to J2ME. My example is a game involving a cowboy walking through a prairie jumping over tumbleweeds. It’s kind of a silly game, but it illustrates most of the basics that you’ll need when writing more reasonable games.


All of the source code and resources for this example can be found here.


Getting Started


If you haven’t already downloaded and installed J2ME, you can get it at http://java.sun.com/j2me/download.html. The Java 2 Micro Edition Wireless Toolkit contains a MIDlet development environment, a cell-phone emulator, and a number of helpful demo applications with source code. I’m not going to go into too much detail here about the installation since it varies from system to system and the documentation bundled with the download is reasonably clear.


When you examine the demo applications, you’ll notice that they consist of a jar file and a jad file. The jar file contains the class files, the resources, and a manifest file. The jad file contains approximately the same descriptive information with the addition of two items that help the device load the MIDlets: the size and the URL of the MIDlet jar. Here is an example of the jad file I used for my Hello World application:


MIDlet-1: Hello World, /icons/hello.png, net.frog_parrot.hello.Hello
MMIDlet-Description: Hello World for MIDP
MIDlet-Jar-URL: hello.jar
MIDlet-Name: Hello World
MIDlet-Permissions:
MIDlet-Vendor: frog-parrot.net
MIDlet-Version: 2.0
MicroEdition-Configuration: CLDC-1.0
MicroEdition-Profile: MIDP-2.0
MIDlet-Jar-Size: 3201


The MIDlet-1 (and MIDlet-2, etc.) property gives the name of the MIDlet, the location of the MIDlet’s icon, and the MIDlet class to run. The first two items describe how the MIDlet will appear on the menu of MIDlets. The icon should be in the jar file, and its location should be given in the same format as is used by the method Class.getResource(). Thus in this example your jar file should contain a top level folder called icons which contains an icon called hello.png which looks like this:


 


The MIDlet-Jar-Size property gives the size of the corresponding jar file in bytes which you can find by looking at the properties or long listing of the jar file. Be aware that if you rebuild the demos or your own applications using the build script bundled with the toolkit, you must manually update the size of the jar file in the jad file. If the MIDlet-Jar-Size property in the jad file does not match the size of the jar file, the MIDlet will not run. (Since the size generally changes with every build and it’s annoying to open your jad file in a text editor with every build, I’ve included some build script modification suggestions in Appendix A at the end of this article.) The MIDlet-Jar-URL property gives the address of the MIDlet jar relative to the location of the jad file. If the jar file and the jad file are kept in the same directory, this is just the name of the jar file. The other properties are self-explanatory.


Hello World


To illustrate the components of a very basic MIDlet I’ve written a version of the classic “Hello World” application. The MIDlet will display the message “Hello World!” on the screen and remove it (or later put it back) when the “Toggle Msg” button is pressed. The “Exit” button will terminate the MIDlet. The application consists of two classes: the MIDlet class called Hello and the Canvas class called HelloCanvas. How it works is sufficiently simple that I will leave the explanations of the various steps in the comments.


Here’s the code for Hello.java:


package net.frog_parrot.hello;


import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;


/**
 * This is the main class of the hello world demo.
 *
 * @author Carol Hamer
 */
public class Hello extends MIDlet implements CommandListener {


  /**
   * The canvas is the region of the screen that has been allotted
   * to the game.
   */
  HelloCanvas myCanvas;


  /**
   * The Command objects appear as buttons.
   */
  private Command exitCommand = new Command(“Exit”, Command.EXIT, 99);


  /**
   * The Command objects appear as buttons.
   */
  private Command newCommand = new Command(“Toggle Msg”, Command.SCREEN, 1);


  /**
   * Initialize the canvas and the commands.
   */
  public Hello() {
    myCanvas = new HelloCanvas(Display.getDisplay(this));
    myCanvas.addCommand(exitCommand);
    myCanvas.addCommand(newCommand);
    myCanvas.setCommandListener(this);
  }


  //—————————————————————-
  //  implementation of MIDlet


  /**
   * Start the application.
   */
  public void startApp() throws MIDletStateChangeException {
    myCanvas.start();
  }
 
  /**
   * If the MIDlet was using resources, it should release
   * them in this method.
   */
  public void destroyApp(boolean unconditional)
      throws MIDletStateChangeException {
  }


  /**
   * This method is called to notify the MIDlet to enter a paused
   * state.  The MIDlet should use this opportunity to release
   * shared resources.
   */
  public void pauseApp() {
  }


  //—————————————————————-
  //  implementation of CommandListener


  /*
   * Respond to a command issued on the Canvas.
   * (either reset or exit).
   */
  public void commandAction(Command c, Displayable s) {
    if(c == newCommand) {
      myCanvas.newHello();
    } else if(c == exitCommand) {
      try {
 destroyApp(false);
 notifyDestroyed();
      } catch (MIDletStateChangeException ex) {
      }
    }
  }
 
}


Here’s the code for HelloCanvas.java:


package net.frog_parrot.hello;


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


/**
 * This class represents the region of the screen that has been allotted
 * to the game.
 *
 * @author Carol Hamer
 */
public class HelloCanvas extends javax.microedition.lcdui.game.GameCanvas {


  //———————————————————
  //   fields


  /**
   * A handle to the screen of the device.
   */
  Display myDisplay;


  /**
   * whether or not the screen should currently display the
   * “hello world” message.
   */
  boolean mySayHello = true;


  //—————————————————–
  //    initialization and game state changes


  /**
   * Constructor merely sets the display.
   */
  public HelloCanvas(Display d) {
    super(false);
    myDisplay = d;
  }


  /**
   * This is called as soon as the application begins.
   */
  void start() {
    myDisplay.setCurrent(this);
    repaint();
  }


  /**
   * toggle the hello message.
   */
  void newHello() {
    mySayHello = !mySayHello;
    // paint the display
    repaint();
  }


  //——————————————————-
  //  graphics methods


  /**
   * clear the screen and display the hello world message if appropriate.
   */
  public void paint(Graphics g) {
    // x and y are the coordinates of the top corner of the
    // game’s display area:
    int x = g.getClipX();
    int y = g.getClipY();
    // w and h are the width and height of the display area:
    int w = g.getClipWidth();
    int h = g.getClipHeight();
    // clear the screen (paint it white):
    g.setColor(0xffffff);
    g.fillRect(x, y, w, h);
    // display the hello world message if appropriate:.
    if(mySayHello) {
      Font font = g.getFont();
      int fontHeight = font.getHeight();
      int fontWidth = font.stringWidth(“Hello World!”);
      // set the text color to red:
      g.setColor(0x00ff0000);
      g.setFont(font);
      // write the string in the center of the screen
      g.drawString(“Hello World!”, (w – fontWidth)/2,
     (h – fontHeight)/2,
     g.TOP|g.LEFT);
    }
  }


}


 


MIDP 2.0 Games: a Step-by-Step Tutorial with Code Samples (Step 2)

The MIDlet Class

Now it’s time to get started on the real game. If you copy the code from this section and all of the following sections, it should compile together to form the example game “Tumbleweed.”

The MIDlet class controls some of the basic items that are common to all MIDlets (such as start and stop buttons) hence doesn’t need to vary much from one MIDlet to another. This MIDlet class (called Jump) resembles the MIDlet class from my “Hello World” example, but has a few additional items that I will explain before displaying the code. Remember that the MIDlet class is the class that needs to be mentioned in the MIDlet’s jad file.

This MIDlet has several different commands. The user can only pause the game when it’s unpaused, can only unpause the game when it’s paused, and can only start over when the game has ended, so only one of the commands “Go”, “Pause”, and “Play Again” appears at a time. Therefore each time I add one of these two commands to the screen, I remove the other two. Of course it is certainly possible to add several custom commands at a time to the MIDlet if your game needs them. The device’s JVM will deal with how to place them. For example the default emulator creates a menu of commands when there are more commands than available buttons. The additional arguments to the Command’s constructor help the device to decide how to map the Commands to physical buttons. Using static field codes such as Command.BACK or Command.HELP will ensure that standard types of commands will be mapped to their usual buttons on the device. The implementation of the priority argument may vary some from device to device, but in general those commands that have higher priority (indicated by a lower priority value) will be more easily accessible to the user.

The other thing to notice when reading this class is what happens when the method destroyApp() is called. This should be called just before exiting to free up any resources that your game may be using. This example is simple enough that it has no shared resources that it needs to let go of, but it is occupying memory (which is valuable on a small device!) so it’s a good idea to set your MIDlet’s object references to null so that any objects your game was using can be garbage collected. Calling notifyDestroyed() returns control of the device to the surrounding application, but it does not necessarily cause the JVM to exit (unless the user explicitly stops the Java functions and/or turns the device off). So in particular classes that were loaded by your MIDlet will remain in memory even after your game is done. It is especially important to keep this in mind when using static fields as they generally retain their values from one run to the next and they occupy space in memory when they’re no longer in use.

Here’s the code for Jump.java:

package net.frog_parrot.jump;

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

/**
 * This is the main class of the tumbleweed game.
 *
 * @author Carol Hamer
 */
public class Jump extends MIDlet implements CommandListener {

  //———————————————————
  //   game object fields

  /**
   * the command to end the game.
   */
  private Command myExitCommand = new Command(“Exit”, Command.EXIT, 99);

  /**
   * the command to start moving when the game is paused.
   */
  private Command myGoCommand = new Command(“Go”, Command.SCREEN, 1);

  /**
   * the command to pause the game.
   */
  private Command myPauseCommand = new Command(“Pause”, Command.SCREEN, 1);

  /**
   * the command to start a new game.
   */
  private Command myNewCommand = new Command(“Play Again”, Command.SCREEN, 1);

  /**
   * the canvas that all of the game will be drawn on.
   */
  JumpCanvas myCanvas;

  /**
   * the thread that advances the cowboy.
   */
  GameThread myGameThread;

  //—————————————————–
  //    initialization and game state changes

  /**
   * Initialize the canvas and the commands.
   */
  public Jump() {
    myCanvas = new JumpCanvas(this);
    myCanvas.addCommand(myExitCommand);
    myCanvas.addCommand(myGoCommand);
    myCanvas.setCommandListener(this);
  }

  /**
   * Switch the command to the play again command.
   */
  void setNewCommand() {
    myCanvas.removeCommand(myPauseCommand);
    myCanvas.removeCommand(myGoCommand);
    myCanvas.addCommand(myNewCommand);
  }

  /**
   * Switch the command to the go command.
   */
  void setGoCommand() {
    myCanvas.removeCommand(myPauseCommand);
    myCanvas.removeCommand(myNewCommand);
    myCanvas.addCommand(myGoCommand);
  }

  /**
   * Switch the command to the pause command.
   */
  void setPauseCommand() {
    myCanvas.removeCommand(myNewCommand);
    myCanvas.removeCommand(myGoCommand);
    myCanvas.addCommand(myPauseCommand);
  }

  //—————————————————————-
  //  implementation of MIDlet

  /**
   * Start the application.
   */
  public void startApp() throws MIDletStateChangeException {
    myGameThread = new GameThread(myCanvas);
    myCanvas.start();
  }
 
  /**
   * stop and throw out the garbage.
   */
  public void destroyApp(boolean unconditional)
      throws MIDletStateChangeException {
    myGameThread.requestStop();
    myGameThread = null;
    myCanvas = null;
    System.gc();
  }

  /**
   * request the thread to pause.
   */
  public void pauseApp() {
    setGoCommand();
    myGameThread.pause();
  }

  //—————————————————————-
  //  implementation of CommandListener

  /*
   * Respond to a command issued on the Canvas.
   * (either reset or exit).
   */
  public void commandAction(Command c, Displayable s) {
    if(c == myGoCommand) {
      myCanvas.removeCommand(myGoCommand);
      myCanvas.addCommand(myPauseCommand);
      myGameThread.go();
    } else if(c == myPauseCommand) {
      myCanvas.removeCommand(myPauseCommand);
      myCanvas.addCommand(myGoCommand);
      myGameThread.go();
    } else if(c == myNewCommand) {
      myCanvas.removeCommand(myNewCommand);
      myCanvas.addCommand(myGoCommand);
      myGameThread.requestStop();
      myGameThread = new GameThread(myCanvas);
      System.gc();
      myCanvas.reset();
    } else if(c == myExitCommand) {
      try {
 destroyApp(false);
 notifyDestroyed();
      } catch (MIDletStateChangeException ex) {
      }
    }
  }
 
}

The Thread Class

The use of threads is not unique to MIDlets, and should be very familiar to any game developer. In this example the run() method contains the main loop of the game which prompts the GameCanvas to check which keys are pressed and then advance the graphical objects accordingly as long as the game is not paused or stopped.

Here’s the code for GameThread.java:

package net.frog_parrot.jump;

/**
 * This class contains the loop that keeps the game running.
 *
 * @author Carol Hamer
 */
public class GameThread extends Thread {

  //———————————————————
  //   fields

  /**
   * Whether or not the main thread would like this thread
   * to pause.
   */
  boolean myShouldPause;

  /**
   * Whether or not the main thread would like this thread
   * to stop.
   */
  static boolean myShouldStop;

  /**
   * Whether or not this thread has been started.
   */
  boolean myAlreadyStarted;

  /**
   * A handle back to the graphical components.
   */
  JumpCanvas myJumpCanvas;

  //———————————————————-
  //   initialization

  /**
   * standard constructor.
   */
  GameThread(JumpCanvas canvas) {
    myJumpCanvas = canvas;
  }

  //———————————————————-
  //   actions

  /**
   * start or pause or unpause the game.
   */
  void go() {
    if(!myAlreadyStarted) {
      myAlreadyStarted = true;
      start();
    } else {
      myShouldPause = !myShouldPause;
    }
  }

  /**
   * pause the game.
   */
  void pause() {
    myShouldPause = true;
  }

  /**
   * stops the game.
   */
  static void requestStop() {
    myShouldStop = true;
  }

  /**
   * start the game..
   */
  public void run() {
    // flush any keystrokes that occurred before the
    // game started:
    myJumpCanvas.flushKeys();
    myShouldStop = false;
    myShouldPause = false;
    while(true) {
      if(myShouldStop) {
 break;
      }
      if(!myShouldPause) {
 myJumpCanvas.checkKeys();
 myJumpCanvas.advance();
      }
    }
  }

}

 

MIDP 2.0 Games: a Step-by-Step Tutorial with Code Samples (Step 3)

The GameCanvas Class


The GameCanvas class represents the area of the screen that the device has allotted to your game. The javax.microedition.lcdui.game.GameCanvas class differs from its superclass javax.microedition.lcdui.Canvas in two important ways: graphics buffering and the ability to query key states. Both of these changes give the game developer enhanced control over precisely when the program deals with events such as keystrokes and repainting the screen.


The graphics buffering allows all of the graphical objects to be created behind the scenes and then flushed to the screen all at once when they’re ready. This makes animation smoother. I’ve illustrated how to use it in the method advance() in the code below. (The method advance() is called from the main loop of my GameThread object.) Notice that all you need to do is call paint(getGraphics()) and then call flushGraphics(). To make your program more efficient there is even a version of the flushGraphics() method which allows you to repaint just a subset of the screen if you know that only part has changed. As an experiment I tried replacing the calls to paint(getGraphics()) and flushGraphics() with calls to repaint() and then serviceRepaints() as you might if your class extended Canvas instead of GameCanvas. In my simple examples it didn’t make much difference, but if your game has a lot of complicated graphics the GameCanvas version will undoubtedly make a big difference.


If you’re following along in the code below, you’ll notice that after flushing the graphics (still in the method advance()), I have the thread wait one millisecond. This is partially to be sure that the freshly painted graphics stay on the screen for an instant before the next paint, but it is also useful to help the keystroke query work correctly. As I mentioned above, the ability to query key states is one of the differences between GameCanvas and Canvas. With Canvas, if you want to know about keystroke events you must implement the keyPressed(int keyCode) method which the enveloping Java program will call when it wants to tell your program that a key has been pressed. With GameCanvas, you can call the method getKeyStates() whenever your program wants to know which keys have been pressed. Of course the value returned by getKeyStates() (telling you what key(s) have been pressed), is still updated on another thread, so it’s necessary to put a short wait inside your game loop to make sure that the key states value is updated in a timely fashion allowing your game to respond immediately when the user presses a key. Even a millisecond will do the trick. (I earlier wrote a racecar game in which I neglected to put a wait in the main game loop, and I found that the car would go halfway around the track between the time I pressed the lane change key and the time the car actually changed lanes…).


It’s easy to see how these two enhancements in GameCanvas improve control over the order of execution of painting and keystroke-related updates. Going back to my GameThread class, notice that the main game loop first tells my GameCanvas subclass (called JumpCanvas) to query the key states (see the method JumpCanvas.checkKeys() below for details). Then once the key events have been dealt with, the main loop of the GameThread class calls JumpCanvas.advance() which tells the LayerManager to make appropriate updates in the graphics (more on that in the next sections) and then paints the screen and then waits as explained above.


Here’s the code for JumpCanvas.java:


package net.frog_parrot.jump;


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


/**
 * This class is the display of the game.
 *
 * @author Carol Hamer
 */
public class JumpCanvas extends javax.microedition.lcdui.game.GameCanvas {


  //———————————————————
  //   dimension fields
  //  (constant after initialization)


  /**
   * the height of the green region below the ground.
   */
  static int GROUND_HEIGHT = 32;


  /**
   * a screen dimension.
   */
  static int CORNER_X;


  /**
   * a screen dimension.
   */
  static int CORNER_Y;


  /**
   * a screen dimension.
   */
  static int DISP_WIDTH;


  /**
   * a screen dimension.
   */
  static int DISP_HEIGHT;


  /**
   * a font dimension.
   */
  static int FONT_HEIGHT;


  /**
   * the default font.
   */
  static Font FONT;


  /**
   * a font dimension.
   */
  static int SCORE_WIDTH;


  /**
   * The width of the string that displays the time,
   * saved for placement of time display.
   */
  static int TIME_WIDTH;


  //———————————————————
  //   game object fields


  /**
   * a handle to the display.
   */
  Display myDisplay;


  /**
   * a handle to the MIDlet object (to keep track of buttons).
   */
  Jump myJump;


  /**
   * the LayerManager that handles the game graphics.
   */
  JumpManager myManager;


  /**
   * whether or not the game has ended.
   */
  static boolean myGameOver;


  /**
   * the player’s score.
   */
  int myScore = 0;


  /**
   * How many ticks we start with.
   */
  int myInitialGameTicks = 950;


  /**
   * this is saved to determine if the time string needs
   * to be recomputed.
   */
  int myOldGameTicks = myInitialGameTicks;


  /**
   * the number of game ticks that have passed.
   */
  int myGameTicks = myOldGameTicks;


  /**
   * whether or not this has been painted once.
   */
  boolean myInitialized;


  /**
   * The initial time string.
   */
  static String myInitialString = “1:00”;


  /**
   * we save the time string to avoid recreating it
   * unnecessarily.
   */
  String myTimeString = myInitialString;


  //—————————————————–
  //    gets/sets


  /**
   * This is called when the game ends.
   */
  static void setGameOver() {
    myGameOver = true;
    GameThread.requestStop();
  }


  /**
   * Find out if the game has ended.
   */
  static boolean getGameOver() {
    return(myGameOver);
  }


  //—————————————————–
  //    initialization and game state changes


  /**
   * Constructor sets the data.
   */
  public JumpCanvas(Jump midlet) {
    super(false);
    myDisplay = Display.getDisplay(midlet);
    myJump = midlet;
  }


  /**
   * This is called as soon as the application begins.
   */
  void start() {
    myGameOver = false;
    myDisplay.setCurrent(this);
    repaint();
  }


  /**
   * sets all variables back to their initial positions.
   */
  void reset() {
    myManager.reset();
    myScore = 0;
    myGameOver = false;
    myGameTicks = myInitialGameTicks;
    myOldGameTicks = myInitialGameTicks;
    repaint();
  }


  /**
   * clears the key states.
   */
  void flushKeys() {
    getKeyStates();
  }


  //——————————————————-
  //  graphics methods


  /**
   * paint the game graphics on the screen.
   */
  public void paint(Graphics g) {
    // perform the calculations if necessary:
    if(!myInitialized) {
      CORNER_X = g.getClipX();
      CORNER_Y = g.getClipY();
      DISP_WIDTH = g.getClipWidth();
      DISP_HEIGHT = g.getClipHeight();
      FONT = g.getFont();
      FONT_HEIGHT = FONT.getHeight();
      SCORE_WIDTH = FONT.stringWidth(“Score: 000”);
      TIME_WIDTH = FONT.stringWidth(“Time: ” + myInitialString);
      myInitialized = true;
    }
    // clear the screen:
    g.setColor(0xffffff);
    g.fillRect(CORNER_X, CORNER_Y, DISP_WIDTH, DISP_HEIGHT);
    g.setColor(0x0000ff00);
    g.fillRect(CORNER_X, CORNER_Y + DISP_HEIGHT – GROUND_HEIGHT,
        DISP_WIDTH, DISP_HEIGHT);
    // create (if necessary) then paint the layer manager:
    try {
      if(myManager == null) {
 myManager = new JumpManager(CORNER_X, CORNER_Y + FONT_HEIGHT*2,
      DISP_WIDTH, DISP_HEIGHT – FONT_HEIGHT*2 – GROUND_HEIGHT);
      }
      myManager.paint(g);
    } catch(Exception e) {
      errorMsg(g, e);
    }
    // draw the time and score
    g.setColor(0);
    g.setFont(FONT);
    g.drawString(“Score: ” + myScore,
   (DISP_WIDTH – SCORE_WIDTH)/2,
   DISP_HEIGHT + 5 – GROUND_HEIGHT, g.TOP|g.LEFT);
    g.drawString(“Time: ” + formatTime(),
     (DISP_WIDTH – TIME_WIDTH)/2,
     CORNER_Y + FONT_HEIGHT, g.TOP|g.LEFT);
    // write game over if the game is over
    if(myGameOver) {
      myJump.setNewCommand();
      // clear the top region:
      g.setColor(0xffffff);
      g.fillRect(CORNER_X, CORNER_Y, DISP_WIDTH, FONT_HEIGHT*2 + 1);
      int goWidth = FONT.stringWidth(“Game Over”);
      g.setColor(0);
      g.setFont(FONT);
      g.drawString(“Game Over”, (DISP_WIDTH – goWidth)/2,
           CORNER_Y + FONT_HEIGHT, g.TOP|g.LEFT);
    }
  }


  /**
   * a simple utility to make the number of ticks look like a time…
   */
  public String formatTime() {
    if((myGameTicks / 16) + 1 != myOldGameTicks) {
      myTimeString = “”;
      myOldGameTicks = (myGameTicks / 16) + 1;
      int smallPart = myOldGameTicks % 60;
      int bigPart = myOldGameTicks / 60;
      myTimeString += bigPart + “:”;
      if(smallPart / 10 < 1) {
 myTimeString += “0”;
      }
      myTimeString += smallPart;
    }
    return(myTimeString);
  }


  //——————————————————-
  //  game movements


  /**
   * Tell the layer manager to advance the layers and then
   * update the display.
   */
  void advance() {
    myGameTicks–;
    myScore += myManager.advance(myGameTicks);
    if(myGameTicks == 0) {
      setGameOver();
    }
    // paint the display
    try {
      paint(getGraphics());
      flushGraphics();
    } catch(Exception e) {
      errorMsg(e);
    }
    // we do a very short pause to allow the other thread
    // to update the information about which keys are pressed:
    synchronized(this) {
      try {
 wait(1);
      } catch(Exception e) {}
    }
  }


  /**
   * Respond to keystrokes.
   */
  public void checkKeys() {
    if(! myGameOver) {
      int keyState = getKeyStates();
      if((keyState & LEFT_PRESSED) != 0) {
 myManager.setLeft(true);
      }
      if((keyState & RIGHT_PRESSED) != 0) {
 myManager.setLeft(false);
      }
      if((keyState & UP_PRESSED) != 0) {
 myManager.jump();
      }
    }
  }


  //——————————————————-
  //  error methods


  /**
   * Converts an exception to a message and displays
   * the message..
   */
  void errorMsg(Exception e) {
    errorMsg(getGraphics(), e);
    flushGraphics();
  }


  /**
   * Converts an exception to a message and displays
   * the message..
   */
  void errorMsg(Graphics g, Exception e) {
    if(e.getMessage() == null) {
      errorMsg(g, e.getClass().getName());
    } else {
      errorMsg(g, e.getClass().getName() + “:” + e.getMessage());
    }
  }


  /**
   * Displays an error message if something goes wrong.
   */
  void errorMsg(Graphics g, String msg) {
    // clear the screen
    g.setColor(0xffffff);
    g.fillRect(CORNER_X, CORNER_Y, DISP_WIDTH, DISP_HEIGHT);
    int msgWidth = FONT.stringWidth(msg);
    // write the message in red
    g.setColor(0x00ff0000);
    g.setFont(FONT);
    g.drawString(msg, (DISP_WIDTH – msgWidth)/2,
   (DISP_HEIGHT – FONT_HEIGHT)/2, g.TOP|g.LEFT);
    myGameOver = true;
  }


}


 


MIDP 2.0 Games: a Step-by-Step Tutorial with Code Samples (Step 4)

Now that I’ve discussed the special functions of a GameCanvas, I’d like to go over the main function of a Canvas in general, namely painting the screen. This usually takes place in the method paint(Graphics g) which you can override. The Graphics object can be queried for screen dimensions and then can be used to draw strings, images, and simple geometric objects such as rectangles. The Graphics object has a rich selection methods for drawing things on the screen. (It’s similar to the java.awt.graphics package except that since javax.microedition.lcdui.Graphics is intended for small devices it saves memory by having each shape be drawn by a simple method call instead of creating individual objects.) It would take several pages for me to go over all of the things that you can draw with the Graphics object, so here I will stick to discussing the parts of the class that I used. Plus the JavaDoc for javax.microedition.lcdui.Graphics is quite thorough and clear, so you should definitely look it over when writing your game.


In my example game “Tumbleweed” I need to draw a cowboy walking through a prairie jumping over tumbleweeds. The screen during the game looks like this:



As you can see I’ve put the score on the bottom and the time remaining on the top. (To simplify the game I just have it end when the player runs out of time.) As the cowboy is walking along, I would like his background to scroll to the right or to the left (otherwise he won’t have very far to go on such a small screen…) but I would like the time and the score to stay in place. To accomplish this I have my JumpCanvas class take care of painting the stable strip on the top and the bottom of the screen and I delegate the interesting graphics to the LayerManager (more details on that in the next section).


Looking in the paint(Graphics g) method, you see that the first step is to use the graphics object to get the screen dimensions and then use that information to calculate where the objects should be placed. If you’re interested in maintaining Java’s “write once, run anywhere” philosophy, it is obviously better to base the screen layout on the (dynamically determined) dimensions of the current screen rather than basing the dimensions on fixed constants. Even so your game will likely look strange on a screen that is significantly larger or smaller than the one you wrote the game on. You may want to throw an Exception if the screen size is outside of a reasonable max or min.


At the risk of belaboring the obvious, I’ll point out that in the paint(Graphics g) method, after calculating the appropriate sizes for the top and bottom regions, I paint the top one white and the bottom one green with g.fillRect and then I use g.drawString to add the time and the score. (Don’t ask me why my prairie has both green grass AND tumbleweeds: my only excuse is that I know more about Java than I know about the wild west…) Then I calculate the size of the region between them and pass it along to my subclass of LayerManager.


The LayerManager Class


The interesting graphical objects in a J2ME game are usually represented by subclasses of the javax.microedition.lcdui.game.Layer class. The background layers could be instances of javax.microedition.lcdui.game.TiledLayer and the player (and his enemies) would likely be instances of javax.microedition.lcdui.game.Sprite, both of which are subclasses of Layer. The LayerManager class helps you to organize all of these graphical layers. The order in which you append your Layers to your LayerManager determines the order in which they will be painted. (The first one appended is the last one painted.) The top layers will cover the lower layers although you can allow parts of the lower layers to show through by creating image files that have transparent regions.


Probably the most useful aspect of the LayerManager class is that you can create a graphical painting that is much larger than the screen and then choose which section of it will appear on the screen. Imagine drawing a huge and elaborate drawing and then covering it with a piece of paper that has a small rectangular hole that you can move around. The whole drawing represents what you can stock into the LayerManager, and the hole is the window showing the part that appears on the screen at any given time. Allowing the possibility of a virtual screen that is much larger than the actual screen is extremely helpful for games on devices with very small screens. It will save you huge amounts of time and effort if for example your game involves a player exploring an elaborate dungeon. The confusing part is that this means that you have to deal with two separate coordinate systems. The Graphics object of the GameCanvas has one coordinate system, but the various Layers need to be placed in the LayerManager according to the LayerManager’s coordinate system. So keep in mind that the method LayerManager.paint(Graphics g, int x, int y) paints the layer on the screen according to the coordinates of the GameCanvas whereas the method LayerManager.setViewWindow(int x, int y, int width, int height) sets the visible rectangle of the LayerManager in terms of the LayerManager’s coordinate system.


In my example I have a very simple background (it’s just a repeating series of patches of grass), but I would like the cowboy to stay in the middle of the screen as he goes to the right and left, so I need to continuously change which part of the LayerManager’s graphical area is visible. I do this by calling the method setViewWindow(int x, int y, int width, int height) from the paint(Graphics g) method of my subclass of LayerManager (called JumpManager). More precisely, what happens is the following: The main loop in the GameThread calls JumpCanvas.checkKeys() which queries the key states and tells the JumpManager class whether the cowboy should be walking to the right or to the left and whether he should be jumping. JumpCanvas passes this information along to JumpManager by calling the methods setLeft(boolean left) or jump(). If the message is to jump, the JumpManager calls jump() on the cowboy Sprite. If the message is that the cowboy is going to the left (or similarly to the right), then when the GameThread calls the JumpCanvas to tell the JumpManager to advance (in the next step of the loop), the JumpManager tells the cowboy sprite to move one pixel to the left and compensates by moving the view window one pixel to the right to keep the cowboy in the center of the screen. These two actions are accomplished by incrementing the field myCurrentLeftX (which is the x-coordinate that is sent to the method setViewWindow(int x, int y, int width, int height)) and then calling myCowboy.advance(gameTicks, myLeft). Of course I could keep the cowboy centered by not moving him and not appending him to the LayerManager but rather painting him separately afterwards, but it’s easier to keep track of everything by putting all of the moving graphics on one set of layers and then keeping the view window focused on the cowboy Sprite. While telling the cowboy to advance his position, I also have the tumbleweed Sprites advance their positions and I have the grass TiledLayer advance its animation and then I check if the cowboy has collided with any tumbleweeds, but I will go into more detail on those steps in the following sections. After moving the game pieces around, the JumpManager calls the method wrap() to see if the view window has reached the edge of the background, and if so, to move all of the game objects so that the background appears to continue indefinitely in both directions. Then the JumpCanvas repaints everything and then the game loop begins again.


I’ll just add a few words here about the method wrap(). The class LayerManager unfortunately does not have a built in wrapping capability for the case in which you have a simple background that you would like to have repeat indefinitely. The LayerManager’s graphical area will appear to wrap when the coordinates sent to setViewWindow(int x, int y, int width, int height) exceed the value Integer.MAX_VALUE, but that is unlikely to help you. Thus you have to write your own functions to prevent the player Sprite from leaving the region that contains background graphics. In my example, the background grass repeats after the number of pixels given by Grass.TILE_WIDTH*Grass.CYCLE. So whenever the x-coordinate of the view window (myCurrentLeftX) is an integer multiple of the length of the background, I move the view window back to the center and also move all of the Sprites in the same direction which seamlessly prevents the player from reaching the edge.


Here’s the code for JumpManager.java:


package net.frog_parrot.jump;


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


/**
 * This handles the graphics objects.
 *
 * @author Carol Hamer
 */
public class JumpManager extends javax.microedition.lcdui.game.LayerManager {


  //———————————————————
  //   dimension fields
  //  (constant after initialization)


  /**
   * The x-coordinate of the place on the game canvas where
   * the LayerManager window should appear, in terms of the
   * coordiantes of the game canvas.
   */
  static int CANVAS_X;


  /**
   * The y-coordinate of the place on the game canvas where
   * the LayerManager window should appear, in terms of the
   * coordiantes of the game canvas.
   */
  static int CANVAS_Y;


  /**
   * The width of the display window.
   */
  static int DISP_WIDTH;


  /**
   * The height of this object’s graphical region. This is
   * the same as the height of the visible part because
   * in this game the layer manager’s visible part scrolls
   * only left and right but not up and down.
   */
  static int DISP_HEIGHT;


  //———————————————————
  //   game object fields


  /**
   * the player’s object.
   */
  Cowboy myCowboy;


  /**
   * the tumbleweeds that enter from the left.
   */
  Tumbleweed[] myLeftTumbleweeds;


  /**
   * the tumbleweeds that enter from the right.
   */
  Tumbleweed[] myRightTumbleweeds;


  /**
   * the object representing the grass in the background..
   */
  Grass myGrass;


  /**
   * Whether or not the player is currently going left.
   */
  boolean myLeft;


  /**
   * The leftmost x-coordinate that should be visible on the
   * screen in terms of this objects internal coordinates.
   */
  int myCurrentLeftX;


  //—————————————————–
  //    gets/sets


  /**
   * This tells the player to turn left or right.
   * @param left whether or not the turn is towards the left..
   */
  void setLeft(boolean left) {
    myLeft = left;
  }


  //—————————————————–
  //    initialization and game state changes


  /**
   * Constructor merely sets the data.
   * @param x The x-coordinate of the place on the game canvas where
   * the LayerManager window should appear, in terms of the
   * coordiantes of the game canvas.
   * @param y The y-coordinate of the place on the game canvas where
   * the LayerManager window should appear, in terms of the
   * coordiantes of the game canvas.
   * @param width the width of the region that is to be
   * occupied by the LayoutManager.
   * @param height the height of the region that is to be
   * occupied by the LayoutManager.
   */
  public JumpManager(int x, int y, int width, int height) {
    CANVAS_X = x;
    CANVAS_Y = y;
    DISP_WIDTH = width;
    DISP_HEIGHT = height;
    myCurrentLeftX = Grass.CYCLE*Grass.TILE_WIDTH;
    setViewWindow(0, 0, DISP_WIDTH, DISP_HEIGHT);
  }


  /**
   * sets all variables back to their initial positions.
   */
  void reset() {
    if(myGrass != null) {
      myGrass.reset();
    }
    if(myCowboy != null) {
      myCowboy.reset();
    }
    if(myLeftTumbleweeds != null) {
      for(int i = 0; i < myLeftTumbleweeds.length; i++) {
 myLeftTumbleweeds[i].reset();
      }
    }
    if(myRightTumbleweeds != null) {
      for(int i = 0; i < myRightTumbleweeds.length; i++) {
 myRightTumbleweeds[i].reset();
      }
    }
    myLeft = false;
    myCurrentLeftX = Grass.CYCLE*Grass.TILE_WIDTH;
  }


  //——————————————————-
  //  graphics methods


  /**
   * paint the game graphic on the screen.
   * initialization code is included here because some
   * of the screen dimensions are required for initialization.
   */
  public void paint(Graphics g) throws Exception {
    // create the player:
    if(myCowboy == null) {
      myCowboy = new Cowboy(myCurrentLeftX + DISP_WIDTH/2,
       DISP_HEIGHT – Cowboy.HEIGHT – 2);
      append(myCowboy);
    }
    // create the tumbleweeds to jump over:
    if(myLeftTumbleweeds == null) {
      myLeftTumbleweeds = new Tumbleweed[2];
      for(int i = 0; i < myLeftTumbleweeds.length; i++) {
 myLeftTumbleweeds[i] = new Tumbleweed(true);
 append(myLeftTumbleweeds[i]);
      }
    }
    if(myRightTumbleweeds == null) {
      myRightTumbleweeds = new Tumbleweed[2];
      for(int i = 0; i < myRightTumbleweeds.length; i++) {
 myRightTumbleweeds[i] = new Tumbleweed(false);
 append(myRightTumbleweeds[i]);
      }
    }
    // create the background object:
    if(myGrass == null) {
      myGrass = new Grass();
      append(myGrass);
    }
    // this is the main part of the method:
    // we indicate which rectangular region of the LayerManager
    // should be painted on the screen and then we paint
    // it where it belongs.  The call to paint() below
    // prompts all of the appended layers to repaint themselves.
    setViewWindow(myCurrentLeftX, 0, DISP_WIDTH, DISP_HEIGHT);
    paint(g, CANVAS_X, CANVAS_Y);
  }


  /**
   * If the cowboy gets to the end of the graphical region,
   * move all of the pieces so that the screen appears to wrap.
   */
  void wrap() {
    if(myCurrentLeftX % (Grass.TILE_WIDTH*Grass.CYCLE) == 0) {
      if(myLeft) {
 myCowboy.move(Grass.TILE_WIDTH*Grass.CYCLE, 0);
 myCurrentLeftX += (Grass.TILE_WIDTH*Grass.CYCLE);
 for(int i = 0; i < myLeftTumbleweeds.length; i++) {
   myLeftTumbleweeds[i].move(Grass.TILE_WIDTH*Grass.CYCLE, 0);
 }
 for(int i = 0; i < myRightTumbleweeds.length; i++) {
   myRightTumbleweeds[i].move(Grass.TILE_WIDTH*Grass.CYCLE, 0);
 }
      } else {
 myCowboy.move(-(Grass.TILE_WIDTH*Grass.CYCLE), 0);
 myCurrentLeftX -= (Grass.TILE_WIDTH*Grass.CYCLE);
 for(int i = 0; i < myLeftTumbleweeds.length; i++) {
   myLeftTumbleweeds[i].move(-Grass.TILE_WIDTH*Grass.CYCLE, 0);
 }
 for(int i = 0; i < myRightTumbleweeds.length; i++) {
   myRightTumbleweeds[i].move(-Grass.TILE_WIDTH*Grass.CYCLE, 0);
 }
      }
    }
  }


  //——————————————————-
  //  game movements


  /**
   * Tell all of the moving components to advance.
   * @param gameTicks the remainaing number of times that
   *        the main loop of the game will be executed
   *        before the game ends.
   * @return the change in the score after the pieces
   *         have advanced.
   */
  int advance(int gameTicks) {
    int retVal = 0;
    // first we move the view window
    // (so we are showing a slightly different view of
    // the manager’s graphical area.)
    if(myLeft) {
      myCurrentLeftX–;
    } else {
      myCurrentLeftX++;
    }
    // now we tell the game objects to move accordingly.
    myGrass.advance(gameTicks);
    myCowboy.advance(gameTicks, myLeft);
    for(int i = 0; i < myLeftTumbleweeds.length; i++) {
      retVal += myLeftTumbleweeds[i].advance(myCowboy, gameTicks,
      myLeft, myCurrentLeftX, myCurrentLeftX + DISP_WIDTH);
      retVal -= myCowboy.checkCollision(myLeftTumbleweeds[i]);
    }
    for(int i = 0; i < myLeftTumbleweeds.length; i++) {
      retVal += myRightTumbleweeds[i].advance(myCowboy, gameTicks,
           myLeft, myCurrentLeftX, myCurrentLeftX + DISP_WIDTH);
      retVal -= myCowboy.checkCollision(myRightTumbleweeds[i]);
    }
    // now we check if we have reached an edge of the viewable
    // area, and if so we move the view area and all of the
    // game objects so that the game appears to wrap.
    wrap();
    return(retVal);
  }


  /**
   * Tell the cowboy to jump..
   */
  void jump() {
    myCowboy.jump();
  }


}