An Introduction to Developing for Mobile Devices Using J2ME/MIDP (Part 1)
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 heavenThe 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! OverviewThis 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: The different platformsFirstly 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:
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!) J2MEJ2ME (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. CLDCCLDC (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. MIDPThe MIDP (Mobile Information Device Profile) profile (API) defines elements such as:
And most importantly for us:
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. DevicesTo 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!
For a more detailed list, check out this link! Environment / ToolsBefore you get your coding fingers all warmed up lets take a look at the basic MIDP development environment and process. J2SE SDKAs 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 ToolkitAs 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 WTK can be downloaded and installed from here. Editor of choiceTo 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: Once everything is installed lets get going on the fun stuff, writing code! (at last!) MIDP the APIWhat'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:
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 MIDletMIDP 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 blocksAs 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:
Important limitations and pitfallsThe 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!)
Ok, enough already! lets go! First MIDletTo 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 projectWhen 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.
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"
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 MIDletOk, 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() { } } Compiling, preverifying and runningTo 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"
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 CommandsForms 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. 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! 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 inputMaybe 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! Input handlingJust 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! 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 loopThe 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!) Using ImagesImages 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..) Double bufferingTo 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! 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! 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. ResourcesBelow is a random list of sites related to J2ME and mobile gaming, enjoy! http://java.sun.com/j2me/ - Sun's J2ME website
Discuss this article in the forums
See Also: © 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
|