Welcome!

Java Authors: Liz McMillan, Walter H. Pinson, III, Maureen O'Gara, Yakov Werde, Tony Bishop

Related Topics: Java

Java: Article

GoodBye Snake

GoodBye Snake

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.

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

    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 ).

    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.

    • 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.

      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 key- Released(), 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.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.