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

}

 

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.