HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

As time goes by, J2ME is the buzzword that's appearing in more and more magazines and talked about beside more office coffee machines than any other. And slowly, the big handset manufacturers are releasing their first MIDP-compliant phones into the marketplace. In previous issues of JDJ (Vol. 6, issues 7, 8, and 9), you may have read Jason Briggs' articles introducing general MIDlet development techniques. This article will take a swift sidestep and focus on MIDP as a platform for developing games. We can assure you, in the near future, you won't be firing up Snake the next time your train is late.

Tied to the '80s
In the computer industry we're all becoming accustomed - and complacent - to the fact that processors get faster, memory capacity increases, disks become more capacious, and applications, as a result, become increasingly bloated. Most triple-A game titles released for the PC and console market now feature incredibly immersive three-dimensional environments and meticulously rendered full-motion video, and today's gamer typically demands this of his or her next purchase. The downside is that not only do these titles carry a development price tag upwards of $3 million and require two years of sweat (sometimes blood) and tears from a large development team, they also require a computer with impressive processing and graphics capabilities to run them. In comparison to these behemoths, the average MIDP device has a small, limited-color screen, very little RAM - often less than a megabyte - restricted amounts of data storage, and worst of all, a processor with only a few MHz.

Some may see this as a major flaw in the mobile gaming market; however, we strongly disagree. It might have seemed that the days when the bored teenage bedroom coder could cobble together a Manic Miner or Skool Daze were truly behind us - but not anymore. J2ME has taken us straight back to 1983; an era when the only thing that counted was pure, hard-core playability. Only this time we can get our fix where we want - and if you're really lucky, you may even get mistaken for tapping out a quick text-message to your boss!

Goals
In this article we take you through some of the basic concepts of game architecture, producing sample MIDP code as we go, and we'll discuss the salient features of the various libraries. We don't aim to produce a complete game, but by the end of the article you should understand what's needed to produce a simple game of your own and have some skeleton code upon which to base your work.

Getting Ready
As with the previous JDJ articles on MIDP, we'll use Sun's J2SE SDK1.3 and the J2ME Wireless Toolkit (WTK). If you don't have them already, go to http://java.sun.com/j2se/1.3/ or http:// java.sun.com/products/j2mewtoolkit/ to download the latest versions.

Although we won't be working with it specifically in this article, also worth a mention is Sun's Forte for Java, an IDE that tightly integrates itself with the WTK. The Community Edition is available for a free download at www.sun.com/forte /ffj/buy.htm. If you do use Forte, be sure to install it before you install the WTK.

For the purposes of these examples, we'll assume a couple of standard directories, namely:

  • C:\jdk1.3: The root directory for the JDK
  • C:\j2mewtk: The root directory for the Wireless Toolkit
Creating the Project
For this project we'll work with KToolbar. It's a small tool that comes standard with the WTK and gives a helpful interface to some of MIDP's peculiarities. KToolbar manages all the stages of compilation, preverification, JAR construction, and JAD file management for you. It also helps keep your source code organized by imposing a directory structure to work within. A quick word of warning: it's a useful tool, but not very intelligent. Be sure to check things by hand if you get yourself in a pickle.

The WTK will have installed itself into your start menu as the J2ME Wireless Toolkit. In that group will be a shortcut to KToolbar. Launch it and press the New Project button.

Enter both the Project Name and the MIDlet Class Name as "MyGame" and click on Create Project. Click OK on the settings box that appears. The defaults are fine.

You should see the output as in Figure 1. Your project has been created under the apps directory of the WTK installation.

Figure 1

Three directories , shown in Figure 2, will have been created for you:

  • bin: The JAD and JAR files will be created here when you build your project.
    • src: This is where you place your Java source files. KToolbar will compile anything under here with javac and add the compiled classes into the distribution JAR file in the bin directory.
    • res: Put any resource files you use in your project in this directory. They will be added to the JAR file. You should not use the res/ prefix when referencing these files in your code, e.g., a file .../MyGame/res/images/myImage.png should be referenced in your MyGame Java code as /images/myImage.png.

    Figure 2

    A quick refresher - all MIDlets must contain the following methods:

    public void startApp()
    public void pauseApp()
    public void destroyApp( boolean unconditional )

    The WTK doesn't provide any skeleton code, so create MyGame.java in the src directory and enter the following skeleton (see Listing 1).

    This code sets up a basic TextBox to be displayed on the screen, as in Figure 3, and ties an Exit button to the first available soft key on the device. On the default phone this is the top-left button. The commandAction method (from the CommandListener interface) ties the correct bit of functionality to the Command.

    Figure 3

    You'll notice that this MIDlet has an init() method that really doesn't do much. What's interesting is that this method is restricted so it can be run only once during the life of the MIDlet. Contrary to popular expectation, the startApp() method can in fact be called multiple times during the execution of a MIDlet. This isn't as important in the simple case we have so far, but will become necessary quite soon.

    MIDP divides its UI library into two sets of classes, the high-level API and the low-level API. If you've played with MIDP before, then you're already familiar with the TextBox object and the other high-level API objects such as Alert, Form, and List. We don't want to spend any time discussing these here as they aren't very useful to game developers. We like to get our hands dirty and play with individual pixels, organizing the screen how we want it, so we need to use the low-level API. That's something we'll do right away by adding a splash screen to the MIDlet.

    A Splash Screen
    Splash screens are used at the start of a game. They're short animations or stills that are displayed for a few seconds and can usually be dismissed by the user at any time. It's a perfect opportunity to show off your team logo or a funky title screen for your game.

    The following code implements a Splash class that's responsible for displaying and dismissing itself. The splash screen is dismissed either after a set period of time, or when the user presses any key or hits the touch-screen (if the device has one).

    This allows us to add a splash screen to our code by just constructing a new instance of the Splash class. Technically, a Displayable cannot dismiss itself without having another Displayable to replace it, so the constructor takes two parameters: the Display and the Displayable that we want to follow our splash screen (see Listing 2).

    The work of drawing anything to the screen is done by the paint method. In this case we just clear the screen and then draw the contents of a preloaded Image object into the graphics context. Note that we clear the screen first. In MIDP it's often beneficial to draw to the entire canvas. System screens that are thrown up by the device - perhaps an incoming telephone call - can interrupt the MIDlet screen. When those system screens are dismissed, the original MIDlet screen is left corrupted and needs to be fully redrawn. When the screen is interrupted in this way the canvas is informed by the reinvocation of its hideNotify() and showNotify() methods. The call to showNotify() is always followed by a call to paint().

    The clear() method raises some notable points. First, we can see how to get some basic details about the device we're running on. The Display object exposes the size of the display and whether the device supports color or not - and if so, how many colors. We can also see that MIDP thinks there's only one active color and that all drawing commands will be rendered in this color. There's no Color class, so we must use a single integer to hold a grayscale shade (which can be used by a color device) or an integer RGB-triplet for colors.

    The drawRect() and fillRect() methods are also worth mentioning. When drawing the outline of a rectangle (drawRect(...)), the outline is 1 pixel greater than the width and height specified. fillRect() works differently, however: fillRect(x,y,w,h) fills in the area inside the rectangle that would have been drawn by drawRect(x,y,w,h). In Figure 4, the black squares show the additional area that would be affected by the call drawRect( 0, 0, 4, 2 ), compared to the purple squares, which show the rectangle created with fillRect( 0, 0, 4, 2 ).

    Figure 4

    Basic Game Architecture
    At the heart of every game is the main game loop, seen in Figure 5. Put simply, it's a basic series of procedures for getting the player's input, updating the game state, and displaying the output.

    Figure 5
    • Initialization: This is the stuff that happens at the start of the game: setting up the screen, showing intros, options menus, etc.
    • Player input: These are routines that take the player's input and store it in a way that allows the game state to change as necessary. We can handle this easily through the event-based API of the canvas using the following methods: keyPressed(), keyRepeated(), keyReleased(), pointer- Pressed(), pointerDragged(), pointerReleased(), and the CommandListener's commandAction() method.
    • Update game internals: This is the real meat of the game. Here we update all the game objects, check for collisions, update the scores, move the computer-controlled players, and so on.
    • Display the screen: Here, we convert the internal game state to the graphic that we then present on the screen. Each of the game's actors and background objects are drawn onto a graphics context.
    This can be done in two ways. Either the graphics can be drawn straight to the screen, or the graphics can be drawn to an off-screen buffer and when all the drawing is complete, this buffer is drawn onto the main screen in one go (also known as double buffering).
    Double buffering is an easy way to get flicker-free animation, because copying the entire off-screen buffer to the live display is usually a very fast process. Some MIDP devices support double buffering in hardware, so it's available transparently for free, programmatically speaking. You can test for this with the isDoubleBuffered() method of the canvas object.
  • Ending the game: At the end of the game, there'll normally be some sort of ending sequence, perhaps an animation or high score table.
The Architecture
What we propose for a program architecture is explained in Figure 6.

Figure 6

A GameCanvas Skeleton
Your game canvas code can be structured roughly as shown in Listing 3. You can call the GameCanvas class from your main MIDlet class and then launch the main game loop thread from there, for example in a newGame() method.

The paint() method needs to be called from your main game loop so that there's a screen redraw for every frame. You aren't supposed to call it directly. Instead the main game loop can utilize the repaint() method to request a repaint and the serviceRepaints() method to force waiting repaint requests to be executed immediately.

In the event-based methods keyPressed() and keyReleased(), you can test which keys have been pressed and set a state variable that will be read and processed by the game loop thread.

switch (getGameAction( param )) {
case Canvas.LEFT:
key_left = true;
break;

case Canvas.RIGHT:
key_right = true;
break;
...
}

The game loop thread can be launched from the newGame() method by calling:

GameLoop gameLoop = new GameLoop( this );
Thread t = new Thread( gameLoop );
t.start();

A Main Game Loop Thread Skeleton
Listing 4 is an example of a main game loop thread skeleton. From here you can start developing some fun MIDlets, but be warned: you may discover there's a rough ride ahead. The current stock of MIDP devices aren't speed demons and the libraries are, arguably, still too small.

Author Bios
Mark Quinn is the games technical manager at the wireless entertainment company iFone. [email protected]

Julien Tranchant is a developer at the games development house Aqua Pacific.

	



Listing 1


import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
 /**
  *
  * @author mark
  * @version
  */
  public class MyGame
  extends MIDlet
  implements CommandListener {
 private Display display;
  private boolean initialized = false;
  private Command exitCommand = new Command( "Exit", Command.EXIT, 
  1 );
  private Displayable dummy;
 /**
  *
  */
  public Ast() {
  }
 /**
  * Performs any initializations necessary.
  * Only runs once per MIDlet execution.
  */
  private void init() {
  if (!initialized) {
  display = Display.getDisplay( 
  this );
 dummy = new TextBox( "Placeholder", 
  "replace me with a game canvas",
  30, TextField.ANY );
  dummy.addCommand( exitCommand );
  dummy.setCommandListener( this );
 initialized = true;
  }
  }
 public void startApp() {
  init();
  display.setCurrent( dummy );
  }
 public void pauseApp() {
  }
 /**
  * Perform any cleanup and resource freeing necessary.
  */
  public void destroyApp( boolean unconditional ) {
  // we don't need to do anything here yet
  }
 /**
  * Respond to commands, including exit
  * On the exit command, cleanup and notify the execution environment
  * that the MIDlet has destroyed itself.
  */
  public void commandAction(Command c, Displayable s) {
 if (c == exitCommand) {
  destroyApp( false 
  );
  notifyDestroyed();
  }
  }
  }
  
Listing 2

import java.util.*;
import javax.microedition.lcdui.*;
 public class Splash
  extends Canvas {
 private Display display;
  private Displayable next;
  private Timer timer = new Timer();
 public Splash( Display display, Displayable next ) 
  {
  this.display = display;
  this.next = next;
 display.setCurrent( this );
  }
 protected void showNotify() {
  timer.schedule( new TimerTask() 
  { public void run() { displayNext(); }}
  , 10000 );
  }
 protected void hideNotify() {
  timer.cancel();
  }
 protected void keyPressed( int keyCode ) {
  displayNext();
  }
 protected void pointerPressed( int x, int y 
  ) {
  displayNext();
  }
 private void displayNext() {
  display.setCurrent( next 
  );
  }
 private void clear( Graphics gfx ) {
 int r=0, g=0, b=0, gray=0;
  boolean color = display.isColor();
 // save out the current 
  color
  if (color) {
  r = 
  gfx.getRedComponent();
  g = 
  gfx.getBlueComponent();
  b = 
  gfx.getGreenComponent();
  }
  else {
  gray 
  = gfx.getGrayScale();
  }
 // clear the graphics 
  context
  gfx.setGrayScale( 255 
  );
  gfx.fillRect( -1, -1, 
  getWidth() + 1, getHeight() + 1 );
 // restore the current 
  color;
  if (color) {
  gfx.setColor( 
  r, g, b );
  }
  else {
  gfx.setGrayScale( 
  gray );
  }
  }
 protected void paint( Graphics g ) {
  clear( g );
 Image logo = null;
  try {
  logo = Image.createImage( 
  "/images/logo.png" );
  }
  catch (java.io.IOException e) {}
 g.drawImage(logo, 0, 0, g.LEFT|g.TOP);
  }


}

Make the following additions to MyGame.java.
 /**
  * Performs any initializations necessary and displays 
  the splas
  */
  private void init() {
  display = Display.getDisplay( 
  this );
 dummy = new TextBox( "Placeholder", 
  "replace me with a game canvas",
  30, TextField.ANY );
  dummy.addCommand( exitCommand 
  );
  dummy.setCommandListener( this 
  );
 showSplash( display, dummy 
  );
 initialized = true;
  }
 public void showSplash( Display d, Displayable 
  next ) {
  new Splash( display, next );
  }
  
 Listing 3

import javax.microedition.lcdui.*;
 public class GameCanvas
  extends Canvas
  implements CommandListener {
 /**
  * Creates new GameCanvas
  */
  public GameCanvas( MyGame myGame ) {
  // Do initialization and set 
  an exit soft button
 addCommand( new Command( "Exit", 
  Command.EXIT, 0 ) );
  setCommandListener(this);
  }
 public void paint( Graphics g ) {
  // Paint everything for the game
  // First, erase the screen and then display the 
  background and actors
  } 
 public void newGame() {
  // Start a thread which contains a game loop
  }
 private void quitCanvas() {
  // stop the realtime thread
  }
 public void quit() {
  // return to the welcome screen
  }
 protected void keyReleased( int param ) {
  // process keys when released
  }
 protected void keyPressed( int param ) {
  // process keys when pressed
  }
 public void commandAction(Command p1, Displayable 
  p2) {
  if (p1.getCommandType() 
  == Command.EXIT)
  quitCanvas();
  }
  }
  
 Listing 4

import javax.microedition.lcdui.*;
import java.util.*;
 public class GameLoop
  implements Runnable {
 private GameCanvas gameCanvas;p> public GameLoop( 
  GameCanvas gameCanvas ){
  }
 private void init(){
  }
 private void processKeys(){
  }
 private void moveObjects(){
  }
 private void processCollision(){
  }
 // Thread entry when started
  public void run() {
 while(gameLoopState == STATE_RUN) 
  {
 processKeys();
 moveObjects();
 processCollision();
 // force the game 
  canvas to paint the frame
  gameCanvas.repaint();
  gaemCanvas.serviceRepaints 
  ();
  }
  }
 // the function called by the game canvas every frame which 
  draws graphics
  public void paint( Graphics g ) {
 }
 // quit the thread loop
  public void quit() {
 }
 }

  
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.