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.


Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.