- Exploring the J2ME APIs
- Understanding MIDlets
- Building the Skeleton Example Program
- Summary
- Extreme Game Makeover
Building the Skeleton Example Program
Although I'd love to lead you through the creation of a real-time 3D multiplayer game as your first MIDlet game, I don't think it would serve as a best first impression of how MIDlets are structured and coded because of the overwhelming complexity. Instead, you learn here how to build a very simple MIDlet called Skeleton that displays information about the mobile phone as lines of text onscreen. Because this information includes important device parameters such as the game screen size and color depth, you'll find it useful in double checking the parameters of real phones.
In building the Skeleton MIDlet, you go through the same sequence of steps outlined in the previous section. This process will be very similar for every MIDlet game that you develop. Following are the steps involved in creating the Skeleton MIDlet:
Code the MIDlet.
Compile the MIDlet.
Preverify the MIDlet.
Package the MIDlet.
Test the MIDlet.
The next few sections tackle each of these steps in succession, culminating in the completion of your first J2ME MIDlet.
Writing the Program Code
In this section you assemble the code for the Skeleton MIDlet a section at a time, with the complete source code listing appearing at the end. The first code for your MIDlet is the code that imports a couple of important J2ME packages. Although you can certainly avoid importing any packages and reference every J2ME class and interface with its fully qualified name (javax.microedition.midlet.MIDlet, for example), this can be quite cumbersome and ultimately makes the code hard to read. So the first two lines of your MIDlet import the two primary packages associated with MIDlet development:
import javax.microedition.midlet.*; import javax.microedition.lcdui.*;
NOTE
Most Java programmers frown on importing entire packages via the * wildcard because it doesn't reveal much about what specific classes you're importing. However, it is a quick and easy way to import all the classes in a package, and for the purposes of this book, I went the easy route to keep the code as simple as possible. Feel free to import classes one at a time in your own code to make your code more explicit.
The javax.microedition.midlet package includes support for the MIDlet class itself, whereas the javax.microedition.lcdui package includes support for the GUI classes and interfaces that are used to construct MIDlet GUIs, such as the Display class. With those two packages imported, you're ready to declare the SkeletonMIDlet class, which is derived from MIDlet:
public class SkeletonMIDlet extends MIDlet implements CommandListener {
The fact that SkeletonMIDlet extends MIDlet is no surprise, but the implementation of the CommandListener interface might seem a little strange. This interface is necessary to create an Exit command that allows the user to exit the MIDlet. More specifically, the CommandListener interface is implemented so that the MIDlet can respond to command events.
The only member variable in the SkeletonMIDlet class is an SCanvas object that represents the main screen:
private SCanvas canvas;
The SCanvas class type is a custom MIDlet-specific canvas class you see in a moment that is derived from Canvas. The canvas is initialized in the startApp() method, which follows:
public void startApp() { if (canvas == null) { canvas = new SCanvas(Display.getDisplay(this)); Command exitCommand = new Command("Exit", Command.EXIT, 0); canvas.addCommand(exitCommand); canvas.setCommandListener(this); } // Start up the canvas canvas.start(); }
The Exit command is created and added to the canvas so that the canvas can respond to it.
The startApp() method is called whenever the MIDlet enters the Active state, and the first step in the method is to create a canvas. The Display object for the MIDlet is obtained and passed into the canvas as part of its creation. The Exit command is then created when you pass three arguments to the Command constructor that specify the name of the command, its type, and its priority. The name of the command is user defined and appears above one of a phone's soft buttons or on a menu, depending on its priority and how many buttons are available. The command type must be one of several built-in Command constants such as EXIT, OK, or CANCEL.
The command is added to the canvas so that it becomes active. It is still necessary to designate a command listener to receive and process command events. You accomplish this by calling the setCommandListener() method and passing this, which makes the MIDlet class (SkeletonMIDlet) the command listener. That's perfect because earlier you made the class implement the CommandListener interface.
NOTE
The priority of a command is used to determine the placement of the command for user access. This is necessary because most devices have limited buttons available for MIDlet-specific usage. Therefore, only the most important commands are mapped to these soft buttons. Other commands are still available, but only from menus within the MIDlet that aren't as easily accessible as a soft button. The priority value of a command decreases with the level of importance; that is, a value of 1 is assigned to a command with the highest priority. In the Skeleton example, the Exit command is given a priority of 2, which means that it has a high priority. Of course, priority values are entirely relative, and because no other commands are in this example the value doesn't matter.
The Exit command for the Skeleton MIDlet is handled in the commandAction() method, which follows:
public void commandAction(Command c, Displayable s) { if (c.getCommandType() == Command.EXIT) { destroyApp(true); notifyDestroyed(); } }
The two arguments passed into the commandAction() method are the command and the screen on which the command was generated. Only the command is of interest in this particular example. The Command object is compared with the Command.EXIT member constant to see whether the Exit command is indeed the command being handled. If so, the destroyApp() method is called to destroy the MIDlet. The true argument to this method indicates that the destruction is unconditional, which means that the MIDlet is destroyed even if some error or exception occurs during the destruction process. The notifyDestroyed() method is called afterward to notify the application manager that the MIDlet has entered the Destroyed state.
The Skeleton MIDlet doesn't have any use for the pauseApp() and destroyApp() methods, but you must provide empty implementations for them nonetheless:
public void pauseApp() {} public void destroyApp(boolean unconditional) {}
Although you've seen the bits and pieces separately, the complete code for the SkeletonMIDlet.java source code file is shown in Listing 3.1.
Listing 3.1 The Source Code for the SkeletonMIDlet Class Is Located in SkeletonMIDlet.java
import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class SkeletonMIDlet extends MIDlet implements CommandListener { private SCanvas canvas; public void startApp() { if (canvas == null) { canvas = new SCanvas(Display.getDisplay(this)); Command exitCommand = new Command("Exit", Command.EXIT, 0); canvas.addCommand(exitCommand); canvas.setCommandListener(this); } // Start up the canvas canvas.start(); } public void pauseApp() {} public void destroyApp(boolean unconditional) {} public void commandAction(Command c, Displayable s) { if (c.getCommandType() == Command.EXIT) { destroyApp(true); notifyDestroyed(); } } }
You don't use these methods for anything in this example, but you must provide empty implementations to satisfy the MIDlet class.
To be thorough, you need to call the destroyApp() method, although notifyDestroyed() is what is really ending the MIDlet.
The remaining code for the Skeleton MIDlet example is associated with the SCanvas class, which is shown in Listing 3.2.
Listing 3.2 The SCanvas Class Serves as a Customized Canvas for the Skeleton MIDlet
import javax.microedition.lcdui.*; public class SCanvas extends Canvas { private Display display; public SCanvas(Display d) { super(); display = d; } void start() { display.setCurrent(this); repaint(); } public void paint(Graphics g) { // Clear the canvas g.setColor(0, 0, 0); // black g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(255, 255, 255); // white // Draw the available screen size int y = 0; String screenSize = "Screen size: " + Integer.toString(getWidth()) + " x " + Integer.toString(getHeight()); g.drawString(screenSize, 0, y, Graphics.TOP | Graphics.LEFT); // Draw the number of available colors y += Font.getDefaultFont().getHeight(); String numColors = "# of colors: " + Integer.toString(display.numColors()); g.drawString(numColors, 0, y, Graphics.TOP | Graphics.LEFT); // Draw the number of available alpha levels y += Font.getDefaultFont().getHeight(); String numAlphas = "# of alphas: " + Integer.toString(display.numAlphaLevels()); g.drawString(numAlphas, 0, y, Graphics.TOP | Graphics.LEFT); // Draw the amount of total and free memory Runtime runtime = Runtime.getRuntime(); y += Font.getDefaultFont().getHeight(); String totalMem = "Total memory: " + Long.toString(runtime.totalMemory() / 1024) + "KB"; g.drawString(totalMem, 0, y, Graphics.TOP | Graphics.LEFT); y += Font.getDefaultFont().getHeight(); String freeMem = "Free memory: " + Long.toString(runtime.freeMemory() / 1024) + "KB"; g.drawString(freeMem, 0, y, Graphics.TOP | Graphics.LEFT); } }
This line of code is extremely important because it sets this canvas as the current canvas for the MIDlet.
It's important to clear the background before you begin drawing content.
The SCanvas class is derived from the Canvas class, and accepts a Display object as the only parameter to its constructor. The constructor simply sets the display member variable so that the MIDlet display is accessible throughout the canvas code. The start() method calls the setCurrent() method on the Display object to set the canvas as the current screen. It's possible to have multiple screens in a MIDlet, in which case you can use the setCurrent() method to switch between them. The start() method calls repaint() to force a paint of the canvas so that it is initially painted properly.
NOTE
Although the SCanvas class in the Skeleton MIDlet is derived from Canvas, most of the examples throughout the book actually use canvas classes derived from GameCanvas, which provides game-specific features such as double-buffered graphics and efficient key handling. These features aren't necessary in the Skeleton example.
The painting of the canvas represents the majority of the code in the Skeleton MIDlet, and is handled by the paint() method. It isn't important for you to understand the details of this graphics code right nowthe next chapter tackles mobile game graphics in detail. I will give you a quick summary, however, just in case you want to try and follow along as a look-ahead to the next chapter.
The method begins by clearing the canvas with a solid color fill in black. The paint color is then changed to white to draw the text. The screen size is obtained first, and then drawn, centered horizontally near the top of the screen. The number of available colors and alpha levels are obtained next and displayed below the screen size. Finally, the amounts of total and free memory are determined, and displayed last.
NOTE
The number of alpha levels supported by a phone determine how much control you have over transparent areas in graphical images. For example, all phones support at least two alpha levels, which correspond to a pixel being either fully opaque or fully transparent. Some phones support as many as 256 alpha levels, which allow you to create graphics with partial transparency, where you can see through an object in varying amounts.
Now that the source code is complete, you're almost ready to build and test the Skeleton MIDlet. But first you need to create a couple of important support files that are required for packaging the MIDlet for distribution.
Preparing the MIDlet for Distribution
Packaging a MIDlet game for distribution involves compressing the various files used by the MIDlet into an archive known as a JAR file. In addition to compressing the preverified class files for a MIDlet into a JAR file, you must also include in the JAR file any resources associated with the MIDlet, as well as a manifest file that describes the contents of the JAR file.
In the case of the Skeleton MIDlet, the only resource is an icon that is displayed next to the MIDlet name on a device. I explain about the icon in just a moment. For now, let's focus on the manifest file. The manifest file is a special text file that contains a listing of MIDlet properties and their respective values. This information is very important because it identifies the name, icon, and classname of each MIDlet in a JAR file, as well as the specific CLDC and MIDP version targeted by the MIDlets in the JAR file. Remember that multiple MIDlets can be stored in a single JAR file, in which case the collection is known as a MIDlet suite.
NOTE
All the examples throughout the book target MIDP 2.0 and CLDC 1.0. Although some mobile phones support CLDC 2.0, CLDC 1.0 is sufficient for most game programming. However, MIDP 2.0 is extremely important because game features were added to the MIDP API as of version 2.0.
The manifest file for a MIDlet suite must be named Manifest.mf, and must be placed in the JAR file with the MIDlet classes and resources. Following is the code for the Manifest file associated with the Skeleton MIDlet:
MIDlet-1: Skeleton, /icons/Skeleton_icon.png, SkeletonMIDlet MIDlet-Name: Skeleton MIDlet-Description: Skeleton Example MIDlet MIDlet-Vendor: Stalefish Labs MIDlet-Version: 1.0 MicroEdition-Configuration: CLDC-1.0 MicroEdition-Profile: MIDP-2.0
The first entry in the manifest file is by far the most important because it specifies the name of the MIDlet, along with its icon file and its executable class name. You might notice that the property is named MIDlet-1; if you were to include additional MIDlets in this MIDlet suite, you would reference them as MIDlet-2, MIDlet-3, and so on. The MIDlet-Name, MIDlet-Description, MIDlet-Vendor, and MIDlet-Version properties all refer to the MIDlet suite as a whole. However, in this case the Skeleton MIDlet is the only MIDlet in the suite, so it's okay to refer to the entire suite as Skeleton. The last two properties identify the versions of the configuration and profile that the MIDlet suite targets, which in this case are CLDC 1.0 and MIDP 2.0.
Earlier I mentioned that you must include any resources in a JAR file along with the MIDlet classes and the manifest file. At the very least a MIDlet will have an icon as a resource. This icon is simply a 12x12 pixel image that is stored in the PNG image format. It can be a color or black-and-white image, depending on whether or not the mobile phone you're targeting has a color screen. I created a small picture of a skull to use as the Skeleton icon, and stored it in a file called Skeleton_icon.png.
One small caveat in regard to the icon for a MIDlet is that it must be stored in a folder named icons within the MIDlet JAR file. Similar to ZIP files, JAR files enable you to store files within folders inside of the archive. To place a file in such a folder inside a JAR file, you must reference it from a subfolder when you add it to the JAR file. It is somewhat standard convention to place MIDlet resources in a folder named res beneath the folder where the source code files are located. Knowing this, a simple solution for the icon is to place it in an icons folder located beneath the res folder.
Figure 3.2 Adhering to a simple folder structure for your MIDlet source files helps keep things organized.
Speaking of folder structure and MIDlets, there is a standard way to organize folders for MIDlet source files. Figure 3.2 shows the folder structure you should try and stick to when organizing your MIDlet source files.
The different folders in the figure are used to store the following:
srcJava source code files
binManifest file, JAD file, and JAR file
classesCompiled Java bytecode files
tmpclassesCompiled and verified Java bytecode files
resResource files other than icons (images, sounds, and so on)
res/iconsIcon files
NOTE
The example MIDlets included in the J2ME Wireless Toolkit, including the example games you saw in the previous chapter, adhere to this same folder structure.
In addition to the manifest file that resides in the JAR file for a MIDlet, an additional application descriptor file is required for distributing a MIDlet. An application descriptor file, or JAD file, contains information similar to that found in a manifest file. The JAD file is used by the Java emulator when you test a MIDlet suite. Following is the JAD file for the Skeleton MIDlet:
MIDlet-1: Skeleton, /icons/Skeleton_icon.png, SkeletonMIDlet MIDlet-Name: Skeleton MIDlet-Description: Skeleton Example MIDlet MIDlet-Vendor: Stalefish Labs MIDlet-Version: 1.0 MicroEdition-Configuration: CLDC-1.0 MicroEdition-Profile: MIDP-2.0 MIDlet-Jar-Size: 2706 MIDlet-Jar-URL: Skeleton.jar
This is your own version number for the MIDlet, which in this case means Skeleton 1.0.
If this size (in bytes) doesn't match the exact size of the JAR file, the MIDlet won't run.
With the exception of the last two entries, the JAD file contains information with which you are already quite familiar. The last two entries specify the size of the JAR file (in bytes) for the MIDlet and the name of the JAR file. It is important that you update the size of the JAR file any time you repackage the MIDlet because the size is likely to change each time you rebuild the MIDlet.
Building and Testing the Finished Product
The previous chapter introduced you to the KToolbar visual development tool that allows you to build and run MIDlets with very little effort. To build the Skeleton MIDlet with KToolbar, you must first copy the entire Skeleton MIDlet folder to the apps folder beneath the J2ME Wireless Toolkit installation folder. After the Skeleton folder has been copied, you can open the Skeleton project from within KToolbar by clicking the Open Project button on the toolbar.
After the project opens, just click the Build button on the toolbar to build the Skeleton MIDlet. After the project is built, click the Run button on the toolbar to run it in the J2ME emulator. Figure 3.3 shows the Skeleton MIDlet available to be run within the emulator.
Figure 3.3 The Skeleton MIDlet is made available for running within the application manager of the J2ME emulator.
NOTE
All the source code and relevant files for the example MIDlets throughout the book are available on the accompanying CD-ROM.
Figure 3.4 The Skeleton MIDlet displays information about the mobile phone such as the screen size and number of available colors.
Because Skeleton is the only MIDlet in the MIDlet suite, it is already highlighted and ready to be launched. To run it, click the Action button on the device keypad, which appears between the arrow keys, or the Launch soft button; you can also press Enter on your computer keyboard. Figure 3.4 shows the Skeleton MIDlet executing in the emulator.
To exit the Skeleton MIDlet, click the soft button beneath the word Exit. This invokes the Exit command and results in the MIDlet being destroyed. You can also exit a MIDlet by clicking the red End button, which is used on a real device to end a phone call.