by [email="kim@macrospace.com"]Kim Daniel Arthur[/email]
The world of mobile gaming has never been as hot as it is today! Through the last couple of years the mobile gaming experience has grown from asynchronous black and white games to real-time colour multiplayer java games. The introduction of colour mass market "gaming" enabled mobile phones has taken mobile gaming a giant step up the gaming food chain.
Hobbyist heaven
The current state of mobile game development resembles much that of the early 80s, where a single developer could lock himself into his bedroom for a month and come out with a fresh best selling classic. Add the simple fact that you can get all the tools you need to make your own mobile game completely free (without having a bad conscience), and you will understand why mobile game development is attracting so much interest from hobby developers and smaller game houses alike.
"So how do I get started?" I hear you ask. Well read on!
Overview
This 2-part article will introduce you to the world of mobile game development through J2ME and MIDP. The first part will give a general introduction to the platform and environment, familiarise you with the MIDP API and help you code, compile and run your first MIDP game.
The second part will take a more detailed look at important elements in MIDP development such as:
- Device specific APIs
- Tips and tricks on how to reduce your application size
- Targeting multiple devices
- Implementing sound and music
The different platforms
Firstly lets take a look at the main target platforms. The list of platforms is ever growing but can be split into 2 main categories; Java based and C++ based. Within these 2 categories there are several variations with their own characteristics and device base.
The most widely available/supported platforms are:
Java based C based (non java) MIDP (J2ME) Mophun ExEn Brew WGE .NET compact framework DoJa It is important to consider which platforms have the widest range of device support, and more importantly which devices are most popular among users. In some cases you might want to target a specific region or phone network operator, in this case the choice of target platform might not lie in your hands. As an example, Verizon (one of the largest network operators in the US) are pushing Brew enabled handsets. So far J2ME/MIDP seems to be the most widely available platform, chosen by Nokia, Motorola, Siemens and Samsung to mention a few.
This article will focus on the J2ME/MIDP platform and the supporting devices. If you wish to read more about the other platforms check out the resource section where you will find links to related information.
So what is all this J2ME and MIDP business anyways?
(4 letter acronym warning!)
J2ME
J2ME (Java 2 Micro Edition) is an optimised subset of J2SE (Java 2 Standard Edition) or "normal java". J2ME itself defines a further subset of configurations and profiles that are used to tailor the environment for low-end devices such as PDAs and mobile phones.
CLDC
CLDC (Connected Limited Device Configuration) is one of the J2ME configurations which is designed for devices with slow(er) processors and limited memory. Typically devices with a 16 or 32bit CPU and from 128k to 512kb+ memory.
MIDP
The MIDP (Mobile Information Device Profile) profile (API) defines elements such as:
- High level user interface elements
- Application lifecycle management
- Local data storage
- Connectivity And most importantly for us:
- Low level Graphics APIs
- Input handling
- Media (Sound) The MIDP profile together with the CLDC configuration make up the KVM (Kilo Virtual Machine) which is the runtime that we will be developing for.
Devices
To wet your appetite a little here is a list of some of the devices that you will be able to develop for when you become the master of MIDP!
Nokia Motorola Siemens Samsung 3510i T720 S55 S100 3300 A500 5100 6650 6100 7210 7650 3650 N-Gage For a more detailed list, check out this link!
Environment / Tools
Before you get your coding fingers all warmed up lets take a look at the basic MIDP development environment and process.
J2SE SDK
As you will be writing and compiling java code you will need the standard J2SE SDK. This includes the basic tools needed to compile your code. So if you haven't already, download and install the J2SE SDK from here.
The Wireless Toolkit
As you might have suspected you will be developing for a device that is not a "PC" so you will need something that emulates the target device or platform. Enter the J2ME Wireless Toolkit. The J2ME Wireless toolkit or WTK will be your best friend (and sometimes enemy) in the coming time. The WTK includes these main features:
- The MIDP API classes and documentation
- Default set of device emulators
- Example code and applications
- Java class pre-verifier (more about this one later) The WTK can be downloaded and installed from here.
Editor of choice
To write your code you can of course use the editor you are most friendly with. My personal favourite is UltraEdit. There are several Java IDEs that integrate well with the WTK:
- Eclipse
- Sun ONE studio
- JBuilder Once everything is installed lets get going on the fun stuff, writing code! (at last!)
MIDP the API
What's here, what's not?
As you start developing for MIDP you will soon see that the API is relatively small and compact. The API consists of 7 nicely Class packages:
- java.io - Provides data stream classes that among other things are useful for reading from resources (level files, images, sounds..)
- java.lang - Includes the basic Java Classes derived from the J2SE API. All important classes like Thread, primitive wrapper classes ( Byte, Short, Integer..) and a cut down Math class.
- java.util - A subset of the J2SE java.util package that holds a set of helpful utility classes like Random, Vector, Hashtable and the TimerTask class.
- javax.microedition.io - This package contains all the networking related classes and interfaces. Be aware that HTTP is the only mandatory network protocol in MIDP implementations.
- javax.microedition.lcdui - The mightiest package of them all! Includes classes both for low and high level UI operations. The high level components include Forms, Lists, TextFields and Commands all-important to handle user input and navigation. Most commonly used in games for menus and instruction screens. Among the low level objects are Canvas, Graphics and Image which provide you with typical game actions like drawing to the screen and catching user input.
- javax.microedition.midlet - the midlet package defines the entry and exit point for MIDP applications (MIDlets). It contains a single Class (yes, the MIDlet class) which is used by the AMS (Application management software) to control the lifecycle or state of your MIDlet. All MIDP applications you make must have a class that extends the MIDlet class to allow the AMS to (most importantly) start and stop your application.
- javax.microedition.rms - the rms package provides you with mechanisms to store data persistently. Nice to use for storing high scores, save games and the likes! The basic storage elements are referred to as records which you can read and write via the RecordStore class. (you might be used to store data in a local file, but in MIDP you would write it to a RecordStore, as you have no access to the file system as such) If you have previous experience from Java programming, maybe even made an Applet or two you will see quite a few similarities with the J2SE counterpart. And the basics of MIDlet development will be quicker to pick up. If you are new to Java all these packages and classes might sound quite daunting, but once you have your MIDlet running you will soon enough grasp what is needed to complete your game project!
The MIDlet
MIDP applications are called MIDlets (similar to the well known Applet), the files you can consider the MIDP executable are the Jad and Jar files.
The Jar file is archive (zip file) including most importantly your games Class files and resources. It also includes a Java Manifest file which along with the Jad (Java Application Descriptor) file contains vital information about your MIDlet.
We will find out exactly what is included in these files alittle later!
The building blocks
As mentioned the basic building block and entry point of your application (ok lets call it your game), game, is the MIDlet class. Also mentioned earlier was the AMS which is the piece of software on the device that manages your games lifecycle. When the user opens his list of games and decide to start your game the AMS will create a new instance of your main class, the one that extends MIDlet. The AMS will use the default (no argument) constructor of your MIDlet class to do this. If no error / Exception occurs when doing so, it will call the startApp() method on the new MIDlet instance. Your MIDlet is now in "active" state, this is where you gain control and can start performing your magic!
Another important building block for your game is the Canvas, which defines the all imporant methods to draw to the screen and capture user input. The Canvas class itself extends a class called Displayable, the Displayable class is an base for all objects that can be "placed onto" the devices display, such as Lists and Forms.
The Canvas class defines several important methods that we should take at now so you will be mentally prepared for what is to come later! The methods are commonly referred to as event delivery methods, they deliver events that you can handle as needed in your game:
- keyPressed( int keyCode ) - Indicates that a key has been pressed. Which key that has been pressed can be identified through the single int parameter of the method.
- paint( Graphics g ) - this is called by the Virtual Machine when a scheduled repaint is performed. The Graphics parameter is the object used to render to the Canvas. NOTE: you should never call paint manually, if you want the Canvas to be repainted you can call repaint() on the Canvas!
- keyReleased( int keyCode ) - works the same way as keyPressed() but is triggered by the release of a key.
Important limitations and pitfalls
The last thing to do before we make your first MIDlet is to identify some all important limitations and pitfalls. (don't let these scare you!)
- Do not believe the myth that there is no transparency support in MIDP. Most devices and emulators support transparent images. (Some require the pngs to be saved as 24bit as opposed to indexed mode). Some devices (Nokia) even support alpha transparency!
- MIDP has no floating point support. (no double or float) But this should not limit your possibilities as fixed point math will come to your rescue!
- No trigonometry functions. So once again its time to dig out those lookup tables!
- No direct access to image data (pixels) through generic MIDP. So for example common tasks like get()'ing and set()'ing of pixels are not possible.(not quite true for set()'ing as you can draw a 1 pixel line or rectangle to do this) But there are some devices (Nokia) that provide you with device specific methods to access the pixels and image data.
- No generic support for rotation / scaling of images. Although some devices provide (Nokia) you device specific methods for this. Commonly rotation in 90 degrees increments and flipping both horizontally and vertically are implemented in device specific libraries.
- Graphic modes are not palette based. 4096 is the most common colour count.
- Most phones do not support multiple simultaneous key presses.
- Watch your application size. Most devices have a defined maximum application size ranging from 30kb on the low end b&w phones to the more generous 180kb limits on the high end colour phones. For colour games a good target when it comes to application size is 64kb which is the lowest limit around for colour MIDP devices. (Remember to always check the application size limit for each phone you target!)
- Try to keep the amount of classes in your game to a minimum, as each class will add size overhead and heap memory overhead to your game. Sometimes you will even have to break common design rules to get around the size and speed limitations. (For example using accessor methods like getX(), setX() are considered an unnecessary overhead)
- If you are planning to support a wide range of devices, don't put game related logic in the class that extends Canvas. As on some devices (Nokia for example ;) ) where you night want to extend a device specific Canvas class called FullCanvas. So the less game logic you have in your Canvas class the less unique code you need for different device versions of your game!
- Remember to obfuscate your Class files! Not only does this reduce the size of your files, it makes it harder for others to decompile your game. (http://proguard.sourceforge.net/ , http://www.retrologic.com/retroguard-main.html) Ok, enough already! lets go!
First MIDlet
To write our first MIDlet we will use the J2ME Wireless Toolkit and the tools it provides. The most important tool is the KToolbar, from within the KToolbar you can create and manage your MIDlet projects. It has features to compile, package, run and even obfuscate your MIDlet.
Using the KToolbar to manage your MIDlet build process will enable you to quickly get into the development of your first MIDlet. You wont have to worry about doing all the compiling and packaging on the command line, but rather you can save this for later when you are comfortable with the environment. As your projects grow in size your needs for a tailored build process will increase and you will most probably need more control! (More on this in part 2)
Setting up the project
When you start the KToolbar, you will see the following window. Take a close look, this is your new friend!
To create your project, click (you guessed it) the New project button.
In the first field, enter the name of your Project. The second field "MIDlet Class Name" is where you define the name of your MIDlets main class. This class will be the one that extends MIDlet and is instantiated by the AMS. The actual name you give it is not important, but it must be a valid Java Class name. For now lets call it Startup. The next window holds all the properties that must be present in your MIDlets Jad and Manifest file.
- MIDlet-Jar-Size - This Jad attribute must always reflect the exact size in bytes of your MIDlets Jar file. When using the KToolbar to package your MIDlet this is handled automatically. (If the jad file holds a wrong value for your Jar files size, it will fail to install to the target device!)
- MIDlet-Jar-URL - The url to the Jar file of your MIDlet. (Typically just the filename of the jar file, this is more important in conjunction with delivery of the MIDlet via over the air downloads)
- MIDlet-Name - The name of your MIDlet, which appears in the list of MIDlets in the devices AMS.
- MIDlet-Vendor - The name of the MIDlets vendor, you or your company.
- MIDlet-Version - The current version of the MIDlet.
- MicroEdition-Configuration - Mandatory field to identify which configuration the MIDlet uses
- MicroEdition-Profile - Mandatory field to identify which profile the MIDlet uses When the new project wizard is complete you will be prompted with the following (or similar):
Place Java source files in "c:\j2mewtk\apps\MyGame\src"
Place Application resource files in "c:\j2mewtk\apps\MyGame\res"
What happened now is that the KToolbar has created a directory structure for your project. KToolbar projects are placed in their own subdirectory in the "apps" folder which is located in the folder where you chose to install the WTK.
As the KToolbar states, we should place all our source files in the newly created "src" folder.
Basic MIDlet
Ok, we have the project set up and are ready to go. In the "src" folder, create a file called Startup.java (case sensitive). This will be the source file for our MIDlets main class. The Startup Class will extend javax.microedition.midlet.MIDlet which has 3 abstract methods that need implementing. These are the all important methods that control the lifecycle of our MIDlets.
Below is the source for our first and most basic MIDlet
import javax.microedition.midlet.*; public class Startup extends MIDlet { /* * Default constructor used by AMS to create an instance * of our main MIDlet class. */ public Startup() { //Print message to console when Startup is constructed System.out.println("Constructor: Startup()"); } /* * startApp() is called by the AMS after it has successfully created * an instance of our MIDlet class. startApp() causes our MIDlet to * go into a "Active" state. */ protected void startApp() throws MIDletStateChangeException { //Print message to console when startApp() is called System.out.println("startApp()"); } /* * destroyApp() is called by the AMS when the MIDlet is to be destroyed */ protected void destroyApp( boolean unconditional ) throws MIDletStateChangeException { } /* * pauseApp() is called by the AMS when the MIDlet should enter a paused * state. This is not a typical "game" pause, but rater an environment pause. * The most common example is an incoming phone call on the device, * which will cause the pauseApp() method to be called. This allows * us to perform the needed actions within our MIDlet */ protected void pauseApp() { } }example01.zip
Compiling, preverifying and running
To compile all the src files in your project (currently just Startup.java) press "Build".
What the KToolbar does now is to compile your source code against the MIDP and CLDC APIs (and any libraries in the /lib folder of the currently selected emulator) into a folder called tmpclasses. The command it executes would be similar to (relative to project folder):
javac -d tmpclasses -bootclasspath %wtk%\lib\midpapi.zip -classpath tmpclasses;classes src\*.java
The next step the KToolbar takes is to preverify your java Classes and place them in the "classes" folder in your project. Class files must be preverified before they can be run on a MIDP device.
The command line approach for preverifying: (preverify.exe is a tool provided with the WTK, located in the WTK's \bin folder)
preverify -classpath %wtk%\lib\midpapi.zip tmpclasses -d classes
Now that your MIDlet is compiled and preverified you can run it by pressing "Run". As the only thing we are doing in our MIDlet is to print 2 strings to the console nothing will actually happen in the display of the emulator, but you should see the following in the WTK console:
Building "MyGame"
Build complete
Constructor: Startup()
startApp()
This shows us that the Startup class was constructed through its default constructor and then startApp() was called. Not very exciting, but important for our MIDlet to start
Using Forms and Commands
Forms and Commands are high level UI components that come in handy for building menus and showing instructions in games. The Form class is a subclass of Displayable, this means that we can directly display it on the devices Display.
The devices current Displayable can be set by help from the Display class. To get a reference to a Display object for our MIDlet we call the static method getDisplay() on the Display class. The method takes one parameter which is a reference to an instance of our MIDlet class:
Display display = Display.getDisplay( midletInstance );we can now set the display to any Object that extends Displayable by calling:
display.setCurrent(nextDisplayable );Now that we know this, lets try and make a Form and display it!
First we need to create a Form, the most basic form we can create is an empty form with a title;
Form basicForm = new Form("Form Title");Let's add a String to the Form as well, this is done by appending it :
basicForm.append("My basic Form");So we now have a Display and a Form ( which extends Displayable ), so we have all we need to proceed in showing the Form;
display.setCurrent( basicForm );Put all this in our startApp() method and this will be the first thing that happens when our MIDlet launches.
Form and Display are both Classes in the javax.microedition.lcdui package so we must remember to import this in our source file.
example02.zip
Build and run!
Now that we have the Form, lets add a Command so that we can get some high-level input from the user. Lets make a Command that allows the user to generate a random number and append it to the Form.
A Command is a component that triggers an action in our MIDlet. To capture the event triggered when the user activates the Command we need to use the CommandListener interface. The CommandListener interface defines one method that we must implement:
commandAction( Command c, Displayable d )When the user triggers the Command, the implementation will call the commandAction method on the associated CommandListener. The CommandListener is set by the setCommandListener() method on the Displayable where the Command has been added.
appendCommand = new Command( "Random", Command.SCREEN, 0 );The Command constructor takes 3 parameters, the String associated with the Command, the type of Command (indicates to the implementation what type of action this Command will perform, sometimes affects the positioning of the Command on the screen) and the Commands priority or display order if you wish. And add it to our Form (addCommand() is inherited from Displayable, so all Displayables can have Commands):
basicForm.addCommand( appendCommand );We now have a Command added to our Form, the next step would be to set the CommandListener of our Form. Lets make our Startup class implement CommandListener and override the commandAction() method to perform our actions.
basicForm.setCommandListener( this );As we wanted to generate a random number we will also need to add a Random number generator to our app. For this we must import java.util.Random and create a new generator :
generator = new Random( System.currentTimeMillis() );This creates a new Random generator and seeds it with the current time in milliseconds.
/* * Callback method for the CommandListener, notifies us of Command action events */ public void commandAction( Command c, Displayable d ) { //check if the commandAction event is triggerd by our appendCommand if( c == appendCommand ) { //append a String to the form with a random number between 0 and 50. basicForm.append("Random: " + ( Math.abs( generator.nextInt() ) %50 ) + "\n"); } }Build and run!
example03.zip
This was a brief introduction to the High-level components of the MIDP API. They provide a generic portable way of displaying information and getting high-level user input. Next is where the heart of your game will take place, so get ready to paint() that Canvas!
Using the Canvas to draw and handle input
Maybe the most important thing in a game is being able to draw stuff to the screen, be it characters, items or backgrounds. To do this in MIDP we will be using the Canvas, Graphics and Image classes, these are the main classes you will be using for your low-level graphics handling.
The Canvas is an abstract class and we must therefore subclass it to be able to use it, lets make a new Class called GameScreen that extends Canvas. As we have seen before the Canvas class defines the abstract paint( Graphics g ) method, in our GameScreen class we will override this method which will allow us to draw to the Graphics object passed to the paint() method by the Virtual Machine.
This leaves us with the following source for our GameScreen class:
import javax.microedition.lcdui.*; public class GameScreen extends Canvas { //Default constructor for our GameScreen class. public GameScreen() { } /* * called when the Canvas is to be painted */ protected void paint( Graphics g ) { } }Now we have the basics we need to draw to the screen, lets get things set up to receive key events from the user. The Canvas class defines 3 methods that handle key events, keyPressed(), keyReleased() and keyRepeated(). The canvas class has empty implementations of these methods, so it is up to us to override them and handle the events as we see fit.
/* * called when a key is pressed and this Canvas is the * current Displayable */ protected void keyPressed( int keyCode ) { } /* * called when a key is released and this Canvas is the * current Displayable */ protected void keyReleased( int keyCode ) { }As you see we have only implemented keyPressed() and keyReleased() not keyRepeated(). You should try not to rely on keyRepeated() events as the frequency of the calls to keyRepeated() varies a lot from device to device. And using the behaviour keyRepeated() provides us is not the optimal way to check whether the user has held the key down or not.
Ok so we are now ready to receive input and draw to the screen, before we go any further lets make sure we know how to get the Canvas we have made displayed on the screen. Remember the Startup class we made earlier? Lets change this class so that its sole purpose is to serve as an entry point to our game and create and display a new instance of our GameScreen class.
protected void startApp() throws MIDletStateChangeException { Display display = Display.getDisplay( this ); //GameScreen extends Canvas which extends Displayable so it can // be displayed directly display.setCurrent( new GameScreen() ); }So now we are creating a new GameScreen and displaying it. Now we can try out some of the primitive drawing methods available from the Graphics class.
//set the current colour of the Graphics context to a darkish blue // 0xRRGGBB g.setColor( 0x000088 ); //draw a filled rectangle at x,y coordinates 0, 0 with a width //and height equal to that of the Canvas itself g.fillRect( 0, 0, this.getWidth(), this.getHeight() );By setting the colour via setColor( int rgbColor ) we will affect all subsequent rendering operations to this Graphics context. Hence our call to fillRect( x, y, width, height ) will draw a filled rectangle in our desired colour. This also introduces 2 quite important methods of the Canvas class, getWidth() and getHeight() you will use these methods to obtain the total available for you to draw to on the Canvas. These are important values when targeting multiple devices with varying screen sizes. Always obtain the values via getWidth() and getHeight() don't be tempted to hardcode the values as you will create a lot of extra work for yourself when you want to port your game. Try to make all your draws to the screen (where possible) relative to the width and height of the Canvas.
Build and run!
example04.zip
Input handling
Just to get the hang of handling key events lets make the colour of our filled rectangle change when the user presses a key. For fun we can make the rectangle red when the user presses the LEFT key, green when for RIGHT, black for UP, white for DOWN and blue for FIRE.
As you might have noticed, a key pressed event is represented by a int value reflecting the key code of the key the user pressed. This key code can be treated in 2 separate ways, either via its actual value (KEY_NUM0 to KEY_NUM9, KEY_STAR or KEY_POUND which make up a standard telephone keypad) or its game action value (UP, DOWN, LEFT, RIGHT, FIRE, GAME_A, GAME_B,GAME_C,GAME_D). Why have 2 approaches you ask? As there are so many different keypad layouts and configuration, using the game action value of a key code will allow us to identify keys by their game action in a portable fashion. To retrieve the game action mapped to a key code we use the getGameAction( int keyCode ) method of the Canvas class.
/* * called when the Canvas is to be painted */ protected void paint( Graphics g ) { //set the current color of the Graphics context to the specified RRGGBB colour g.setColor( colour ); //draw a filled rectangle at x,y coordinates 0, 0 with a width // and height equal to that of the Canvas itself g.fillRect( 0, 0, this.getWidth(), this.getHeight() ); } /* * called when a key is pressed and this Canvas is the * current Displayable */ protected void keyPressed( int keyCode ) { //get the game action from the passed keyCode int gameAction = getGameAction( keyCode ); switch( gameAction ) { case LEFT: //set current colour to red colour = 0xFF0000; break; case RIGHT: //set current colour to green colour = 0x00FF00; break; case UP: //set current colour to black colour = 0x000000; break; case DOWN: //set current colour to white colour = 0xFFFFFF; break; case FIRE: //set current colour to blue colour = 0x0000FF; break; } //schedule a repaint of the Canvas after each key press as //currently do not have any main game loop to do this for us. repaint(); }Build and run!
example05.zip
If you look at the last line of the keyPressed() method you will see a call to repaint(). This schedules a repaint of the Canvas. Normally we would not do this from within the keyPressed() method, but at the end of our game loop. So now is a good time to get that main game loop going!
Game loop
The Thread class will be used to spawn our game thread so we can use this for our main loop. Threads can be created in 2 different ways. Either by subclassing Thread and overriding the run() method of the Thread class. But as mutliple inheritance is not possible in Java (our GameScreen class is already extending Canvas) we will use the second approach which is to implement the Runnable interface and implement the run() method of that interface. This means we can spawn a new Thread by passing the instance of our GameScreen class (which implements Runnable) to the Threads constructor.
//Default constructor for our GameScreen class. public GameScreen() { //create a new Thread on this Runnable and start it immediately new Thread( this ).start(); } /* * run() method defined in the Runnable interface, called by the * Virtual machine when a Thread is started. */ public void run() { }Now when we construct our GameScreen it will create and start a new Thread which triggers a call to our run() method.
We want our main loop to be called at a fixed rate, for this example lets set the rate to 15 times per second. (Although it is impossible to give an exact performance figure that will apply to all games, 15fps is a reasonably indicative average to start off with)
Within the run() method of our class we implement the timing logic for our main loop:
/* * run() method defined in the Runnable interface, called by the * Virtual machine when a Thread is started. */ public void run() { //set wanted loop delay to 15th of a second int loopDelay = 1000 / 15; while( true ) { //get the time at the start of the loop long loopStartTime = System.currentTimeMillis(); //call our tick() fucntion which will be our games heartbeat tick(); //get time at end of loop long loopEndTime = System.currentTimeMillis(); //caluclate the difference in time from start til end of loop int loopTime = (int)(loopEndTime - loopStartTime); //if the difference is less than what we want if( loopTime < loopdelay ) { try { //then sleep for the time needed to fullfill our wanted rate thread.sleep( loopdelay - looptime ); } catch( exception e ) { } } } } /* * our games main loop, called at a fixed rate by our game thread */ public void tick() { }To test our main loop lets make it change the colour of the background to a random colour every frame. We can use this opportunity to move the repaint() call to within our game loop.
/* * Our games main loop, called at a fixed rate by our game Thread */ public void tick() { //get a random number within the RRGGBB colour range colour = generator.nextInt() & 0xFFFFFF; //schedule a repaint of the Canvas repaint(); //forces any pending repaints to be serviced, and blocks until //paint() has returned serviceRepaints(); }Build and run! (don't stare to long at the screen, you will be mesmerized!)
example06.zip
Using Images
Images in MIDP are very easy to create. The easiest way is to call the static method of the Image class, createImage( String name ). The String passed to the method is the location of the image within the MIDlets jar file. So the first thing we need to to is make a image to use in our game. Generic MIDP only supports PNG images. When using the KToolbar to build our projects, all we need to to is place the image file in the "res" folder of our project. Create a image and called "sprite.png" and put it in the res folder. Normally you should keep your images files as small as possible, there are several tricks, the obvious ones are to save them as indexed mode PNGs. To gain an extra few bytes you can optimise them with for example XAT image optimiser or pngcrush, these tools on average save you 30% of your original image size! Note: for transparency to work in the default WTK emulators your files must be saved as 24bit pngs, this is not true for most of the actual devices or device specific emulators.
try { myImage = Image.createImage("/sprite.png"); } catch( Exception e ) { e.printStackTrace(); }There you go, image created. Images take time to create and require a lot of runtime memory, so keep your image creation within controlled areas of your game. Actual memory usage varies between implementations, bear in mind graphic modes are not palette based, a typical implementation will use 2 bytes of memory per pixel.
To draw the created Image to the screen, use the drawImage() method of the Graphics class.
g.drawImage( myImage, x, y, Graphics.TOP | Graphics.LEFT );The Graphics.TOP | Graphics.LEFT parameter is called an anchor. This defines how the Image should be drawn relative to the x and y coordinates. The Graphics.TOP constant causes the TOP of the image to be at the y coordinate, and the Graphics.LEFT constant causes the LEFT of the image to be at the x coordinate. So if you wanted to draw an image at the center of the screen, the quickest way to do it is to set the anchor to the vertical and horizontal center of the Image:
g.drawImage( myImage, this.getWidth()/2, this.getHeight()/2, Graphics.VCENTER | Graphics.HCENTER );Build and run! (i suggest we get rid of the random colour thingy before it causes any permanent damage to our brain..)
example07.zip
Double buffering
To avoid flickering when drawing to the screen we will need to use well known double buffering techniques, where everything is rendered to an off screen buffer then later drawn to the visible screen. Some implementations will actually do the double buffering for us! Whether a device does so or not can be queried at runtime via the isDoubleBuffered() method on the Canvas. The advantage of not having to do the double buffering yourself is that it saves us the runtime memory needed to store the off screen buffer. We can easily write our code to automatically check and cater for devices that need us to implement double buffering ourselves.
At the same time as we load and create all our needed Images we can create our off screen buffer if needed.
/* * Creates all the needed images, called upon creation * of our GameScreen class */ public void createImages() { try { //if device doesnt do automatic double buffering if( !isDoubleBuffered() ) { //create offscreen Image bufferImage = Image.createImage( getWidth(), getHeight() ); //get a Graphics context to we can render onto the bufferImage buffer = bufferImage.getGraphics(); } myImage = Image.createImage("/sprite.png"); } catch( Exception e ) { e.printStackTrace(); } }We create a new empty Image by calling Image.createImage( width, height ). The Image should be the exact same size as the Canvas's viewable area. In MIDP there are Mutable and Immutable Images. The difference being that Immutable Images, the ones created from image files/data, cannot be modified once created. A mutable Image, normally created through Image.createImage( width, height) can be modified by obtaining a Graphics context that will render to the Image itself. This is done by calling getGraphics() on the Image. This is what we have done for our back buffer!
With a small modification to our paint() method we can accommodate for those devices that do not do the buffering for us.
/* * called when the Canvas is to be painted */ protected void paint( Graphics g ) { //cache a reference to the original Graphics context Graphics original = g; //if device doesn't do automatic double buffering if( !isDoubleBuffered() ) { //change the g object reference to the back buffer Graphics context g = buffer; } //set the current color of the Graphics context to the specified RRGGBB colour g.setColor( colour ); //draw a filled rectangle at x,y coordinates 0, 0 with a width // and height equal to that of the Canvas itself g.fillRect( 0, 0, this.getWidth(), this.getHeight() ); //draw an image to the centre of the screen g.drawImage( myImage, this.getWidth()/2, this.getHeight()/2, Graphics.VCENTER | Graphics.HCENTER ); if( !isDoubleBuffered() ) { //draw the off screen Image to the original graphics context original.drawImage( bufferImage, 0, 0, Graphics.TOP | Graphics.LEFT ); } }This might be a little confusing at first, at the top of the paint() method we keep a reference to the original Graphics context that was passed as a parameter to the method. We then check whether we need to perform the double buffering, if so we change the Graphics context that the g variable references to the Graphics context obtained from the buffer Image. At the end of the paint method we again check if we needed to perform the double buffering, and draw the buffer Image to the original Graphics context we kept earlier.
Build and run!
Example08.zip
To wrap up, lets change our input handling so we can move our image around the screen by pressing the keys.
/* * called when a key is pressed and this Canvas is the * current Displayable */ protected void keyPressed( int keyCode ) { //get the game action from the passed keyCode int gameAction = getGameAction( keyCode ); switch( gameAction ) { case LEFT: //move image left imageDirection = LEFT; break; case RIGHT: //move image right imageDirection = RIGHT; break; case UP: //move image up imageDirection = UP; break; case DOWN: //move image down imageDirection = DOWN; break; case FIRE: //set current to a random colour colour = generator.nextInt()&0xFFFFFF; break; } } /* * Our games main loop, called at a fixed rate by our game Thread */ public void tick() { int myImageSpeed = 4; switch( imageDirection ) { case LEFT: myImageX-=myImageSpeed; break; case RIGHT: myImageX+=myImageSpeed; break; case UP: myImageY-=myImageSpeed; break; case DOWN: myImageY+=myImageSpeed; break; } //schedule a repaint of the Canvas repaint(); //forces any pending repaints to be serviced, and blocks until //paint() has returned serviceRepaints(); }Build and run!
Example09.zip
all.zip
That sums up the first part of the article, you should now be equipped with the tools and knowledge you need to make your first MIDP game. You know how to display and capture high level information, you can create and draw to a Canvas, you can even set up a game loop and handle key events, so now it is all up to you and your imagination!
The next part of the article will among other things focus on specific devices and their APIs, optimising the size of your MIDlet and look into ways you can more easily target multiple devices.
Resources
Below is a random list of sites related to J2ME and mobile gaming, enjoy!
http://java.sun.com/j2me/ - Sun's J2ME website
http://midlet.org - a huge repository of MIDlets and the chance to make your game publicly available
http://www.billday.com/j2me/ - nice resource with mixed info on J2mE
http://www.midlet-review.com - Mobile game review site, see what games are around and how they rate
http://games.macrospace.com - excellent collection of commercial games
http://www.microjava.com - a good resource for J2ME related news, tutorials and articles
http://www.mophun.com - The biggest mophun resource around
http://wireless.ign.com - IGN's wireless gaming section
http://www.qualcomm.com/brew - All the info you will need to get started with Brew
http://www.kobjects.org/devicedb - list of devices and device specs
http://wireless.java.sun.com/device/ - Another detailed list of java devices
http://home.rochester.rr.com/ohommes/MathFP/ - easy to use publicly available fixed point library for J2ME
http://www.forum.nokia.com/main.html - Nokia's developer site, lots of news, tools and developer forums
http://www.motocoder.com - Motorola's developer site
http://archives.java.sun.com/archives/kvm-interest.html - Sun's KVM mailing list