Vous êtes sur la page 1sur 157

So you want to make your own Java games for java enabled mobile phones.

It's either that or you stumbled on this page by mistake. Just like I said in the introductory post, we'll be using Netbeans IDE on the tutorials that will be presented here. Here's a list of things you need and where to get them:

Java SE Development Kit (JDK) 6.0 | Download Site NetBeans IDE 5.5.1/6.0 Beta 2 | Download Site Netbeans Mobility Pack 5.5.1 for CLDC/MIDP (only for NetBeans 5.5.1) | Download Site

Note that you can download the NetBeans IDE 5.5 bundled with the JDK 6.0 from either site. If you're downloading NetBeans 6.0 Beta 2, you can get it bundled with Mobility pack. The difference between NetBeans 5.5 and 6.0 is that, in NetBeans 6.0 you get to try out the nifty Game Builder that helps you create maps, sprites, and sprite animation sequences. You also get the new syntax highlighting and other new stuff improved from the previous version. If you downloaded the bundle:

Install JDK-Netbeans IDE bundle. Install Netbeans Mobility Pack

If you downloaded them individually:

Install JDK 6.0 Update 3. Install Netbeans IDE 5.5.1/6.0 Beta 2.

Install Netbeans Mobility Pack for CLDC/Midp( only for NetBeans 5.5.1)

That's it! You can now start making J2me applications for your java enabled mobile phone. There's one more thing we have to add to our checklist - a way to transfer your game on your mobile phone. This depends on the phone you're working on. Here's some options you might be able to choose from :

Bluetooth - Use this if your phone is Bluetooth capable. Infrared Cable - Use this if you don't have Bluetooth. USB/Serial Cable - Use this if you have neither Bluetooth nor Infrared Card Reader - Use this if your phone has a memory card and you can't use any of the above.

Aside from the cable/dongle you want to use, you might also need the software and drivers to make them work. You can get these from your phone's manufacturer site or the CD-Rom that came with your phone. An example of the kind of software you might need is the Nokia PC Suite for Nokia cellphones. Obviously, you will also need an image editor for creating the graphics you will use in your game. But for starters you can also find resources on the internet that offer sprites, tiles and other graphic content for you to practice on. Some of which I have added to the links section. Alright, I think that's about it for our checklist. If you have some questions place a comment under this post

As I've said in the Getting Started tutorial, we will be using NetBeans IDE in making our applications for Java enabled mobile phones. If you prefer using the J2me Toolkit you can skip this tutorial as it's a bit about familiarizing the user with the NetBeans IDE. In this tutorial we will be creating our first, traditional "Hello World" MIDlet. "So what's a MIDlet?", you say. Bah! Details...details. Who cares, right? Seriously, in simple terms a MIDlet is a java program you can run on mobile devices such as java enabled cellphones. A MIDlet is to your phone as an exe is to pc (excuse the bad analogy heh). If you haven't already, open the NetBeans IDE. You should see something similar to this:

Choose "File" from the main menu and click on "New Project". Select "Mobile" from the list of Categories and "Mobile Application" from the list of Projects. Click on the "Next" button.

On the next screen, you get to name your project and choose it's location on your hard drive. As an example I typed "MyMidlet" in that box. Be sure to place a check on both "Set as Main Project" and "Create Hello MIDlet" options before you hit the "Next " button.

Select an emulator platform. I recommend choosing the one with the highest version. Leave the Device on "Default ColorPhone". Choose CLDC-1.0 from Device Configuration and MIDP 2.0 for the Device Profile. There's more about these settings later but for now hit the "Finish" button.

If all went well, NetBeans would have created the MIDlet for you including the whole "Hello World" program. You can view the source code of the application by expanding the items of the treeview in the Projects panel. Click on the Run icon from the toolbar, it's the one with the yellow and green rectangles (back-to-backarrows?), or you can just press F6 from your keyboard.

After it has finished compiling and building the program the emulator window will pop-up. You will see the list of MIDlets on the phone screen with your one and only MIDlet already highlighted. You can click on the Select button or the Softkey labeled with "Launch" to run the program or you can simply hit the Enter key from your keyboard.

Painless, wasn't it? Now about those "Platform Settings" we chose earlier. It really depends on the phone you want your program to run on. Most phones

at the time of writing already supports MIDP 2.0 and this in turn allows us to use new libraries that were added to J2me specifically for game development. From here on, the rest of the tutorials you will find here will be focused on MIDP 2.0 phones. The reason that I chose CLDC-1.0 is for compatibility. If you're targeting a specific phone model it's best to view the device specification for that phone from the manufacturers website. One of the main changes from CLDC-1.0 and CLDC-1.1 is that in the latter they have added support for real numbers or floats. Unless you really need to use floats in your code I suggest sticking to CLDC-1.0 for the time being. You can also find resources on the internet on how to deal with floats on the CLDC-1.0 platform.

Creating the Midlet The last tutorial showed you how to use Netbeans IDE to create a basic MIDP 2.0 MIDlet, compile it, and preview your program in the emulator. Unfortunately, the code generated for the Hello MIDlet is of little or no use for making games. So in this tutorial you will be shown the basic code needed for creating your games. First off, open Netbeans and create a new mobile application project. What we need is a blank project so remember to uncheck the "Create Hello MIDlet" on the "Name and Location" screen.

Now we need to create the MIDlet ourselves. There are several ways to go about this.

By pressing CTRL+N on your keyboard. By choosing New File from the File Menu By right-clicking on the project panel treeview and selecting New, then Midlet

For those of you who used one of the first 2 ways, select MIDP from the categories and MIDlet from the list of file types then click on the "Next" button.

On the next screen, type in midMain for MIDlet Name. The MIDlet Class Name field will be automatically filled in for you. Leave the MIDlet icon blank for now. As a rule of thumb, never create your files in the "default package". Enter MyGame in the Package field. A folder with the same name will be created inside the project source folder where all source code for your game should go. Click on the "Finish" button.

Congratulations!! You have succeeded on creating an empty MIDlet that does absolutely nothing. Next stop, Creating the Canvas...

We need to make a canvas based on the new GameCanvas class. The canvas is where we draw all our stuff like images, sprites, maps, scores so it can be shown on the phones screen. It also let's us know what keys were pressed on the phone so we can respond to them. Choose New File from the File menu. Select Java Classes from the list of categories and Java Class from the list of file types then click on the "Next" button. Alternatively, you can right-click on the package name then choose New and Java Class. Type in clsCanvas for the Class Name. Make sure to set the Package field to the package name we set earlier for our MIDlet in the first part of this tutorial. Click on the "Finish" button when you're done.

You should see the source code of class file we just created in the editor panel. If not, then navigate to the file from the project panel and double-click on the filename. Except for the author's name and the file/class/package name (if you chose your own), it should look similar to the code below. * * clsCanvas.java * * Created on October 15, 2007, 7:13 PM * * To change this template, choose Tools | Template Manager * and open the template in the editor. */ package MyGame; /** * * @author devlin */ public class clsCanvas { /** Creates a new instance of clsCanvas */

public clsCanvas() { } }

It's now time to turn this class into a GameCanvas class. Modify this part of the code: public class clsCanvas {

...into this: public class clsCanvas extends GameCanvas implements Runnable {

Your code should now look like this: (I will strip the comments from the code from now on for readability.) package MyGame; public class clsCanvas extends GameCanvas implements Runnable { public clsCanvas() { } }

Press Shift+ALT+F (short cut for Fix Imports) to make sure that all import statements our code requires are detected and added automatically. (Do this periodically or whenever we use a new class or code.) package MyGame; import javax.microedition.lcdui.game.GameCanvas;

public class clsCanvas extends GameCanvas implements Runnable { public clsCanvas() { } }

Now it's time to get rid of those ugly red lines signifying errors in our code. We first need to fulfill the requirements of the base class that we are extending so modify the code further like so: package MyGame; import javax.microedition.lcdui.game.GameCanvas; public class clsCanvas extends GameCanvas implements Runnable { public clsCanvas() { super(true); } public void run() { } }

Tada! No more errors! The superclass GameCanvas requires us to implement it's constructor which has one boolean parameter - suppressKeyEvents. Passing the value true stops key events like keyPressed, keyRepeated and keyReleased from being called and optimizing your code. Then how do we respond to user input then? We use the function getKeyStates() to find out what keys were pressed whenever we need them. More on this later. Our canvas class also implements the Runnable class which allows our game to run on a separate thread. This requires us to implement the run()

method. This will contain our main loop and most game related code. Let's modify the code further to include the main loop for which we will be using a while-loop construct. Modify your run() method like so: public void run() { while(isRunning){ flushGraphics(); } }

Add this private variable under the main class declaration statement public class clsCanvas extends GameCanvas implements Runnable { private boolean isRunning = true;

One feature of the GameCanvas is an off-screen buffer where everything you draw is first rendered. The contents of this buffer is transferred to the screen after the flushGraphics() function is called. This eliminates flicker and makes your animations smoother. So flushGraphics() is called every time at the end of each loop after you have drawn what you want to be shown on the screen. With the isRunning variable set to true, the while loop will run forever. We must add a way for the loop to terminate and our program to end. Let's allow the user to end our program when the Fire or 5 key is pressed on the phone. This is where the getKeyStates() function comes in. Modify the run() method like so: public void run() { int iKey = 0; while(isRunning){ iKey = getKeyStates(); if ((iKey & GameCanvas.FIRE_PRESSED) != 0){

isRunning = false; } flushGraphics(); try{ Thread.sleep(30); } catch (Exception ex){ } } }

We make the thread sleep or pause for 30 milliseconds after each loop. This keeps the the thread from hogging the processing power of the phone and the game from being unresponsive. Without it, the game will not be able to respond to key presses at the moment it needs to. You can change this value to what you want as long as it works. We will be adding frame limiting at some point and that will dynamically adjust the sleep value and make the frame rate of the game somewhat stable on different phones. Let's add some drawing code so we can see what we've done later when we run the application. Add this code in our global variable declarations right under the isRunning declaration: private boolean isRunning = true; private Graphics g;

Add this code in the run() method just before the while loop: g = getGraphics(); while(isRunning){

Add this code inside the run() method just before the flushGraphics() function call: //set drawing color to black g.setColor(0x000000); //fill the whole screen g.fillRect(0, 0, getWidth(), getHeight()); // set drawing color to white g.setColor(0xffffff); //display the key code last pressed g.drawString(Integer.toString(iKey), 2, 2, Graphics.TOP | Graphics.LEFT); flushGraphics();

Add this code at the end of the run() method: } g = null; }

Hit Shift+ALT+F to update the imports section and your code should now look like this: package MyGame; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.game.GameCanvas; public class clsCanvas extends GameCanvas implements Runnable { private boolean isRunning = true; private Graphics g; public clsCanvas() { super(true); } public void run() { int iKey = 0; g = getGraphics();

while(isRunning){ iKey = getKeyStates(); if ((iKey & GameCanvas.FIRE_PRESSED) != 0){ isRunning = false; } //set drawing color to black g.setColor(0x000000); //fill the whole screen g.fillRect(0, 0, getWidth(), getHeight()); // set drawing color to white g.setColor(0xffffff); //display the key code last pressed g.drawString(Integer.toString(iKey), 2, 2, Graphics.TOP | Graphics.LEFT); flushGraphics(); try{ Thread.sleep(30); } catch (Exception ex){ } } g = null; } }

Notice we declared a new Graphics object named g. The Graphics object contains the methods we must use to draw stuff on the GameCanvas. Just take note that the color passed when setColor() is called will apply to succeeding calls to drawing methods until setColor() is called again with a different color. The Graphics object is also declared as a global variable and is assigned to the actual object right before the main loop. This saves us from having to call the getGraphics() method of the GameCanvas over and over.

Displaying the GameCanvas

In this last part of the tutorial entitled Basic Game Template, we will modify the code of our MIDlet and Canvas classes to make the both ends meet and the application to run. It is recommended that you start with Part 1 of this tutorial if you haven't done so. First, we'll need to add a new global variable to reference the MIDlet from the canvas class. Let's call it fParent. So add this code to clsCanvas like so: private Graphics g; private midMain fParent;

Modify the contructor to accept the MIDlet class as a parameter and assign the value to our global variable: public clsCanvas(midMain m) { super(true); fParent = m; }

The canvas needs a way to notify the MIDlet that the main loop has ended and the program needs to terminate. Add this code at the end of our run() method: } g = null; fParent.destroyApp(false); fParent = null; }

We need a new method that the MIDlet can call to start the game thread. Let's call it the start() method and add the code under the clsCanvas constructor:

public void start(){ Thread runner = new Thread(this); runner.start(); }

You should press Shift+ALT+F after editing the code...just in case. Here's the completed clsCanvas source code: package MyGame; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.game.GameCanvas; public class clsCanvas extends GameCanvas implements Runnable { private boolean isRunning = true; private Graphics g; private midMain fParent; public clsCanvas(midMain m) { super(true); fParent = m; } public void start(){ Thread runner = new Thread(this); runner.start(); } public void run() { int iKey = 0; g = getGraphics(); while(isRunning){ iKey = getKeyStates(); if ((iKey & GameCanvas.FIRE_PRESSED) != 0){ isRunning = false; } //set drawing color to black g.setColor(0x000000); //fill the whole screen

g.fillRect(0, 0, getWidth(), getHeight()); // set drawing color to white g.setColor(0xffffff); //display the key code last pressed g.drawString(Integer.toString(iKey), 2, 2, Graphics.TOP | Graphics.LEFT); flushGraphics(); try{ Thread.sleep(30); } catch (Exception ex){ } } g = null; fParent.destroyApp(false); fParent = null; } }

Now open the source code of our MIDlet. We will define a new global variable called myCanvas that will allow the MIDlet to create and reference the clsCanvas class. Place the code just under the midMain class declaration: public class midMain extends MIDlet { clsCanvas myCanvas;

Next, we will modify the startApp() method of our midlet so that it creates a new instance of the clsCanvas class, starts it in a new thread, and finally making the the canvas the current displayed item. Add this code to the startApp() method: public void startApp() { Display d = Display.getDisplay(this); myCanvas = new clsCanvas(this); myCanvas.start(); d.setCurrent(myCanvas); }

The last code we will add is for making sure we release all the resources our MIDlet has used and terminate gracefully when your game has ended. Place this code in the destroyApp() method: public void destroyApp(boolean unconditional) { myCanvas = null; notifyDestroyed(); }

Here's the completed midMain source code: package MyGame; import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class midMain extends MIDlet { clsCanvas myCanvas; public void startApp() { Display d = Display.getDisplay(this); myCanvas = new clsCanvas(this); myCanvas.start(); d.setCurrent(myCanvas); } public void pauseApp() { } public void destroyApp(boolean unconditional) { myCanvas = null; notifyDestroyed(); } }

Yay! We're done! You can now test your by pressing the F6 key on your keyboard or clicking on the Run Main Project icon on the toolbar. That ends

our tutorial on creating a basic template for your game. I will also be using this template for the rest of the tutorials that will be posted here. If you have some comments, suggestions or having problem with the source code, feel free to leave a comment or use the ShoutBox. Have fun.

Making a FullScreen Canvas Updated : 10/24/2007 In this rather short tutorial, we're going to make our canvas take up the whole screen area of the phone. Open the project you created in the tutorial Basic MIDP 2.0 Game Template. Open the file clsCanvas.java then insert setFullScreenMode(true); at the end of the contructor: public clsCanvas(midMain m) { super(true); fParent = m; setFullScreenMode(true); }

That's all there is to it. Update As commented by our anonymous tipper, some of you might encounter a bug while calling the setFullScreenMode() function from the constructor of the GameCanvas class. I decided to dig deeper on this and I was able to find a post at Sun's Java Forums. One poster mentioned a problem with the SonyEricsson P900 and P910 phones. You can read the whole thread here: Link to Thread. I will try to find more information about bugs or other weird behaviors when using the setFullScreenMode() function

Loading Images Into Your Game

Updated : 10/22/2007 The title says it all. We're gonna load some graphics into your game. We're going to build upon the project we made in the tutorial Basic MIDP 2.0 Game Template. So better do that part first if you haven't already. I will be referring to that project as "project".

Image Format The preferred image format is the PNG format. This is a good thing since PNG supports alpha transparency which means that the images can be antialiased and smoother looking. The bad news is not all phones can display images with alpha transparent pixels correctly. It depends on your target phone. But it's still better to avoid alpha transparent pixels as much as possible.

Color Depth Take note that different phones have different color depths. The color depth of the phones today ranges from 8-bit(256colors) to 24-bit color (16,777,216 colors). What does this all mean? While your hi-res imagery looks great on phones with higher depth, they would get dithered on phones with lower color depth making the images look pixelated. Further loss of color might even make your images unrecognizable given the small screen size the game will be viewed from. There's also only a small amount of memory available on mobile phones for you to work with and pictures that use more colors eats up more memory. So as a rule of thumb use less colors for your graphics.

Introducing earth.png For this tutorial I've prepared a quick-and-dirty 16x16 pixel version of your

home planet. W000t!! I drew a whole planet in less than 5 minutes!! It's a bit large though coz' it used up 34 colors (502 bytes). Actual Size of Earth (16x16)

Enlarged View of Earth (64x64)

Open an explorer window and navigate to the folder where you saved the project and make a backup of the whole project folder just in case something goes wrong (or for historical purposes). Better yet use a VCS like CVS or VSS. NetBeans supports either (VSS via plug-in). I After making a backup, go inside the the project folder and inside the src folder. So if the path to your project folder is: c:\YourDocs\netbeans\BasicGameTemplate ...the path to the src folder should be: c:\YourDocs\netbeans\BasicGameTemplate\src Create a new folder inside the src folder named "images". The path to which should look like this: c:\YourDocs\netbeans\BasicGameTemplate\src\images Firefox users can right-click on the earth.png image and choose Save

Image As from the context menu. IE users can right-click on the earth.png image and choose Save Picture As from the context menu. Save the PNG file in the images folder you just created. You can save any of the images above as they are the same file. An even easier way to create the "images" folder is to:
1. Open the project in NetBeans. 2. Right-click on the project name in the Projects panel. 3. From the pop-up menu, choose New and then Folder. 4. Type "images" as the Folder Name in the resulting dialog box. 5. Make sure src is selected as the Parent Folder. 6. Click on the "Finish" button.

Loading the Image Open NetBeans and press CTRL+Shift+O. This will bring about the Open Project dialog box where you can choose the project folder. You can also click on the purple/violet colored folder icon in the main toolbar. Don't forget to set it as the main project by selecting the Open as Main Project at the right side of the dialog box. If that was the same project you had open when you closed NetBeans, the project would be automatically loaded the next time you open NetBeans.

Expand the nodes of the treeview in the Projects panel. You should now see the images folder listed there and earth.png listed under that folder.

You can now open the class file clsCanvas.java so we can start coding. First we need to make a global variable to hold our image. Let's name it "imgEarth". Declare imgEarth right under the fParent declaration like so:

private midMain fParent; private Image imgEarth;

Now would be a good time to press Shift+ALT+F to update the imports section. Let's make a new method named load() where we will place all the code to initialize our game. This is where we actually load the image file. Place it right after the start() method like so:

public void start(){ Thread runner = new Thread(this); runner.start(); } public void load(){ try{ // try to load the image file imgEarth = Image.createImage("/images/earth.png"); }catch(Exception ex){ // exit the app if it fails to load the image isRunning = false; return; } }

The createImage() method of the Image class is used to load the image and assign the resulting Image object to our imgEarth variable. We used the method inside an exception handling block so you can catch the

exception error it generates when it fails to load the image. Just so you know, NetBeans won't let you use it without exception handling. Let's make an unload() method where we can place all the shutdown or cleanup code our game needs. Place it after the newly created load() method:

public void load(){ try{ // try to load the image file imgEarth = Image.createImage("/images/earth.png"); }catch(Exception ex){ // exit the app if it fails to load the image isRunning = false; return; } } public void unload(){ // make sure the object get's destroyed imgEarth = null; }

Now it's time to call the new functions inside the run() method. Place the load() method under the iKey variable declaration like so:

public void run() { int iKey = 0; load(); g = getGraphics();

...then call the unload() method after we assign null to the g variable:

} g = null; unload(); fParent.destroyApp(false);

When you're done, your clsCanvas code should look like this:

package MyGame; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; import javax.microedition.lcdui.game.GameCanvas; public class clsCanvas extends GameCanvas implements Runnable { private boolean isRunning = true; private Graphics g; private midMain fParent; private Image imgEarth; public clsCanvas(midMain m) { super(true); fParent = m; setFullScreenMode(true); } public void start(){ Thread runner = new Thread(this); runner.start(); } public void load(){ try{ // try to load the image file imgEarth = Image.createImage("/images/earth.png"); }catch(Exception ex){ // exit the app if it fails to load the image isRunning = false; return; } }

public void unload(){ // make sure the object get's destroyed imgEarth = null; } public void run() { int iKey = 0; load(); g = getGraphics(); while(isRunning){ iKey = getKeyStates(); if ((iKey & GameCanvas.FIRE_PRESSED) != 0){ isRunning = false; } //set drawing color to black g.setColor(0x000000); //fill the whole screen g.fillRect(0, 0, getWidth(), getHeight()); // set drawing color to white g.setColor(0xffffff); //display the key code last pressed g.drawString(Integer.toString(iKey), 2, 2, Graphics.TOP | Graphics.LEFT); flushGraphics(); try{ Thread.sleep(30); } catch (Exception ex){ } } g = null; unload(); fParent.destroyApp(false); fParent = null; } }

Drawing the Image

To draw the image on the screen insert this line of code inside the run() method just before the flushGraphics() function call:

//draw the image g.drawImage(imgEarth, 50, 50, Graphics.TOP | Graphics.LEFT); flushGraphics();

The drawImage() method of the Graphics object g is used to draw the image unto our canvas at the coordinates 50(X), 50(Y). The last parameter, Graphics.Top | Graphics.Left, defines the anchor point or part of the image that will reside at the given coordinates. In this example the top-left part of the image will be positioned at coordinates 50,50 , X and Y respectively. The completed clsCanvas source code:

package MyGame; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; import javax.microedition.lcdui.game.GameCanvas; public class clsCanvas extends GameCanvas implements Runnable { private boolean isRunning = true; private Graphics g; private midMain fParent; private Image imgEarth; public clsCanvas(midMain m) { super(true); fParent = m; setFullScreenMode(true); }

public void start(){ Thread runner = new Thread(this); runner.start(); } public void load(){ try{ // try to load the image file imgEarth = Image.createImage("/images/earth.png"); }catch(Exception ex){ // exit the app if it fails to load the image isRunning = false; return; } } public void unload(){ // make sure the object get's destroyed imgEarth = null; } public void run() { int iKey = 0; load(); g = getGraphics(); while(isRunning){ iKey = getKeyStates(); if ((iKey & GameCanvas.FIRE_PRESSED) != 0){ isRunning = false; } //set drawing color to black g.setColor(0x000000); //fill the whole screen g.fillRect(0, 0, getWidth(), getHeight()); // set drawing color to white g.setColor(0xffffff); //display the key code last pressed g.drawString(Integer.toString(iKey), 2, 2, Graphics.TOP | Graphics.LEFT); //draw the image g.drawImage(imgEarth, 50, 50, Graphics.TOP | Graphics.LEFT);

flushGraphics(); try{ Thread.sleep(30); } catch (Exception ex){ } } g = null; unload(); fParent.destroyApp(false); fParent = null; } }

Whenever your ready you can hit F6 on your keyboard and start the MIDlet when the emulator pops up. You should see the something like the screeny below.

Got an error? Something could've been done better? --> Post a comment.

Update The code in this tutorial now runs in fullscreen mode. Details can be found here : Making a FullScreen Canvas

Clipping Images or Displaying Only Parts of an Image Sometimes you just need to display certain parts of an image. Like for instance, when you want to show some animation, It would be better to place all the animation frames inside a single image file rather than to store them in several files. This will help save some precious phone memory since you only have to load one image file into one Image object so there's no extra overhead from using multiple Image object instances. Once again we will just be using the project we last modified in the previous tutorial : Loading Images Into Your Game. I'm also writing this in preparation for the next tutorial which is about graphical menus where clipping plays a major part.

Reintroducing Earth: Animated In this tutorial we're going to show earth spinning on the phone screen like the image displayed below:

Spinning Earth 16 x 16 pixels

Here is the image strip for that animation. Firefox users can right-click and choose "Save Image As" from the context menu while IE users can choose "Save Picture As" instead. Save the image inside the "images" folder of the "src" folder of the project.

Spinning Earth animation with all frames in a single image.

Dimensions: 144 x 16 pixels Frame Size: 16 x 16 pixels

Just in case you're wondering, both images were made from using a satellite image of earth which you can see here:

...which is a bit of an overkill.

Juggling Frames What we're actually going to do is to draw one frame at a time from the image strip you just downloaded. All the while keeping the frame we want to draw inside the clipping rectangle. An illustration would be best to demonstrate what I'm trying to say:

The red rectangle represents the clipping rectangle and everything outside that rectangle will not be visible on the screen. The dimmed portion of the image strip represents the portion that will not be visible on the screen. The frame number being displayed is the current frame that is inside the clipping rectangle and is visible on the screen. We decrement the X of the image strip by 16 pixels to skip to the beginning of each frame going from right to left. Again the most important thing to remember about clipping rectangles is that everything outside it is invisible and everything inside it is visible. It's now time to start NetBeans and open the project. Upon opening the project you should see the "earthstrip.png" file inside the images folder. Navigate your way to clsCanvas.java so we can start editing some code. In the load() method, change "earth.png" to "earthstrip.png".

// try to load the image file imgEarth = Image.createImage("/images/earthstrip.png");

Let's add some variables in the run() method under the iKey declaration:

public void run() { int iKey = 0; int imgX = 50; // x coordinate of the image int frameIndex = 0; // current frame to be drawn long lDelay = 250; //time to pause between frames in milliseconds long lStart = 0; //time we last changed frames in milliseconds long lCurrTick = 0; // current system time in milliseconds;

Add this code inside the main loop after we check for key presses and before we call the drawing methods:

if ((iKey & GameCanvas.FIRE_PRESSED) != 0){ isRunning = false; } lCurrTick = System.currentTimeMillis(); if ((lCurrTick-lStart) >= lDelay){ lStart = lCurrTick; // save the current time if (frameIndex < 8) { frameIndex++; // skip to the next frame } else { frameIndex = 0; // go back to first frame } imgX = 50 - (frameIndex * 16); // compute x relative to clip rect } //restore the clipping rectangle to full screen g.setClip(0, 0, getWidth(), getHeight()); //set drawing color to black g.setColor(0x000000);

There's a lot happening there. First, we store the current system time in milliseconds to the lCurrTick variable. We use that to check if the time that has passed since the last time we changed the frame, lStart, is greater than the delay we set, lDelay. If so, then it's time to change frames. And since we are changing frames, we will set lStart to the current time for our delay to work on the next loop. We have a total of 9 frames in that animation which we index starting at 0. That means the last frame would have an index of 8. So we check if the current frame index which is stored in frameIndex is the last index. If not, then we increment it by 1 frame. If it is the last frame, we then return to the first frame.

After we have figured out if it's time to change frames and what frame to display next, we can now compute the X coordinate of the image strip so the frame we want to show will be drawn inside the clipping rectangle. Here's the formula in pseudo code:

image_X = X_of_clipping_rectangle - (frame_index * frame_width);

setClip(), Not Scissors!! You might have also noticed that we called a new method: setClip(). The setClip() method of the Graphics class is what let's us define the position and size of the clipping rectangle. The first two parameters defines the position, x and y, and the last two parameters defines the width and height of the clipping rectangle. Let's draw some more. Add this code in the main loop just before the call to drawImage():

g.drawString("Frame : " + Integer.toString(frameIndex), 2, 68, Graphics.TOP | Graphics.LEFT); g.drawString("X : " + Integer.toString(imgX), 2, 88, Graphics.TOP | Graphics.LEFT); //clip the drawing area to a single frame g.setClip(50, 50, 16, 16);

...and modify the drawImage() function call like so:

g.drawImage(imgEarth, imgX, 50, Graphics.TOP | Graphics.LEFT);

Notice that we placed two calls to the setClip() method of the Graphics object g. The first call sets the clipping rectangles dimensions to match that of the whole screen area of the phone. The second call sets the clipping rectangle to where the animated globe will be drawn. This is because the dimensions you pass to the setClip() method the last time you call it will be in effect until you call setClip() again with a different set of parameters. This means if you don't reset the clipping rectangle, you won't be able to draw anywhere else on the screen. Anything you draw outside the clipping rectangle will not show up on the phone screen. So it's good practice to call the setClip() method with full screen dimensions at the top of your drawing code. Your clsCanvas source code should now look like this:

package MyGame; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; import javax.microedition.lcdui.game.GameCanvas; public class clsCanvas extends GameCanvas implements Runnable { private boolean isRunning = true; private Graphics g; private midMain fParent; private Image imgEarth; public clsCanvas(midMain m) { super(true); fParent = m; setFullScreenMode(true); } public void start(){ Thread runner = new Thread(this); runner.start(); } public void load(){

try{ // try to load the image file imgEarth = Image.createImage("/images/earthstrip.png"); }catch(Exception ex){ // exit the app if it fails to load the image isRunning = false; return; } } public void unload(){ // make sure the object get's destroyed imgEarth = null; } public void run() { int iKey = 0; int imgX = 50; // x coordinate of the image int frameIndex = 0; // current frame to be drawn long lDelay = 250; //time to pause between frames in milliseconds long lStart = 0; //time we last changed frames in milliseconds long lCurrTick = 0; // current system time in milliseconds; load(); g = getGraphics(); while(isRunning){ iKey = getKeyStates(); if ((iKey & GameCanvas.FIRE_PRESSED) != 0){ isRunning = false; } lCurrTick = System.currentTimeMillis(); if ((lCurrTick-lStart) >= lDelay){ lStart = lCurrTick; // save the current time if (frameIndex < 8) { frameIndex++; // skip to the next frame } else { frameIndex = 0; // go back to first frame } imgX = 50 - (frameIndex * 16); // compute x

relative to clip rect } //restore the clipping rectangle to full screen g.setClip(0, 0, getWidth(), getHeight()); //set drawing color to black g.setColor(0x000000); //fill the whole screen g.fillRect(0, 0, getWidth(), getHeight()); // set drawing color to white g.setColor(0xffffff); //display the key code last pressed g.drawString(Integer.toString(iKey), 2, 2, Graphics.TOP | Graphics.LEFT); g.drawString("Frame : " + Integer.toString(frameIndex), 2, 68, Graphics.TOP | Graphics.LEFT); g.drawString("X : " + Integer.toString(imgX), 2, 88, Graphics.TOP | Graphics.LEFT); //clip the drawing area to a single frame g.setClip(50, 50, 16, 16); //draw the image g.drawImage(imgEarth, imgX, 50, Graphics.TOP | Graphics.LEFT); flushGraphics(); try{ Thread.sleep(30); } catch (Exception ex){ } } g = null; unload(); fParent.destroyApp(false); fParent = null; } }

You can start up the MIDlet now. You should see a spinning globe with the

current frame and x coordinate displayed below it. You can also change the speed in which the globe rotates by increasing or decreasing the value of iDelay in the run() method. Increasing iDelay will make the animation run slower while setting it to a lower value will make the animation run faster.

That's it for clipping. If you have a question or would like to point out a mistake, feel free to post a comment. Have fun!!!

Thursday, October 25, 2007 Input Handling : Keypress with Repeat Rate

There are a lot of ways to deal with user input in your game. This tutorial will show one of the solutions that you can use on certain parts of the game. Like when your prompting the user to exit the game or not, while in the main menu, or when you want the user to choose from some options on the screen. As a warning, we're going to limit the code to using the getKeyStates() method only.

The Problem : It's Like The Keys Get Stuck.. Continues key presses are good while playing the game because the game can respond at the heat of the moment and match the users super reflexes. But not during selection screens. Why so? Let's say your game has a frame rate of 15 frames per second. That's also the speed at which it can act every time the user presses a key on the phone. Imagine that in your game the second item from the top of the list is the View Instructions item and the current selected or highlighted item is the New Game item which is the first item on the menu. The user, trying to get to the Instructions item, presses the down button and the selection moves down from one item to the other at an alarming rate of 15 times in one second. The confused user will not be able to accurately pick items from the menu.

Better yet, imagine typing a document in a text editor and every time you hit the keyboard you get 15 of each character you type in. It would be like driving a car without breaks (No, I'm not drawing that for you!!!).

Making the Keys Toggle In this tutorial we're going to add some interactivity to the spinning globe we made in the last tutorial : Clipping Images or Displaying Only Parts of an Image. We'll change the code so that the user can adjust the speed at which the globe spins. So load the project in NetBeans and open the clsCanvas code. Let's declare some constants and variables. Insert this code under the clsCanvas class declaration:

public class clsCanvas extends GameCanvas implements Runnable { // key repeat rate in milliseconds public static final int keyDelay = 250; //key constants public static final int upKey = 0; public static final int leftKey = 1;

public static final int downKey = 2; public static final int rightKey = 3; public static final int fireKey = 4; //key states for up, left, down, right, and fire key private boolean[] isDown = { false, false, false, false, false }; //last time the key changed state private long[] keyTick = { 0, 0, 0, 0, 0 }; //lookup table for key constants :P private int[] keyValue = { GameCanvas.UP_PRESSED, GameCanvas.LEFT_PRESSED, GameCanvas.DOWN_PRESSED, GameCanvas.RIGHT_PRESSED, GameCanvas.FIRE_PRESSED };

The value assigned to the constant keyDelay will determine how fast a key state changes from being pressed to not pressed while holding down a key. In other words, this will be the repeat rate. The higher the number, the slower the repeat rate gets and vice versa. The next five constants we declared are for the 5 standard game keys Up, Left, Down, Right, and the Fire key. We just defined them so our code won't become confusing, so instead of using numbers we can use "meaningful words". Also, someone told me about 12-14 years ago that it was good practice to define constants for numbers or other fixed values your going to use repeatedly all over your code. The boolean array isDown[] is going to hold the state for each of the standard keys we've defined while the long array keyTick[] will hold the time when a key last changed from being down to up. The last array keyValue[], an integer array to hold the actual key codes for

each key. We defined this so we can loop through each key in our key detection code which you will see in the next code section. We'll add a new method to our clsCanvas class called checkKeys(). Add the code above the run() method:

public void checkKeys(int iKey, long currTick){ long elapsedTick = 0; //loop through the keys for (int i = 0; i < 5; i++){ // by default, key not pressed by user isDown[i] = false; // is user pressing the key if ((iKey & keyValue[i]) != 0){ elapsedTick = currTick - keyTick[i]; //is it time to toggle key state? if (elapsedTick >= keyDelay){ // save the current time keyTick[i] = currTick; // toggle the state to down or pressed isDown[i] = true; } } } } public void run() {

The checkKeys() method takes the value returned by the getKeyStates() method in the iKey parameter and the current time in milliseconds in the currTick parameter. It then loops through all the keys in the keyValue[] array too see if a certain key is being pressed. It then toggles the value of the appropriate element in the isDown[] array depending on how long it's

been since the key was in the pressed state. It also makes sure that the isDown[] array is updated as to which keys are not being pressed. Let's change our run() method so that when the user presses the left key the spinning animation speeds up and when the user presses the right key the animation slows down. First let's get rid of the old way we detect the fire key so that it makes use of the new code we just added. So delete or comment the following lines from the run() method, from inside the while loop:

/* if ((iKey & GameCanvas.FIRE_PRESSED) != 0){ isRunning = false; } */

Next, insert the following code under the line where we store the current time in lCurrTick so we can pass that value to the checkKeys() method: lCurrTick = System.currentTimeMillis(); checkKeys(iKey, lCurrTick); if (isDown[leftKey]){ if (lDelay > 0){ lDelay-=10; } } else if (isDown[rightKey]){ if (lDelay < 1000){ lDelay+=10; } } else if (isDown[fireKey]){ isRunning = false; }

Add this next code just before the second call to setClip() so we can display some feedback when the keys are pressed:

g.drawString("lDelay : " + Long.toString(lDelay), 2, 108, Graphics.TOP | Graphics.LEFT); if (isDown[upKey]) { g.drawString("up key pressed", 2, 128, Graphics.TOP | Graphics.LEFT); } else if (isDown[leftKey]) { g.drawString("left key pressed", 2, 128, Graphics.TOP | Graphics.LEFT); } else if (isDown[downKey]) { g.drawString("down key pressed", 2, 128, Graphics.TOP | Graphics.LEFT); } else if (isDown[rightKey]) { g.drawString("right key pressed", 2, 128, Graphics.TOP | Graphics.LEFT); } //clip the drawing area to a single frame g.setClip(50, 50, 16, 16);

The last code we added displays the value of lDelay so we know if it's actually getting changed. It also displays notification whenever one of the directional keys are pressed. It doesn't display feedback for the Fire key because when that is pressed the MIDlet is terminated. Here's the completed clsCanvas source code:

package MyGame; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; import javax.microedition.lcdui.game.GameCanvas; public class clsCanvas extends GameCanvas implements Runnable {

// key repeat rate in milliseconds public static final int keyDelay = 250; //key constants public static final public static final public static final public static final public static final int int int int int upKey = 0; leftKey = 1; downKey = 2; rightKey = 3; fireKey = 4;

//key states for up, left, down, right, and fire key private boolean[] isDown = { false, false, false, false, false }; //last time the key changed state private long[] keyTick = { 0, 0, 0, 0, 0 }; //lookup table for key constants :P private int[] keyValue = { GameCanvas.UP_PRESSED, GameCanvas.LEFT_PRESSED, GameCanvas.DOWN_PRESSED, GameCanvas.RIGHT_PRESSED, GameCanvas.FIRE_PRESSED }; private private private private boolean isRunning = true; Graphics g; midMain fParent; Image imgEarth;

public clsCanvas(midMain m) { super(true); fParent = m; setFullScreenMode(true); } public void start(){ Thread runner = new Thread(this); runner.start(); } public void load(){ try{ // try to load the image file imgEarth =

Image.createImage("/images/earthstrip.png"); }catch(Exception ex){ // exit the app if it fails to load the image isRunning = false; return; } } public void unload(){ // make sure the object get's destroyed imgEarth = null; } public void checkKeys(int iKey, long currTick){ long elapsedTick = 0; //loop through the keys for (int i = 0; i < 5; i++){ // by default, key not pressed by user isDown[i] = false; // is user pressing the key if ((iKey & keyValue[i]) != 0){ elapsedTick = currTick - keyTick[i]; //is it time to toggle key state? if (elapsedTick >= keyDelay){ // save the current time keyTick[i] = currTick; // toggle the state to down or pressed isDown[i] = true; } } } } public void run() { int iKey = 0; int imgX = 50; // x coordinate of the image int frameIndex = 0; // current frame to be drawn long lDelay = 250; //time to pause between frames in milliseconds long lStart = 0; //time we last changed frames in

milliseconds long lCurrTick = 0; // current system time in milliseconds; load(); g = getGraphics(); while(isRunning){ iKey = getKeyStates(); /* if ((iKey & GameCanvas.FIRE_PRESSED) != 0){ isRunning = false; } */ lCurrTick = System.currentTimeMillis(); checkKeys(iKey, lCurrTick); if (isDown[leftKey]){ if (lDelay > 0){ lDelay-=10; } } else if (isDown[rightKey]){ if (lDelay < 1000){ lDelay+=10; } } else if (isDown[fireKey]){ isRunning = false; } if ((lCurrTick-lStart) >= lDelay){ lStart = lCurrTick; // save the current time if (frameIndex < 8) { frameIndex++; // skip to the next frame } else { frameIndex = 0; // go back to first frame } imgX = 50 - (frameIndex * 16); // compute x relative to clip rect } //restore the clipping rectangle to full screen g.setClip(0, 0, getWidth(), getHeight()); //set drawing color to black g.setColor(0x000000); //fill the whole screen g.fillRect(0, 0, getWidth(), getHeight());

// set drawing color to white g.setColor(0xffffff); //display the key code last pressed g.drawString(Integer.toString(iKey), 2, 2, Graphics.TOP | Graphics.LEFT); g.drawString("Frame : " + Integer.toString(frameIndex), 2, 68, Graphics.TOP | Graphics.LEFT); g.drawString("X : " + Integer.toString(imgX), 2, 88, Graphics.TOP | Graphics.LEFT); g.drawString("lDelay : " + Long.toString(lDelay), 2, 108, Graphics.TOP | Graphics.LEFT); if (isDown[upKey]) { g.drawString("up key pressed", 2, 128, Graphics.TOP | Graphics.LEFT); } else if (isDown[leftKey]) { g.drawString("left key pressed", 2, 128, Graphics.TOP | Graphics.LEFT); } else if (isDown[downKey]) { g.drawString("down key pressed", 2, 128, Graphics.TOP | Graphics.LEFT); } else if (isDown[rightKey]) { g.drawString("right key pressed", 2, 128, Graphics.TOP | Graphics.LEFT); } //clip the drawing area to a single frame g.setClip(50, 50, 16, 16); //draw the image g.drawImage(imgEarth, imgX, 50, Graphics.TOP | Graphics.LEFT); flushGraphics(); try{ Thread.sleep(30); } catch (Exception ex){ } } g = null; unload(); fParent.destroyApp(false); fParent = null;

} }

Great! Now you can hit F6 and view the result of your work.

Ponts to Ponder There are a few more things you can do to the sample code presented in this tutorial. For instance, you can add support for the other game keys or move the checkKeys() method in it's own class along with the supporting variables and even make it a static method. You can also change the value of keyDelay to that which suits you. Finally, with a few modification you can use the same technique when handling key codes from keyPressed() and keyReleased() call back methods. Although the sample code used here is quite usable, it doesn't mean you

can't do it in your own way. What matters most is that you have a general understanding of the problem and how to solve it in a simple but effective manner. Something is not working? I got it totally wrong? You got a question? Feel free to post a comment. XD

Adding an Icon to Your MIDlet or Game

I was looking at my blog's search statistics at Google Webmaster Tools when I found a few users using key words like "j2me icon MIDlet", "MIDP icon bit depth", "MIDlet icon transparency". So I decided to write this tutorial just for those people...how ever late it might seem to be. If you we're following the series of tutorials presented here, you would recall (or not) that in first part of the tutorial Creating a Basic MIDP 2.0 Game Template you were prompted for an icon to associate with your MIDlet.

We chose to ignore the field because it seemed trivial at the time. But I bear great news to you! Because in NetBeans you can still add an icon to your MIDlet through the Project Properties window!!! Oh, so you knew that already. Well, for those of you who don't, you can access the Project Properties from the File Menu and choosing the menu item labeled with "Your-Project-Name" Properties, with the double quotes.

You can also reach the Project Properties by right-clicking on the project name from the Projects Panel treeview and selecting Properties at the bottom of the context menu.

Before you go adding those uber cool icons one thing you have to remember is that size does matter when it comes to MIDlet icons. Meaning the width and height of the icon. If you also read the tutorial Loading Images Into Your Game, it was mentioned that the number of colors used should be kept at a minimum, preferably at the 8-bit depth of 256 colors max. You should also avoid anti-aliased icons because some phones don't support them and will display the anti-aliased icon with "dirty" edges or with random pixels around the edges that are supposed to be semi-transparent. The PNG format is also the preferred and widely used file format for a MIDlet icon. Here are some icon sizes you may consider: Resolution Phone/ 128x1 128x1 176x2 208x1 208x2 240x3 320x2 352x4 416x3 Model 28 60 08 76 08 20 40 16 52 BlackB 45x45 erry Motorol 15x15 a 16x16 32x32

Nokia S40

Nokia S60 1st/2nd Edition Nokia n/a n/a S60 3rd Edition Sagem 18x18 Samsu 16x16 ng 29x29 32x32 Sanyo 24x24 Sharp 27x27 Siemen 18x18 s Sony 16x16 Ericsso 32x32 n

16x16 18x18 n/a n/a 24x24 29x29 n/a n/a 42x29 n/a 29x29

46x46 46x48 n/a 42x29 n/a n/a n/a

n/a

n/a

76x76 n/a

31x31 37x37 37x37 53x53 52x52 64x64 76x76 42x29 55x55 54x54 84x58 64x64

Note that most of the icon sizes for the Nokia mobile phones came from the Using Icons in Midlets technical note at Forum Nokia. Some of the additional sizes came from personally testing them on various phone models. The icon sizes for the other brands of mobile phones came from other developers experiences. Mobile phones behave differently from one another on the way they try to display an incorrectly sized icon. Some phones will scale your icons to the right size. Other phones will display the icon as a small glyph at the upperleft corner of the area where the full sized icon should appear. The glyph displayed is so small you can barely see it. If that's the case, then you can try the next larger icon size. Some phones will also clip the icon to fit the area reserved for it like in the case of using 42x29 pixel icons on older Series 60 phones with a resolution of 176x208. There are several ways to find out what icon size you can use. One sure way to find the size that fits your target phone is by extracting the contents of

the jar file of a game or application designed for that phone. Somehow that didn't quite come out right... If your phone supports themes or allows you to customize the skin, you can usually download a theme editor or supplemental documents for creating themes/skin for your phone and from there you can gather the information you need.

Here are some tools you can use to make the PNG images:

GIMP - Link to Site Paint.Net - Link to Site Adobe Fireworks - Link to Site Adobe Photoshop - Link to Site You can find a lot more tools you can use on the internet.

I won't go into details as to how to use those applications as such is beyond the scope of this tutorial. I'll just give you a screenshot of the settings I use in Fireworks. It should provide some insight on the settings you can use in other applications. These are the settings I use on icons with a transparent background. Notice that there's only one transparent color in the pallet:

These are the settings I use on icons with no transparency:

I have prepared a transparent icon for you to use in this tutorial. You can use your own work of art if you want to.

42x29 pixels

Firefox users can right-click on the image and choose Save Image As from the context menu while IE users can choose Save Picture As from the context menu. Save the PNG file inside the images folder inside the projects src. If you can't find the images folder then make one. You can always just place the file inside the src folder and it will be listed under the Default package. But then again I find it easier to work on the game when all the images are kept inside a folder of their own. Open the Project Properties in NetBeans and navigate your way to the MIDlets section which is listed under Application Descriptor. You will find your MIDlets listed here. Double-click on the MIDlet or select the MIDlet and click on the Edit button to the right of the list.

By now, NetBeans would have detected the new image you have saved earlier and you can simply choose it from the images listed in the combo box of the Edit MIDlet dialog box.

Click the "Ok" button on the Edit MIDlet dialog to confirm your selection then click the "Ok" button again in the Project Properties window to commit your changes. Press F6 to test your MIDlet in the emulator.

When you run the emulator you will see the MIDlet brandishing your new icon. Hold on a second?!! Why does our 42x29 icon look squashed and squarish? That's only how the emulator displays your icon. You will only find out what it actually looks like once you have tested your MIDlet on a real phone. I hope this tutorial have at least given you a clue of how to find your way through the murky world of MIDlet icons. Some humble words from Forrest Grumpy: My momma always said, "MIDlet Icons was like a box of chocolates. You never know what you're gonna get."

No Commands: Delicious Graphical Menus

Aesthetics plays an important part in the games that you make. Making the main menu look like it fits the game is equally as important. If done right, it can set the mood for the player even before actually playing the game. Let's face it. Using Command Listeners and objects for your menus just doesn't feel right. Command objects just don't look like they're part of the game. On some phones, the game is hidden when the menu is activated. Phones don't even display the label for the keys you have to press to activate the menu when the game is in fullscreen mode. Graphical menus solves these problems and makes the game look cool too. Clipping is used extensively in this tutorial. If you need a quick study, turn to this tutorial : Clipping Images or Displaying Only Parts of an Image.

Loading the Images In this tutorial we will make a simple vertical menu in the middle of the canvas. The MIDlet will be designed for mobile phones with 176x208 screen resolution.

I have prepared a clean project for you to start with. It includes the basic game template and the input handling code from the tutorial Input Handling : Keypress with Repeat Rate. It also includes the images we're going to use for the menu. Download the one for the version of NetBeans you will use:

BasicGameTemplate4NB5.zip (39.04 KB) - project code for NetBeans 5.5 BasicGameTemplate4NB6.zip (45.03 KB) - project code for NetBeans 6.0

When you open the project in NetBeans, you should see these 2 files inside the images folder: logo.png - 176x208 pixels

menuitems.png - 82x80 pixels

First we'll have to load the images. So open the clsCanvas code and add these variable declarations above the constructor: private midMain fParent; private Image imgBG; private Image imgMenu; public clsCanvas(midMain m) {

Add the actual image loading code in the load() method. Make sure they are inside the try..catch or exception handling code: public void load(){ try{ // load the images here imgBG = Image.createImage("/images/logo.png"); imgMenu = Image.createImage("/images/menuitems.png");

}catch(Exception ex){ Add these lines inside the unload() method to make sure the resources reserved for those objects will get freed:

public void // make imgMenu imgBG = }

unload(){ sure the object get's destroyed = null; null;

The logo can now replace the call to fillRect() that we used to clear the screen since the logo takes up the whole screen. Remove or comment the lines from the main loop as indicated below:

//restore the clipping rectangle to full screen g.setClip(0, 0, getWidth(), getHeight()); /* start - delete lines //set drawing color to black g.setColor(0x000000); //fill the whole screen g.fillRect(0, 0, getWidth(), getHeight()); */ end - delete lines // set drawing color to white g.setColor(0xffffff);

Now we can draw the logo. Insert this code under the call to setClip() inside the main loop :

//restore the clipping rectangle to full screen g.setClip(0, 0, getWidth(), getHeight()); g.drawImage(imgBG, 0, 0, Graphics.TOP | Graphics.LEFT);

Drawing the Menu We'll need a variable to store the currently focused menu item. Let's call it menuIndex and declare it under the other variables we declared earlier:

private Image imgMenu; private int menuIndex = 0; public clsCanvas(midMain m) {

The drawing code for the menu will be placed on a new method name drawMenu(). Add this code above the run() method:

public void drawMenu(Graphics g){ int cy = 0; for (int i = 0; i < 5; i++){ //compute the Y position of the menu item cy = 64 + (i * 22); //set the clipping rectangle to where the item will be drawn g.setClip(47, cy, 82, 20); if (menuIndex == i){ //draw the light button if the item is focused g.drawImage(imgMenu, 47, cy - 20, Graphics.TOP | Graphics.LEFT); } else { //draw the dark button if the item is not focused g.drawImage(imgMenu, 47, cy, Graphics.TOP | Graphics.LEFT); } //offset of the label is 6 pixels from the top of the button cy += 6; //set the clipping rectangle to where the label will be drawn g.setClip(47, cy, 82, 8);

//draw the label so that it is inside the clipping rectangle g.drawImage(imgMenu, 47, cy - (40 + (i * 8)), Graphics.TOP | Graphics.LEFT); } } public void run() {

The menu will be drawn 64 pixels from the top of the screen and the menu items are drawn at 22 pixel intervals and since each of the menu items is only 20 pixels in height, a 2 pixel space will be left between the menu items acting as a margin. Depending on the value of menuIndex, either a light-blue or a dark-blue button will be drawn. Finally, the label of each menu item is drawn 6 pixels lower than the menu items position. To show the menu on the screen, simply insert the following lines of code at the point after which the logo has been drawn:

//restore the clipping rectangle to full screen g.setClip(0, 0, getWidth(), getHeight()); //draw the logo g.drawImage(imgBG, 0, 0, Graphics.TOP | Graphics.LEFT); //draw the menu drawMenu(g); //restore the clipping rectangle to full screen again g.setClip(0, 0, getWidth(), getHeight()); // set drawing color to white

Notice that we now have 2 calls to setClip() where the clipping rectangle is reset to fullscreen. The second call is necessary because when the drawMenu() is finished, the method would have resized and positioned the clipping rectangle to that of the last label in the menu. Reseting the clipping rectangle after drawMenu() is called allows us to draw more stuff in other

parts of the screen. The only thing left to do now is to make the menu respond to keypresses. Modify the conditional statement under the call to the checkKeys() method inside the main loop like so:

checkKeys(iKey, lCurrTick); if (isDown[upKey]){ //move focus up if (menuIndex > 0){ menuIndex--; } else { menuIndex = 4; } } else if (isDown[downKey]){ //move focus down if (menuIndex < 4){ menuIndex++; } else { menuIndex = 0; } } else if (isDown[fireKey]){ //do action depending on the menu item selected if (menuIndex == 4){ isRunning = false; } }

The last code allows us to move the highlight or focus from one menu item to the other. Pressing the UP key should now move the focus one menu item higher in the list. Pressing the DOWN key should move the focus one menu item lower in the list. Pressing the FIRE key should select the focused menu item and initiate an "action". So far, there is only one "action" defined in this menu and that is for exiting the MIDlet from the EXIT menu item. Here's the completed clsCanvas code so you can check if you missed something:

package MyGame; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; import javax.microedition.lcdui.game.GameCanvas; public class clsCanvas extends GameCanvas implements Runnable { // key repeat rate in milliseconds public static final int keyDelay = 250; //key constants public static final public static final public static final public static final public static final int int int int int upKey = 0; leftKey = 1; downKey = 2; rightKey = 3; fireKey = 4;

//key states for up, left, down, right, and fire key private boolean[] isDown = { false, false, false, false, false }; //last time the key changed state private long[] keyTick = { 0, 0, 0, 0, 0 }; //lookup table for key constants :P private int[] keyValue = { GameCanvas.UP_PRESSED, GameCanvas.LEFT_PRESSED, GameCanvas.DOWN_PRESSED, GameCanvas.RIGHT_PRESSED, GameCanvas.FIRE_PRESSED }; private boolean isRunning = true; private Graphics g; private midMain fParent; private Image imgBG; private Image imgMenu; //stores the focused menu item private int menuIndex = 0; public clsCanvas(midMain m) { super(true); fParent = m; setFullScreenMode(true);

} public void start(){ Thread runner = new Thread(this); runner.start(); } public void load(){ try{ // load the images here imgBG = Image.createImage("/images/logo.png"); imgMenu = Image.createImage("/images/menuitems.png"); }catch(Exception ex){ // exit the app if it fails to load the image isRunning = false; return; } } public void // make imgMenu imgBG = } unload(){ sure the object get's destroyed = null; null;

public void checkKeys(int iKey, long currTick){ long elapsedTick = 0; //loop through the keys for (int i = 0; i < 5; i++){ // by default, key not pressed by user isDown[i] = false; // is user pressing the key if ((iKey & keyValue[i]) != 0){ elapsedTick = currTick - keyTick[i]; //is it time to toggle key state? if (elapsedTick >= keyDelay){ // save the current time keyTick[i] = currTick; // toggle the state to down or pressed isDown[i] = true; } } } } public void drawMenu(Graphics g){

int cy = 0; for (int i = 0; i < 5; i++){ //compute the Y position of the menu item cy = 64 + (i * 22); //set the clipping rectangle to where the item will be drawn g.setClip(47, cy, 82, 20); if (menuIndex == i){ //draw the light button if the item is selected g.drawImage(imgMenu, 47, cy - 20, Graphics.TOP | Graphics.LEFT); } else { //draw the dark button if the item is not selected g.drawImage(imgMenu, 47, cy, Graphics.TOP | Graphics.LEFT); } //offset of the label is 6 pixels from the top of the button cy += 6; //set the clipping rectangle to where the label will be drawn g.setClip(47, cy, 82, 8); //draw the label so that it is inside the clipping rectangle g.drawImage(imgMenu, 47, cy - (40 + (i * 8)), Graphics.TOP | Graphics.LEFT); } } public void run() { int iKey = 0; long lCurrTick = 0; // current system time in milliseconds; load(); g = getGraphics(); while(isRunning){ lCurrTick = System.currentTimeMillis(); iKey = getKeyStates(); checkKeys(iKey, lCurrTick); if (isDown[upKey]){ //move focus up if (menuIndex > 0){ menuIndex--; } else {

menuIndex = 4; } } else if (isDown[downKey]){ //move focus down if (menuIndex < 4){ menuIndex++; } else { menuIndex = 0; } } else if (isDown[fireKey]){ //do action depending on the menu item selected if (menuIndex == 4){ isRunning = false; } } //restore the clipping rectangle to full screen g.setClip(0, 0, getWidth(), getHeight()); //draw the logo g.drawImage(imgBG, 0, 0, Graphics.TOP | Graphics.LEFT); //draw the menu drawMenu(g); //restore the clipping rectangle to full screen again g.setClip(0, 0, getWidth(), getHeight()); // set drawing color to white g.setColor(0xffffff); //display the key code last pressed g.drawString(Integer.toString(iKey), 2, 2, Graphics.TOP | Graphics.LEFT); flushGraphics(); try{ Thread.sleep(30); } catch (Exception ex){ } } g = null; unload(); fParent.destroyApp(false); fParent = null; } }

Press the F6 key in NetBeans to see if it actually works.

Here's a link to a low-res flash video of the MIDlet running on an N70: Click to View Video The technique used here to draw the vertical menu is pretty old school. I've been using it in DOS based games written in Turbo Pascal, DirectX games written in VisualBasic 6.0, and in C# using XNA Game Studio Express. One thing to remember is that you're not just limited to using vertical menus in your game. I just used a vertical menu as an example because they are very user friendly and easy to make. You can change the shape of the menu items to anything you want, knowing that you can use transparent images or change the colors to match your game. Here's a screenshot of a character selection screen for a demo quiz game:

Character sprites came from the MMORPG Trickster.

Using the graphics methods to make the menus for your game let's you use your creativity and the design is only limited by your imagination. Just remember that however unlimited your imagination may seem to be, the phone on the other hand, has limited capabilities. ~John Constantime: "There's always a catch...damn cellphones!"

Wednesday, November 7, 2007 Using Custom Fonts or Bitmap Fonts in MIDP 2.0 Part I

Jump to part: 1 | 2

Have you ever wondered how those other games display scores and text with different styles and colors? You probably found out you can't do that with drawString() and setFont() alone. The truth is that those numbers and letters were drawn using images or what we call Bitmap Fonts.

Ok, so that's really old news. But I'll have you know that about a decade and a half ago, it was the only decent way to draw numbers and text on your game screen. I think that makes it sound even much older...moving on... Bitmap fonts also solves some portability issues in your game as different phones sometimes have different font sizes and can really screw up your design if your relying only on the drawString() method. Using bitmap fonts ensure that those scores and text will look exactly the same on your target phones. In this tutorial you will be shown how to use bitmap fonts with a little help from the setClip() method and type casting. If you need a jump starter on clipping, you can follow the tutorial on Clipping Images or Displaying Only Parts of an Image before you continue with this tutorial. We'll be using the new project templates which you can find here: Clean MIDP 2.0 Game Templates

The ASCII Table Like I mentioned earlier, images are used to draw those numbers and letters. Actually, it's an image strip where each frame contains one character. Here's an example of a bitmap font image:

Bitmap font sample zoomed in 2x. Frame size: 9x10 pixels. Click on the image to see what it actually looks like.

Since it's an image, you have a higher degree of freedom when it comes to customizing the way the font looks. Just remember that you can cause someones eyes to bleed and ultimately delete your game from their phones if you choose a repulsive set of colors and a barely readable font size. What

am I saying?!! The characters on the bitmap font image are arranged based on the standard ASCII table :

This makes it easy for us to determine the location of a character on the bitmap font image using this formula:

positionX = ((int)theCharacter) * frameWidth;

Type casting a char data type to an int gives you the ordinal value of the character, meaning it's Decimal value in the ASCII table.

You may have noticed that there are black and gray rectangles at the beginning of the image. I placed them there as markers for non-printable characters. Because the first 32 characters in the ASCII table are nonprintable, meaning they can't be drawn on the screen. Even the last character in the table, the Delete character, cannot be drawn. So only the characters whose Decimal value is in-between 31 and 127 can be drawn.

Creating the Bimap Font Writer : the clsFont Class You can now open the project templates you just downloaded in NetBeans. We will start by making a new class called "clsFont". A demo in how to create a new class can be found in part II of the tutorial Basic MIDP 2.0 Game Template : Creating the GameCanvas. When your done, you should now see something like this:

package MyGame; public class clsFont { /** Creates a new instance of clsFont */ public clsFont() { } }

Next, we'll declare some global variables under the class declaration:

public class clsFont { // additional space between characters public int charS = 0; // max clipping area public int screenW = 176; public int screenH = 208;

// flag: set to true to use the Graphics.drawString() method // this is just used as a fail-safe public boolean useDefault = false; // height of characters public int charH = 10; // lookup table for character widths public int[] charW = { // first 32 characters 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // space 9, // everything else :P 3, 5, 8, 8, 7, 8, 3, 5, 5, 6, 7, 3, 7, 3, 9, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 3, 3, 6, 6, 6, 6, 9, 6, 6, 6, 6, 6, 6, 6, 6, 3, 6, 6, 6, 9, 6, 6, 6, 6, 6, 6, 7, 6, 6, 9, 6, 6, 6, 5, 9, 5, 4, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 3, 4, 6, 3, 9, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9, 6, 6, 6, 5, 3, 5, 4, // delete character 9}; // the bitmap font image public Image imgFont;

Press ALT+Shift+F or choose "Fix Imports" from the "Source" menu so NetBeans can automagically add those missing import statements. The variable charS is used to define additional space between characters. You can change it if you feel that the characters are too close together and hard to read. It can also be useful if you want to do a spring effect animation and make the characters bounce sideways. The screenW and screenH defines the maximum screen area we can draw

on. They are used to make sure that the clipping rectangle always falls in the area defined. You will know more about that later. Next we have the useDefault variable. This is purely optional and we'll use that to signal if the image file failed to load. More on this later too. The charH variable stores the maximum height of the characters and is used to adjust the height of the clipping rectangle. The integer array charW[] holds the pre-calculated widths of each character. We use this to calculate the exact place where each character in a given string should be drawn and how much space each of them should occupy. This way, the string displayed will look more natural. It also helps save some screen space since the space used by each character is adjusted to only how much space is needed. Unlike monoblock style fonts where each character uses the same width even if the characters themselves only take up half as much space. One thing to note about the values stored in charW[] is that they already include adequate character spacing. But you can still use the charS variable to adjust the spacing if necessary. The last variable imgFont will hold the actual bitmap font image. We will now add the load() method for loading the image and the unload() method for cleaning up when we're done using the class. Add these lines after the class constructor:

/** Creates a new instance of clsFont */ public clsFont() { } public boolean load(String imagePath){ useDefault = false; try{ // load the bitmap font if (imgFont != null){ imgFont = null; }

imgFont = Image.createImage(imagePath); } catch (Exception ex){ // oohh we got an error then use the fail-safe useDefault = true; } return (!useDefault); } public void unload(){ // make sure the object get's destroyed imgFont = null; }

The load() method takes the string parameter imagePath which defines the path to the bitmap font image that we want to load. If it fails to do so, the useDefault variable will be set to true. You can check this variable in your game so you know if the bitmap font got loaded or not and act accordingly. The load() method also returns the inverse value of useDefault so you can use the method in your condition statement to load the bitmap font and at the same time check if it was loaded as in the example below:

if (!myFont.load("/images/fonts.png")){ /* ...do something to handle the error when the image fails to load... ... ... */ }

Next we will add the drawChar() method which will be used to draw a single character on the screen. Add it beneath the unload() method:

public void drawChar(Graphics g, int cIndex, int x, int y,

int w, int h){ // non printable characters don't need to be drawn if (cIndex < 33){ return; } // neither does the delete character if (cIndex > 126){ return; } // get the characters position int cx = cIndex * 9; // reset the clipping rectangle g.setClip(0, 0, screenW, screenH); // resize and reposition the clipping rectangle // to where the character must be drawn g.clipRect(x, y, w, h); // draw the character inside the clipping rectangle g.drawImage(imgFont, x - cx, y, Graphics.TOP | Graphics.LEFT); }

The drawChar() method takes a few parameters: Graphics g - the graphics object used to draw the image int cIndex - the ordinal value of the character to be drawn int x - the horizontal position of the character and clipping rectangle on the screen int y - the vertical position of the character and clipping rectangle on the screen int w - the width of the character and clipping rectangle int h - the height of the character and clipping rectangle

The drawChar() method checks to see if the character supposed to be drawn is a non-printable character by checking the value of cIndex. It then

computes the position of the character on the image and stores it in for later use. The method resets the clipping rectangle to fullscreen using the setClip() method and adjusts the clipping rectangle to exactly where the character should be drawn and matches it's size by using the clipRect() method. It then draws the character on the screen and inside the clipping rectangle. Using setClip() in conjunction with clipRect() is a good way of ensuring that the resulting clipping rectangle is within the phones screen boundaries so that the drawing methods will not draw anything outside the screen. Some phones behave erratically when you try draw stuff outside the screen and produce garbled output. Let's add the final method for the clsFont class, the drawString() method. This is the method we will call in our game to draw the strings, scores, and other text. Add it under the drawChar() method:

public void drawString(Graphics g, String sTxt, int x, int y){ // get the strings length int len = sTxt.length(); // set the starting position int cx = x; // if nothing to draw return if (len == 0) { return; } // our fail-safe if (useDefault){ g.drawString(sTxt, x, y, Graphics.TOP | Graphics.LEFT); return; } // loop through all the characters in the string for (int i = 0; i < len; i++){

// get current character char c = sTxt.charAt(i); // get ordinal value or ASCII equivalent int cIndex = (int)c; // lookup the width of the character int w = charW[cIndex]; // draw the character drawChar(g, cIndex, cx, y, w, charH); // go to the next drawing position cx += (w + charS); } }

The drawString() method parameters:


Graphics g - the Graphics object that will be used to draw the string String sTxt - the string to be drawn int x - the horizontal starting position of the string on the screen int y - the vertical position of the string on the screen

The drawString() method first gets the length of the string and the X position where the first character will be drawn. It then checks the length of the string to see if the string is not empty, otherwise it exits the method. Now you will get to see what the useDefault is used for. If it's set to true, meaning the bitmap font image wasn't loaded, the regular drawString() method of the Graphics object is used to draw the string instead of using the bitmap fonts. The drawString() method loops through each character of the string and gets the ordinal value of each character. The ordinal values are used to get each of the characters widths from the lookup table charW[]. It is also passed to the drawChar() method to define the width of the clipping

rectangle. After the current character has been drawn, the width is added to the current drawing position and the method proceeds with the next character. If you put a value other than 0 in charS, it will also be added to the current drawing position. Here's the completed clsFont source code so you can check your work:

package MyGame; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; public class clsFont { // additional space between characters public int charS = 0; // max clipping area public int screenW = 176; public int screenH = 208; // flag: set to true to use the Graphics.drawString() method // this is just used as a fail-safe public boolean useDefault = false; // height of characters public int charH = 10; // lookup table for character widths public int[] charW = { // first 32 characters 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // space 9, // everything else XD 3, 5, 8, 8, 7, 8, 3, 5, 5, 6, 7, 3, 7, 3, 9, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 3, 3, 6, 6, 6, 6, 9, 6, 6, 6, 6, 6, 6, 6, 6, 3, 6, 6, 6, 9, 6, 6, 6, 6, 6, 6, 7, 6, 6, 9, 6, 6, 6, 5, 9, 5, 4, 6, 4, 6, 6, 6, 6, 6, 6,

6, 6, 3, 4, 6, 3, 9, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9, 6, 6, 6, 5, 3, 5, 4, // delete 9}; // the bitmap font image private Image imgFont; /** Creates a new instance of clsFont */ public clsFont() { } public boolean load(String imagePath){ useDefault = false; try{ // load the bitmap font if (imgFont != null){ imgFont = null; } imgFont = Image.createImage(imagePath); } catch (Exception ex){ // oohh we got an error then use the fail-safe useDefault = true; } return (!useDefault); } public void unload(){ // make sure the object get's destroyed imgFont = null; } public void drawChar(Graphics g, int cIndex, int x, int y, int w, int h){ // non printable characters don't need to be drawn if (cIndex < 33){ return; } // neither does the delete character if (cIndex > 126){ return; } // get the characters position int cx = cIndex * 9;

// reset the clipping rectangle g.setClip(0, 0, screenW, screenH); // resize and reposition the clipping rectangle // to where the character must be drawn g.clipRect(x, y, w, h); // draw the character inside the clipping rectangle g.drawImage(imgFont, x - cx, y, Graphics.TOP | Graphics.LEFT); } public void drawString(Graphics g, String sTxt, int x, int y){ // get the strings length int len = sTxt.length(); // set the starting position int cx = x; // if nothing to draw return if (len == 0) { return; } // our fail-safe if (useDefault){ g.drawString(sTxt, x, y, Graphics.TOP | Graphics.LEFT); return; } // loop through all the characters in the string for (int i = 0; i < len; i++){ // get current character char c = sTxt.charAt(i); // get ordinal value or ASCII equivalent int cIndex = (int)c; // lookup the width of the character int w = charW[cIndex]; // draw the character drawChar(g, cIndex, cx, y, w, charH);

// go to the next drawing position cx += (w + charS); } } }

Press Shift+F11 or choose "Clean and Build Main Project" from the "Build" menu to test-build your project and to see if anything is missing. You can also choose to click on the toolbar icon with the broom glyph to clean and build your project. If everything went well, we can move on to the next step: Word-Wrap and More.

Using Custom Fonts or Bitmap Fonts Part II

Jump to part:

On the previous part of this tutorial, we created a clsFont class for drawing bitmap fonts. Now we will use the class to see if it actualy works. So if you haven't read the first part of this tutorial just click on this link: Using Custom Fonts or Bitmap Fonts Part I. I have also added more methods to the clsFont class that you may find useful so stick around a bit.

Using the Bitmap Font Writer Open the clsCanvas code so we can begin. We will start by adding a new global variable to hold an instance of the clsFont class. Let's call it "myFont" and add it under the other variable declarations like so:

private midMain fParent; private clsFont myFont;

/** Creates a new instance of clsCanvas */ public clsCanvas(midMain m) {

If you expand the images folder on the Projects panel, you should see the image file "fonts.png" listed there. This is the bitmap font we're going to pass to the clsFont.load() method. Add these lines in the end of the clsCanvas.load():

} myFont = new clsFont(); myFont.load("/images/fonts.png"); }

We will also call the clsFont.unload() method inside the clsCanvas.unload() method like so:

public void unload(){ // make sure the object get's destroyed myFont.unload(); myFont = null; }

Lastly, we will use the clsFont.drawString() method to draw some text on the screen. Add these lines above the call to flushGraphics() in the main loop:

g.fillRect(0, 0, screenW, screenH); myFont.drawString(g, "Hello Neo...", 10, 50);

myFont.drawString(g, "1234567890", 10, 70); myFont.drawString(g, "ABCDEFG abcdefg", 10, 90); flushGraphics();

Test your MIDlet by pressing F6 on your keyboard and you should see something like this:

I almost forgot to post the completed clsCanvas source code:

package MyGame; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; import javax.microedition.lcdui.game.GameCanvas; public class clsCanvas extends GameCanvas implements Runnable { // key repeat rate in milliseconds public static final int keyDelay = 250; //key constants public static final int upKey = 0; public static final int leftKey = 1;

public static final int downKey = 2; public static final int rightKey = 3; public static final int fireKey = 4; //key states for up, left, down, right, and fire key private boolean[] isDown = { false, false, false, false, false }; //last time the key changed state private long[] keyTick = { 0, 0, 0, 0, 0 }; //lookup table for key constants :P private int[] keyValue = { GameCanvas.UP_PRESSED, GameCanvas.LEFT_PRESSED, GameCanvas.DOWN_PRESSED, GameCanvas.RIGHT_PRESSED, GameCanvas.FIRE_PRESSED }; private boolean isRunning = true; private Graphics g; private midMain fParent; private clsFont myFont; /** Creates a new instance of clsCanvas */ public clsCanvas(midMain m) { super(true); fParent = m; setFullScreenMode(true); } public void start(){ Thread runner = new Thread(this); runner.start(); } public void load(){ try{ // load the images here }catch(Exception ex){ // exit the app if it fails to load the image isRunning = false; return;

} myFont = new clsFont(); myFont.load("/images/fonts.png"); } public void unload(){ // make sure the object gets destroyed myFont.unload(); myFont = null; } public void checkKeys(int iKey, long currTick){ long elapsedTick = 0; //loop through the keys for (int i = 0; i < 5; i++){ // by default, key not pressed by user isDown[i] = false; // is user pressing the key if ((iKey & keyValue[i]) != 0){ elapsedTick = currTick - keyTick[i]; //is it time to toggle key state? if (elapsedTick >= keyDelay){ // save the current time keyTick[i] = currTick; // toggle the state to down or pressed isDown[i] = true; } } } } public void run() { int iKey = 0; int screenW = getWidth(); int screenH = getHeight(); long lCurrTick = 0; // current system time in milliseconds; load(); g = getGraphics(); while(isRunning){ lCurrTick = System.currentTimeMillis();

iKey = getKeyStates(); checkKeys(iKey, lCurrTick); if (isDown[fireKey]){ isRunning = false; } //restore the clipping rectangle to full screen g.setClip(0, 0, screenW, screenH); //set drawing color to black g.setColor(0x000000); //fill the screen with blackness g.fillRect(0, 0, screenW, screenH); myFont.drawString(g, "Hello Neo...", 10, 50); myFont.drawString(g, "1234567890", 10, 70); myFont.drawString(g, "ABCDEFG abcdefg", 10, 90); flushGraphics(); try{ Thread.sleep(30); } catch (Exception ex){ } } g = null; unload(); fParent.destroyApp(false); fParent = null; } }

Things to Consider You could also consider adding method wrappers in the clsFont class to simplify drawing integer types like so:

public void drawInt(Graphics g, int num, int x, int y){ drawString(g, Integer.toString(num), x, y); }

public void drawLong(Graphics g, long num, int x, int y){ drawString(g, Long.toString(num), x, y); }

A variant of the drawString() that draws the string aligned to the right could easily be made like the following lines:

//draws string from right to left starting at x,y public void drawStringRev(Graphics g, String sTxt, int x, int y){ // get the strings length int len = sTxt.length(); // set the starting position int cx = x; // if nothing to draw return if (len == 0) { return; } // our fail-safe if (useDefault){ g.drawString(sTxt, x, y, Graphics.TOP | Graphics.RIGHT); return; } // loop through all the characters in the string for (int i = (len - 1); i >= 0; i--){ // get current character char c = sTxt.charAt(i); // get ordinal value or ASCII equivalent int cIndex = (int)c; // lookup the width of the character int w = charW[cIndex]; // go to the next drawing position cx -= (w + charS);

// draw the character drawChar(g, cIndex, cx, y, w, charH); } }

Then you can add more method wrappers like this:

public void drawIntRev(Graphics g, int num, int x, int y){ drawStringRev(g, Integer.toString(num), x, y); } public void drawLongRev(Graphics g, long num, int x, int y){ drawStringRev(g, Long.toString(num), x, y); }

Those additional methods could be used in drawing scores and numbers that are aligned to the right. Very useful if you want the score to grow leftwards like in score boards.

Bitmap Fonts With Word Wrap Here is an additional method that you can play with and use to emulate word wrapping when using bitmap fonts:

// space between lines in pixels public int lineS = 2; // draws words that wrap between x and x1 public void drawStringWrap(Graphics g, String s, int x, int y, int x1){ int len = s.length(); // current x int tx = x; // current y

int ty = y; /* word buffer contents width I just thought it would be faster than calling the String.length() method */ int ww = 0; // word buffer String sWord = ""; for (int i = 0; i < len; i++){ char c = s.charAt(i); int cIndex = (int)c; int cw = charW[cIndex]; if ((cIndex > 32) && (cIndex < 127)){ //if not a space and the character is printable //add the character to the buffer sWord += String.valueOf(c); //compute the length of the current word ww += cw; } else { //if space or non-printable character // check if there is a word in the buffer if (ww > 0) { //check if the word goes past the right margin if ((tx + ww) > x1){ // carrage return tx = x; // line feed ty += (charH + lineS); } // draw the contents of the word buffer drawString(g, sWord, tx, ty); } //move to the next position tx += (ww + cw); // clear the word buffer

sWord = ""; // word buffer width to zero ww = 0; } } // if there is a word remaining in the buffer then draw it if (ww > 0) { if ((tx + ww) > x1){ tx = x; ty += (charH + lineS); } drawString(g, sWord, tx, ty); } }

The drawStringWrap() method parameters:


Graphics g - the Graphics object used to draw the bitmapfont String s - the string to be drawn int x - the left margin and horizontal starting position int y - the vertical position where the first line is drawn int x1 - the right margin where the words will be wrapped

The drawStringWrap() method makes use of the drawString() method to draw each word. The method considers the space and non-printable characters as a word delimiter. You can change the line spacing by changing the value of lineS. To test it, add this line before the call to flushGraphics() in main loop in clsCanvas:

myFont.drawString(g, "ABCDEFG abcdefg", 10, 90); myFont.drawStringWrap(g, "blah blah blah blah blah blah " + "blah blah blah blah blah blah blah blah blah

" + "blah blah blah blah blah blah blah blah blah " + "blah blah blah blah blah blah blah blah blah " + "blah blah blah blah blah blah blah blah blah " + "blah ", 10, 100, 166); flushGraphics();

You should see this when you run it:

I hope the comments in the code will make the methods inner workings clear enough for you. If not, just post your questions in the comments. Here is the completed clsFont class with the extended methods:

package MyGame; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image;

public class clsFont { // additional space between characters public int charS = 0; // max clipping area public int screenW = 176; public int screenH = 208; // flag: set to true to use the Graphics.drawString() method // this is just used as a fail-safe public boolean useDefault = false; // height of characters public int charH = 10; // lookup table for character widths public int[] charW = { // first 32 characters 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // space 9, // everything else XD 3, 5, 8, 8, 7, 8, 3, 5, 5, 6, 7, 3, 7, 3, 9, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 3, 3, 6, 6, 6, 6, 9, 6, 6, 6, 6, 6, 6, 6, 6, 3, 6, 6, 6, 9, 6, 6, 6, 6, 6, 6, 7, 6, 6, 9, 6, 6, 6, 5, 9, 5, 4, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 3, 4, 6, 3, 9, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9, 6, 6, 6, 5, 3, 5, 4, // delete 9}; // the bitmap font image private Image imgFont; public clsFont() { } public boolean load(String imagePath){ useDefault = false;

try{ // load the bitmap font if (imgFont != null){ imgFont = null; } imgFont = Image.createImage(imagePath); } catch (Exception ex){ // oohh we got an error then use the fail-safe useDefault = true; } return (!useDefault); } public void unload(){ // make sure the object gets destroyed imgFont = null; } public void drawChar(Graphics g, int cIndex, int x, int y, int w, int h){ // non printable characters don't need to be drawn if (cIndex < 33){ return; } // neither does the delete character if (cIndex > 126){ return; } // get the characters position int cx = cIndex * 9; // reset the clipping rectangle g.setClip(0, 0, screenW, screenH); // resize and reposition the clipping rectangle // to where the character must be drawn g.clipRect(x, y, w, h); // draw the character inside the clipping rectangle g.drawImage(imgFont, x - cx, y, Graphics.TOP | Graphics.LEFT); } public void drawString(Graphics g, String sTxt, int x, int y){

// get the strings length int len = sTxt.length(); // set the starting position int cx = x; // if nothing to draw return if (len == 0) { return; } // our fail-safe if (useDefault){ g.drawString(sTxt, x, y, Graphics.TOP | Graphics.LEFT); return; } // loop through all the characters in the string for (int i = 0; i < len; i++){ // get current character char c = sTxt.charAt(i); // get ordinal value or ASCII equivalent int cIndex = (int)c; // lookup the width of the character int w = charW[cIndex]; // draw the character drawChar(g, cIndex, cx, y, w, charH); // go to the next drawing position cx += (w + charS); } } // extended methods *************************************** public void drawInt(Graphics g, int num, int x, int y){ drawString(g, Integer.toString(num), x, y); } public void drawLong(Graphics g, long num, int x, int y){ drawString(g, Long.toString(num), x, y); }

// Right align methods **************************************** //draws string from right to left starting at x,y public void drawStringRev(Graphics g, String sTxt, int x, int y){ // get the strings length int len = sTxt.length(); // set the starting position int cx = x; // if nothing to draw return if (len == 0) { return; } // our fail-safe if (useDefault){ g.drawString(sTxt, x, y, Graphics.TOP | Graphics.RIGHT); return; } // loop through all the characters in the string for (int i = (len - 1); i >= 0; i--){ // get current character char c = sTxt.charAt(i); // get ordinal value or ASCII equivalent int cIndex = (int)c; // lookup the width of the character int w = charW[cIndex]; // go to the next drawing position cx -= (w + charS); // draw the character drawChar(g, cIndex, cx, y, w, charH); } } public void drawIntRev(Graphics g, int num, int x, int y){

drawString(g, Integer.toString(num), x, y); } public void drawLongRev(Graphics g, long num, int x, int y){ drawString(g, Long.toString(num), x, y); } // Word wrap method **************************************** // space between lines in pixels public int lineS = 2; // draws words that wrap between x and x1 public void drawStringWrap(Graphics g, String s, int x, int y, int x1){ int len = s.length(); // current x int tx = x; // current y int ty = y; /* word buffer contents width I just thought it would be faster than calling the String.length() method */ int ww = 0; // word buffer String sWord = ""; for (int i = 0; i < len; i++){ char c = s.charAt(i); int cIndex = (int)c; int cw = charW[cIndex]; if ((cIndex > 32) && (cIndex < 127)){ //if not a space and the character is printable //add the character to the buffer sWord += String.valueOf(c); //compute the length of the current word ww += cw; } else {

//if space or non-printable character // check if there is a word in the buffer if (ww > 0) { //check if it goes past the right margin if ((tx + ww) > x1){ // carrage return tx = x; // line feed ty += (charH + lineS); } // draw the contents of the word buffer drawString(g, sWord, tx, ty); } //move to the next position tx += (ww + cw); // clear the word buffer sWord = ""; // word buffer width to zero ww = 0; } } // if there is a word remaining in the buffer then draw it if (ww > 0) { if ((tx + ww) > x1){ tx = x; ty += (charH + lineS); } drawString(g, sWord, tx, ty); } } }

There is a lot that could be done to make the clsFont class more functional. For instance, you can add justification or optimize the already existing

methods. You can also add another set of characters to the bitmap with a different style and modify the code so you can select which style to use. Since all the building blocks are in place, I think I'll just leave the missing parts to you.

Using the Sprite Class and Sprite Movement in MIDP 2.0

This short tutorial will show you how to use the built-in Sprite class and how to move it about on the screen. This may come a bit late but I just thought I should throw it in for variety. We will be using the project templates which you can get from this post: Clean Project Templates. Download and extract them to a folder of your choice. I have prepared a new image strip for us to play with. It's an 18x18 pixel white van drawn in 4 directions from frame 0 to frame 3, UP, DOWN, LEFT, and RIGHT.

White Van Image Frame Size:18x18 pixels I know the drawing is not politically correct so please bear with me on this one XD. Firefox users can right-click on the image and choose "Save Image As" from the context menu and IE users can choose "Save Picture As" instead. Save it inside the images folder, inside the src folder of the project template you downloaded. Fire up NetBeans to open your project and navigate your way to the clsCanvas source code. Let's add a couple of variables, one for the image and one for the sprite. Add these lines above the clsCanvas constructor:

private Image imgVan; private Sprite Van; /** Creates a new instance of clsCanvas */ public clsCanvas(midMain m) {

Now we'll load the image and initialize the Sprite object so modify the load() method like so:

public void load(){ try{ // load the images here imgVan = Image.createImage("/images/van.png"); }catch(Exception ex){ // exit the app if it fails to load the image isRunning = false; return; } // initialize the Sprite object Van = new Sprite(imgVan, 18, 18); // show the frame 1 - the second frame Van.setFrame(1); // move to 50, 50 (X, Y) Van.setPosition(50, 50); }

The Sprite() constructor we're using takes in 3 parameters:


1. Image image - The Image object that contains the frames/pictures 2. int frameWidth - the width of each frame in the image 3. int frameHeight - the height of each frame in the image

You must make sure that the width and height of the picture assigned to the Image object must be exactly divisible by the frameWidth and frameHeight that you defined. Otherwise, you will get an exception error at runtime. In this example, each frame of the image is 18 pixels wide and 18 pixels high. After initializing the sprite we use the setFrame() method of the Sprite object to set the current visible frame to 1, which is the second frame counting from left to right and starting with 0. We also set the initial position to 50, 50 (X, Y) on the screen. We will also add some code to the unload() method of clsCanvas to make sure the objects we made gets destroyed:

public void unload(){ // make sure the object gets destroyed Van = null; imgVan = null; }

The next line of code must be placed before the call to flushGraphics():

// draw the sprite Van.paint(g); flushGraphics();

To test the MIDlet, press F6 on your keyboard then press Enter when the emulator shows up. You should see something like this:

For our next trick, we're going to make the van move in all 4 directions: Up, Down, Left, Right. We'll begin by getting the current position of the sprite and storing them in two variables. Add these lines in the run() method just before the call to setClip():

// get the current position of the van int cx = Van.getX(); int cy = Van.getY(); //restore the clipping rectangle to full screen g.setClip(0, 0, screenW, screenH);

We will change the value of those variables depending on what keys are being pressed. Add the following lines beneath the lines you just added:

// get the current position of the van int cx = Van.getX(); int cy = Van.getY();

if ((iKey & GameCanvas.UP_PRESSED) != 0){ // show the van facing up Van.setFrame(0); // move the van upwards cy--; } else if ((iKey & GameCanvas.DOWN_PRESSED) != 0){ // show the van facing down Van.setFrame(1); // move the van downwards cy++; } else if ((iKey & GameCanvas.LEFT_PRESSED) != 0){ // show the van facing left Van.setFrame(2); // move the van to the left cx--; } else if ((iKey & GameCanvas.RIGHT_PRESSED) != 0){ // show the van facing right Van.setFrame(3); // move the van to the right cx++; } // update the vans position Van.setPosition(cx, cy); //restore the clipping rectangle to full screen g.setClip(0, 0, screenW, screenH);

The code above checks which key is being pressed and changes the current frame the sprite is showing to match the direction of it's movement. It then computes for the next location the sprite should move to and updates the sprites position. As a reference for beginners, here is a list of operations to do to move an object in different directions:

UP - decrease the value of the Y coordinate Down - increase the value of the Y coordinate Left - decrease the value of the X coordinate Right - increase the value of the X coordinate

If you run the project now, you should be able to control the van by pressing the arrow/directional keys. Here's the completed clsCanvas source code:

package MyGame; import import import import javax.microedition.lcdui.Graphics; javax.microedition.lcdui.Image; javax.microedition.lcdui.game.GameCanvas; javax.microedition.lcdui.game.Sprite;

public class clsCanvas extends GameCanvas implements Runnable { // key repeat rate in milliseconds public static final int keyDelay = 250; //key constants public static final public static final public static final public static final public static final int int int int int upKey = 0; leftKey = 1; downKey = 2; rightKey = 3; fireKey = 4;

//key states for up, left, down, right, and fire key private boolean[] isDown = { false, false, false, false, false }; //last time the key changed state private long[] keyTick = { 0, 0, 0, 0, 0 }; //lookup table for key constants :P private int[] keyValue = { GameCanvas.UP_PRESSED, GameCanvas.LEFT_PRESSED, GameCanvas.DOWN_PRESSED, GameCanvas.RIGHT_PRESSED, GameCanvas.FIRE_PRESSED }; private boolean isRunning = true; private Graphics g;

private midMain fParent; private Image imgVan; private Sprite Van; /** Creates a new instance of clsCanvas */ public clsCanvas(midMain m) { super(true); fParent = m; setFullScreenMode(true); } public void start(){ Thread runner = new Thread(this); runner.start(); } public void load(){ try{ // load the images here imgVan = Image.createImage("/images/van.png"); }catch(Exception ex){ // exit the app if it fails to load the image isRunning = false; return; } // initialize the Sprite object Van = new Sprite(imgVan, 18, 18); // show the frame 1 - the second frame Van.setFrame(1); // move to 50, 50 (X, Y) Van.setPosition(50, 50); } public void unload(){ // make sure the object gets destroyed Van = null; imgVan = null; } public void checkKeys(int iKey, long currTick){ long elapsedTick = 0; //loop through the keys for (int i = 0; i < 5; i++){

// by default, key not pressed by user isDown[i] = false; // is user pressing the key if ((iKey & keyValue[i]) != 0){ elapsedTick = currTick - keyTick[i]; //is it time to toggle key state? if (elapsedTick >= keyDelay){ // save the current time keyTick[i] = currTick; // toggle the state to down or pressed isDown[i] = true; } } } } public void run() { int iKey = 0; int screenW = getWidth(); int screenH = getHeight(); long lCurrTick = 0; // current system time in milliseconds; load(); g = getGraphics(); while(isRunning){ lCurrTick = System.currentTimeMillis(); iKey = getKeyStates(); checkKeys(iKey, lCurrTick); if (isDown[fireKey]){ isRunning = false; } // get the current position of the van int cx = Van.getX(); int cy = Van.getY(); if ((iKey & GameCanvas.UP_PRESSED) != 0){ // show the van facing up Van.setFrame(0); // move the van upwards cy--; } else if ((iKey & GameCanvas.DOWN_PRESSED) != 0){ // show the van facing down

Van.setFrame(1); // move the van downwards cy++; } else if ((iKey & GameCanvas.LEFT_PRESSED) != 0){ // show the van facing left Van.setFrame(2); // move the van to the left cx--; } else if ((iKey & GameCanvas.RIGHT_PRESSED) != 0){ // show the van facing right Van.setFrame(3); // move the van to the right cx++; } // update the vans position Van.setPosition(cx, cy); //restore the clipping rectangle to full screen g.setClip(0, 0, screenW, screenH); //set drawing color to black g.setColor(0x000000); //fill the screen with blackness g.fillRect(0, 0, screenW, screenH); // draw the sprite Van.paint(g); flushGraphics(); try{ Thread.sleep(30); } catch (Exception ex){ } } g = null; unload(); fParent.destroyApp(false); fParent = null; } }

The code presented in this tutorial also shows the basics of how to make a user controlled sprite or character. I hope it was of some help to you

Using the TiledLayer Class to Display Tile Maps

A tile map is the map/landscape drawn in the background that the character walks on. Tile maps are usually seen in games like Diablo, Fallout, Command & Conquer, StarCraft, Final Fantasy, Pokemon, most 2D RPGs/MMORPGs and strategy games. Here's an example of a game where a player controlled character and some monsters are standing on a tile map:

Screenshot of the game Herbarrio

It's called a tile map because the map is actually made out of smaller images called tiles. The tiles are drawn repeatedly and mix-matched with one another to form the whole map. The collection of different tiles is also called a tileset. The whole point of using a tile map is so the game doesn't have to load a huge image but instead loads a smaller image containing the tileset and recreates the map in the game. The tilesets are usually made so you can use them to make several different maps using the same tile images. This saves a us lot of memory and makes the game load faster.

In this tutorial, we're going to make use of the project created in a previous tutorial Using the Sprite Class and Sprite Movement. We're going to place a map for the van to run on and place obstacles that blocks the vans path. We're also going to take advantage of the TiledLayer class in MIDP 2.0 which was made specifically for drawing tile maps. The map we'll be making will take up the whole screen of a 176x208 pixel display. Here's an illustration to make you more confused:

The large green rectangle is the tile map and the dark green grid inside it represents the tiles that makes up the map. Tile map dimensions are usually measured by the number of tile columns and tile rows. In the example above, the tile map is 11x13 tiles, columns and rows respectivly, and the tiles themselves are 16x16 pixels. This makes a perfect fit for a 176x208 pixel display screen. Creating and Displaying the TiledLayer I prepared a tileset image for us to use in drawing the map. Firefox users can right-click on the image and choose "Save Image As" from the context menu, while IE users can choose "Save Picture As" instead. Save the image in the images folder, inside the src folder of the project.

Tileset Image Dimensions: 160x16 pixels Tile Size: 16x16 pixels Here's a larger view:

Tileset Image zoomed in at 2x

If you're done saving the image, you can now open the project in NetBeans and navigate your way to the clsCanvas code. We'll add some variables to hold our TiledLayer objects and the tileset image before the clsCanvas constructor:

private Image imgTileset; private TiledLayer roadMap; private TiledLayer blockMap; /** Creates a new instance of clsCanvas */ public clsCanvas(midMain m) {

You can press ALT+SHIFT+F now to add the missing export statements automatically. The imgTileset will be used to hold the tileset image you downloaded. The roadMap TiledLayer will be used to display the road which the van can run on. The blockMap TileLayer will be used to display the blocks/obstacles and will also be used to check for collision. Next, we'll add a loadRoadMap() method for initializing the roadMap TiledLayer. Add the following lines beneath the start() method:

public void loadRoadMap(){ //initialize the roadMap roadMap = new TiledLayer(11, 13, imgTileset, 16, 16); // Create a new Random for randomizing numbers Random Rand = new Random(); //loop through all the map cells for (int y = 0; y < 13; y++){ for (int x = 0; x < 11; x++){ // get a random tile index between 2 and 5 int index = (Math.abs(Rand.nextInt()>>>1) % (3)) + 2; // set the tile index for the current cell roadMap.setCell(x, y, index); } } // mark Rand for clean up Rand = null; }

Again, you can press ALT+SHIFT+F now to add the missing export statements automatically. The loadRoadMap() method creates a new TiledLayer object and defines the number of columns, number of rows, the tileset image to be used, the tile width, and the tile height. It then assigns a random tile index from 2-5 to each cell of the map using the setCell() method. Take note that the tile index of the tiles starts at 1 instead of 0. You can also leave a cell empty by assigning 0 to the cell, meaning that cell will not be drawn. We'll do the same for the blockMap TiledLayer and add a loadBlockMap() method. Insert the following lines beneath the loadRoadMap() method:

public void loadBlockMap(){ // define the tile indexes byte[][] blockData = { {10, 8 , 7 , 6 , 10, 9 {6 , 0 , 0 , 0 , 0 , 0 {7 , 0 , 0 , 0 , 0 , 0 {8 , 0 , 0 , 10, 6 , 0 {9 , 0 , 0 , 0 , 0 , 0 {10, 0 , 0 , 0 , 0 , 0 {6 , 0 , 0 , 8 , 0 , 0 {7 , 0 , 0 , 7 , 0 , 0 {8 , 0 , 0 , 6 , 0 , 0 {9 , 0 , 0 , 10, 0 , 0 {10, 0 , 0 , 0 , 0 , 0 {6 , 0 , 0 , 0 , 0 , 0 {7 , 8 , 9 , 10, 6 , 7 };

to be used for each map cell , , , , , , , , , , , , , 8 0 0 0 0 0 0 0 0 7 0 0 8 , , , , , , , , , , , , , 7 , 0 , 0 , 7 , 8 , 9 , 0 , 0 , 10, 6 , 0 , 0 , 9 , 6 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 10, 10, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 6 , 9 }, 8 }, 7 }, 6 }, 10}, 9 }, 8 }, 7 }, 6 }, 10}, 9 }, 8 }, 7 }

//initialize blockMap blockMap = new TiledLayer(11, 13, imgTileset, 16, 16); //loop through all the map cells for (int y = 0; y < 13; y++){ for (int x = 0; x < 11; x++){ // set the tile index for the current cell // take note of the reversed indexes for blockData blockMap.setCell(x, y, blockData[y][x]); } } blockData = null; }

The loadBlockMap() method does pretty much the same thing as what the loadRoadMap() method does. Except this time, the tile index for each cell is predefined in a byte array before being assigned to each map cell. Now, we'll modify the load() method so it will load the tileset image and call the methods we previously added for initializing the TiledLayer objects. We'll also change the initial position of the van so that it's not already hitting a block when the game starts. So modify the load() method like this:

public void load(){ try{ // load the images here imgVan = Image.createImage("/images/van.png"); // load the tileset imgTileset = Image.createImage("/images/tileset1.png"); }catch(Exception ex){ // exit the app if it fails to load the image isRunning = false; return; } // initialize the Sprite object Van = new Sprite(imgVan, 18, 18); // show the frame 1 - the second frame Van.setFrame(1); // move to 16, 16 (X, Y) Van.setPosition(16, 16); //initialize the TiledLayers loadRoadMap(); loadBlockMap(); }

Let's also add some clean up code to the unload() method:

public void unload(){ // make sure the object gets destroyed blockMap = null; roadMap = null; Van = null; imgTileset = null; imgVan = null; }

We can now draw the maps on the screen. Add these lines inside the run() method before the van is drawn:

//draw the road roadMap.paint(g); //draw the blocks blockMap.paint(g); // draw the sprite Van.paint(g); flushGraphics();

Collision Detection If you run the project now, you will notice that the van doesn't really stop when you hit a block. The van just runs over them. Lucky for us, the TiledLayer and Sprite objects already have methods for collision detection. First, let's make it so it's easier to change the speed of the van. Add the global variable vanSpeed under the Van Sprite declaration:

private Sprite Van; private int vanSpeed = 2; private Image imgTileset;

Then, modify the run() method to make use of the vanSpeed variable like so:

if ((iKey & GameCanvas.UP_PRESSED) != 0){ // show the van facing up Van.setFrame(0); // move the van upwards cy -= vanSpeed; } else if ((iKey & GameCanvas.DOWN_PRESSED) != 0){ // show the van facing down Van.setFrame(1); // move the van downwards cy += vanSpeed; } else if ((iKey & GameCanvas.LEFT_PRESSED) != 0){ // show the van facing left Van.setFrame(2); // move the van to the left cx -= vanSpeed; } else if ((iKey & GameCanvas.RIGHT_PRESSED) != 0){ // show the van facing right Van.setFrame(3); // move the van to the right cx += vanSpeed; }

Now for the collision detection part. We'll first store the current position of the van in new variables, tx and ty, so we can reset the vans position to where it was before it collided with a block:

// get the current position of the van int cx = Van.getX(); int cy = Van.getY(); // save the current position in temporary vars // so we can restore it when it hits a block int tx = cx; int ty = cy; if ((iKey & GameCanvas.UP_PRESSED) != 0){

The following lines of code checks if the van hits a block and resets the vans position to where it was before it hit the block. Add the code after the vans position has been updated:

// update the vans position Van.setPosition(cx, cy); //check if the van hits a block if (Van.collidesWith(blockMap, true)){ //reset the van to the original position Van.setPosition(tx, ty); } //restore the clipping rectangle to full screen g.setClip(0, 0, screenW, screenH);

The Sprite.collidesWith() method is used to check if the van collides with any part of the blockMap TiledLayer. The second parameter we passed, the boolean value "true", indicates that it should check for collision at the "pixel level". This means that the collidesWith() method will only return true if the non-transparent parts of the vans image overlaps with nontransparent parts of the blockMap TiledLayer. You can also use the collidesWith() method to check for collision between Sprite objects. Btw, since the map takes up the whole screen, it would probably be better to comment out or remove the code for filling the screen with blackness from the run() method:

//restore the clipping rectangle to full screen g.setClip(0, 0, screenW, screenH); /* comment out or remove this code set drawing color to black g.setColor(0x000000);

fill the screen with blackness g.fillRect(0, 0, screenW, screenH); */ //draw the road roadMap.paint(g);

When you run the project, you should see something like this:

Here's the completed clsCanvas code so you can check your work:

package MyGame; import import import import import import java.util.Random; javax.microedition.lcdui.Graphics; javax.microedition.lcdui.Image; javax.microedition.lcdui.game.GameCanvas; javax.microedition.lcdui.game.Sprite; javax.microedition.lcdui.game.TiledLayer;

public class clsCanvas extends GameCanvas implements Runnable { // key repeat rate in milliseconds public static final int keyDelay = 250; //key constants public static final public static final public static final public static final public static final int int int int int upKey = 0; leftKey = 1; downKey = 2; rightKey = 3; fireKey = 4;

//key states for up, left, down, right, and fire key private boolean[] isDown = { false, false, false, false, false }; //last time the key changed state private long[] keyTick = { 0, 0, 0, 0, 0 }; //lookup table for key constants :P private int[] keyValue = { GameCanvas.UP_PRESSED, GameCanvas.LEFT_PRESSED, GameCanvas.DOWN_PRESSED, GameCanvas.RIGHT_PRESSED, GameCanvas.FIRE_PRESSED }; private boolean isRunning = true; private Graphics g; private midMain fParent; private Image imgVan; private Sprite Van; private int vanSpeed = 2; private Image imgTileset; private TiledLayer roadMap; private TiledLayer blockMap; public clsCanvas(midMain m) { super(true); fParent = m; setFullScreenMode(true);

} public void start(){ Thread runner = new Thread(this); runner.start(); } public void loadRoadMap(){ //initialize the roadMap roadMap = new TiledLayer(11, 13, imgTileset, 16, 16); // Create a new Random for randomizing numbers Random Rand = new Random(); //loop through all the map cells for (int y = 0; y < 13; y++){ for (int x = 0; x < 11; x++){ // get a random tile index between 2 and 5 int index = (Math.abs(Rand.nextInt()>>>1) % (3)) + 2; // set the tile index for the current cell roadMap.setCell(x, y, index); } } Rand = null; } public void loadBlockMap(){ // define the tile indexes byte[][] blockData = { {10, 8 , 7 , 6 , 10, 9 {6 , 0 , 0 , 0 , 0 , 0 {7 , 0 , 0 , 0 , 0 , 0 {8 , 0 , 0 , 10, 6 , 0 {9 , 0 , 0 , 0 , 0 , 0 {10, 0 , 0 , 0 , 0 , 0 {6 , 0 , 0 , 8 , 0 , 0 {7 , 0 , 0 , 7 , 0 , 0 {8 , 0 , 0 , 6 , 0 , 0 {9 , 0 , 0 , 10, 0 , 0 {10, 0 , 0 , 0 , 0 , 0 {6 , 0 , 0 , 0 , 0 , 0 {7 , 8 , 9 , 10, 6 , 7 }; to be used for each map cell , , , , , , , , , , , , , 8 0 0 0 0 0 0 0 0 7 0 0 8 , , , , , , , , , , , , , 7 , 0 , 0 , 7 , 8 , 9 , 0 , 0 , 10, 6 , 0 , 0 , 9 , 6 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 10, 10, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 6 , 9 }, 8 }, 7 }, 6 }, 10}, 9 }, 8 }, 7 }, 6 }, 10}, 9 }, 8 }, 7 }

//initialize blockMap blockMap = new TiledLayer(11, 13, imgTileset, 16, 16); //loop through all the map cells for (int y = 0; y < 13; y++){ for (int x = 0; x < 11; x++){ // set the tile index for the current cell // take note of the reversed indexes for blockData blockMap.setCell(x, y, blockData[y][x]); } } blockData = null; } public void load(){ try{ // load the images here imgVan = Image.createImage("/images/van.png"); imgTileset = Image.createImage("/images/tileset1.png"); }catch(Exception ex){ // exit the app if it fails to load the image isRunning = false; return; } // initialize the Sprite object Van = new Sprite(imgVan, 18, 18); // show the frame 1 - the second frame Van.setFrame(1); // move to 16, 16 (X, Y) Van.setPosition(16, 16); loadRoadMap(); loadBlockMap(); } public void unload(){ // make sure the object gets destroyed blockMap = null; roadMap = null;

Van = null; imgTileset = null; imgVan = null; } public void checkKeys(int iKey, long currTick){ long elapsedTick = 0; //loop through the keys for (int i = 0; i < 5; i++){ // by default, key not pressed by user isDown[i] = false; // is user pressing the key if ((iKey & keyValue[i]) != 0){ elapsedTick = currTick - keyTick[i]; //is it time to toggle key state? if (elapsedTick >= keyDelay){ // save the current time keyTick[i] = currTick; // toggle the state to down or pressed isDown[i] = true; } } } } public void run() { int iKey = 0; int screenW = getWidth(); int screenH = getHeight(); long lCurrTick = 0; // current system time in milliseconds; load(); g = getGraphics(); while(isRunning){ lCurrTick = System.currentTimeMillis(); iKey = getKeyStates(); checkKeys(iKey, lCurrTick); if (isDown[fireKey]){ isRunning = false; } // get the current position of the van int cx = Van.getX();

int cy = Van.getY(); // save the current position in temporary vars // so we can restore it when we hit a block int tx = cx; int ty = cy; if ((iKey & GameCanvas.UP_PRESSED) != 0){ // show the van facing up Van.setFrame(0); // move the van upwards cy -= vanSpeed; } else if ((iKey & GameCanvas.DOWN_PRESSED) != 0){ // show the van facing down Van.setFrame(1); // move the van downwards cy += vanSpeed; } else if ((iKey & GameCanvas.LEFT_PRESSED) != 0){ // show the van facing left Van.setFrame(2); // move the van to the left cx -= vanSpeed; } else if ((iKey & GameCanvas.RIGHT_PRESSED) != 0){ // show the van facing right Van.setFrame(3); // move the van to the right cx += vanSpeed; } // update the vans position Van.setPosition(cx, cy); //check if the van hits a block if (Van.collidesWith(blockMap, true)){ //reset the van to the original position Van.setPosition(tx, ty); } //restore the clipping rectangle to full screen g.setClip(0, 0, screenW, screenH); /* comment out or remove this code

set drawing color to black g.setColor(0x000000); fill the screen with blackness g.fillRect(0, 0, screenW, screenH); */ //draw the road roadMap.paint(g); //draw the blocks blockMap.paint(g); // draw the sprite Van.paint(g); flushGraphics(); try{ Thread.sleep(30); } catch (Exception ex){ } } g = null; unload(); fParent.destroyApp(false); fParent = null; } }

The sample code presented here was made to show a very simple way to use the TiledLayer class, how to draw a Tile Map and check for collision. It also shows a way to randomize numbers by using the Random class. Finally, you can find a lot of information about tile maps on the internet. Although most of them aren't related to MIDP 2.0 or Java, the concept is still the same and can be useful to your game project.

Loading Tile Map Data From a File

In case you haven't seen it yet, there's a push-puzzle demo game in the "Various Games"/"Various Games for MIDP 2.0" sample mobile project included with The NetBeans Mobility Pack. The push-puzzle game demo also shows how to load map data from text files. But the one thing I remember most about that sample game is that the code confused me a lot, heh. Here's my attempt to make things a bit easier for you so you won't have to go through all that code jumping. But instead of loading map data from a text file, we're going to pull the map data from a binary file. In this tutorial, we're going to make use of the project code from the tutorial Using the TiledLayer Class to Display Tile Maps. So head over there first to get the code and to have a better understanding of what's going on. That tutorial displays a user controlled sprite on a map with obstacles. We're going to modify the code so that the obstacle layer data is loaded from a map file. You can get the map file here in SMP format: samplemap.smp (155 bytes) - Download Link Create a new folder called "maps" in the "src" folder of the project and save the map file there. You can view or edit the contents of the map with the map editor which you can get here: Simple Tile Map Editor Info. and Download Page. The link includes instructions and some more info. To preview the map in the map editor, Click on the "Open" icon and choose the "samplemap.smp" file you saved earlier. In the next dialog, select the "tileset1.png" image file inside the "images" folder within the "src" folder of the project. You should see something like this:

When you're ready, open the project in NetBeans and navigate your way to the clsCanvas code. Add the following code below the loadBlockMap() method:

public TiledLayer getMap(String fpath, Image ftiles){ TiledLayer tMap = null; try { // open the file InputStream is = this.getClass().getResourceAsStream(fpath); DataInputStream ds = new DataInputStream(is); try { // skip the descriptor ds.skipBytes(8); // read map width int mW = ds.readByte(); // read map height int mH = ds.readByte(); // read tile width int tW = ds.readByte();

// read tile height int tH = ds.readByte(); // create a new tiled layer tMap = new TiledLayer(mW, mH, ftiles, tW, tH); // loop through the map data for (int rCtr = 0; rCtr < mH; rCtr++){ for (int cCtr = 0; cCtr < mW; cCtr++){ // read a tile index byte nB = ds.readByte(); // if tile index is non-zero // tile index 0 is usually a blank tile if (nB > 0) { // assign (tile index + 1) to the current cell // TiledLayer objects start tile index at 1 // instead of 0 tMap.setCell(cCtr, rCtr, nB + 1); } } } } catch (Exception ex) { tMap = null; System.err.println("map loading error : " + ex.getMessage()); } // close the file ds.close(); ds = null; is = null; } catch (Exception ex) { tMap = null; System.err.println("map loading error : " + ex.getMessage()); } // return the newly created map or null if loading failed return tMap; }

Make sure to press ALT+Shift+F so NetBeans can add the missing import statements. The code we just added is the same getMap() method you will find the Simple Tile Map Editor page. We will use it here to serve as an example of it's usage. We'll make a new method called loadBlockMapFile() that uses the getMap() method to load the map data and initialize the blockMap TiledLayer. This will let you preserve the previous loadBlockMap() method code for future reference. Add the following code below the loadBlockMap() method:

public void loadBlockMapFile(){ //initialize blockMap blockMap = getMap("/maps/samplemap.smp", imgTileset); }

We now have to replace the call to loadBlockMap() inside the load() method to call loadBlockMapFile() instead:

loadRoadMap(); loadBlockMapFile(); }

You should see something like this when you run the project:

There's not much difference between the output of the previous tutorial and this one except for the position of a few blocks and their colors. Here's the completed clsCanvas source code so you can check your work:

package MyGame; import import import import import import import import java.io.DataInputStream; java.io.InputStream; java.util.Random; javax.microedition.lcdui.Graphics; javax.microedition.lcdui.Image; javax.microedition.lcdui.game.GameCanvas; javax.microedition.lcdui.game.Sprite; javax.microedition.lcdui.game.TiledLayer;

public class clsCanvas extends GameCanvas implements Runnable { // key repeat rate in milliseconds public static final int keyDelay = 250; //key constants public static final int upKey = 0;

public public public public

static static static static

final final final final

int int int int

leftKey = 1; downKey = 2; rightKey = 3; fireKey = 4;

//key states for up, left, down, right, and fire key private boolean[] isDown = { false, false, false, false, false }; //last time the key changed state private long[] keyTick = { 0, 0, 0, 0, 0 }; //lookup table for key constants :P private int[] keyValue = { GameCanvas.UP_PRESSED, GameCanvas.LEFT_PRESSED, GameCanvas.DOWN_PRESSED, GameCanvas.RIGHT_PRESSED, GameCanvas.FIRE_PRESSED }; private boolean isRunning = true; private Graphics g; private midMain fParent; private Image imgVan; private Sprite Van; private int vanSpeed = 2; private Image imgTileset; private TiledLayer roadMap; private TiledLayer blockMap; /** Creates a new instance of clsCanvas */ public clsCanvas(midMain m) { super(true); fParent = m; setFullScreenMode(true); } public void start(){ Thread runner = new Thread(this); runner.start(); } public void loadRoadMap(){ //initialize the roadMap

roadMap = new TiledLayer(11, 13, imgTileset, 16, 16); // Create a new Random for randomizing numbers Random Rand = new Random(); //loop through all the map cells for (int y = 0; y < 13; y++){ for (int x = 0; x < 11; x++){ // get a random tile index between 2 and 5 int index = (Math.abs(Rand.nextInt()>>>1) % (3)) + 2; // set the tile index for the current cell roadMap.setCell(x, y, index); } } Rand = null; } public void loadBlockMap(){ // define the tile indexes byte[][] blockData = { {10, 8 , 7 , 6 , 10, 9 {6 , 0 , 0 , 0 , 0 , 0 {7 , 0 , 0 , 0 , 0 , 0 {8 , 0 , 0 , 10, 6 , 0 {9 , 0 , 0 , 0 , 0 , 0 {10, 0 , 0 , 0 , 0 , 0 {6 , 0 , 0 , 8 , 0 , 0 {7 , 0 , 0 , 7 , 0 , 0 {8 , 0 , 0 , 6 , 0 , 0 {9 , 0 , 0 , 10, 0 , 0 {10, 0 , 0 , 0 , 0 , 0 {6 , 0 , 0 , 0 , 0 , 0 {7 , 8 , 9 , 10, 6 , 7 }; to be used for each map cell , , , , , , , , , , , , , 8 0 0 0 0 0 0 0 0 7 0 0 8 , , , , , , , , , , , , , 7 , 0 , 0 , 7 , 8 , 9 , 0 , 0 , 10, 6 , 0 , 0 , 9 , 6 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 10, 10, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 6 , 9 }, 8 }, 7 }, 6 }, 10}, 9 }, 8 }, 7 }, 6 }, 10}, 9 }, 8 }, 7 }

//initialize blockMap blockMap = new TiledLayer(11, 13, imgTileset, 16, 16); //loop through all the map cells for (int y = 0; y < 13; y++){ for (int x = 0; x < 11; x++){ // set the tile index for the current cell // take note of the reversed indexes for blockData

blockMap.setCell(x, y, blockData[y][x]); } } blockData = null; } public void loadBlockMapFile(){ //initialize blockMap from a binary file blockMap = getMap("/maps/samplemap.smp", imgTileset); } public TiledLayer getMap(String fpath, Image ftiles){ TiledLayer tMap = null; try { // open the file InputStream is = this.getClass().getResourceAsStream(fpath); DataInputStream ds = new DataInputStream(is); try { // skip the descriptor ds.skipBytes(8); // read map width int mW = ds.readByte(); // read map height int mH = ds.readByte(); // read tile width int tW = ds.readByte(); // read tile height int tH = ds.readByte(); // create a new tiled layer tMap = new TiledLayer(mW, mH, ftiles, tW, tH); // loop through the map data for (int rCtr = 0; rCtr < mH; rCtr++){ for (int cCtr = 0; cCtr < mW; cCtr++){ // read a tile index byte nB = ds.readByte(); // if tile index is non-zero // tile index 0 is usually a blank tile if (nB > 0) {

//assign (tile index + 1) to the current cell tMap.setCell(cCtr, rCtr, nB + 1); } } } } catch (Exception ex) { tMap = null; System.err.println("map loading error : " + ex.getMessage()); } // close the file ds.close(); ds = null; is = null; } catch (Exception ex) { tMap = null; System.err.println("map loading error : " + ex.getMessage()); } // return the newly created map or null if loading failed return tMap; } public void load(){ try{ // load the images here imgVan = Image.createImage("/images/van.png"); imgTileset = Image.createImage("/images/tileset1.png"); }catch(Exception ex){ // exit the app if it fails to load the image isRunning = false; return; } // initialize the Sprite object Van = new Sprite(imgVan, 18, 18); // show the frame 1 - the second frame Van.setFrame(1); // move to 50, 50 (X, Y) Van.setPosition(16, 16);

loadRoadMap(); loadBlockMapFile(); } public void unload(){ // make sure the object gets destroyed blockMap = null; roadMap = null; Van = null; imgTileset = null; imgVan = null; } public void checkKeys(int iKey, long currTick){ long elapsedTick = 0; //loop through the keys for (int i = 0; i < 5; i++){ // by default, key not pressed by user isDown[i] = false; // is user pressing the key if ((iKey & keyValue[i]) != 0){ elapsedTick = currTick - keyTick[i]; //is it time to toggle key state? if (elapsedTick >= keyDelay){ // save the current time keyTick[i] = currTick; // toggle the state to down or pressed isDown[i] = true; } } } } public void run() { int iKey = 0; int screenW = getWidth(); int screenH = getHeight(); long lCurrTick = 0; // current system time in milliseconds; load(); g = getGraphics(); while(isRunning){ lCurrTick = System.currentTimeMillis();

iKey = getKeyStates(); checkKeys(iKey, lCurrTick); if (isDown[fireKey]){ isRunning = false; } // get the current position of the van int cx = Van.getX(); int cy = Van.getY(); // save the current position in temporary vars // so we can restore it when we hit a block int tx = cx; int ty = cy; if ((iKey & GameCanvas.UP_PRESSED) != 0){ // show the van facing up Van.setFrame(0); // move the van upwards cy -= vanSpeed; } else if ((iKey & GameCanvas.DOWN_PRESSED) != 0){ // show the van facing down Van.setFrame(1); // move the van downwards cy += vanSpeed; } else if ((iKey & GameCanvas.LEFT_PRESSED) != 0){ // show the van facing left Van.setFrame(2); // move the van to the left cx -= vanSpeed; } else if ((iKey & GameCanvas.RIGHT_PRESSED) != 0){ // show the van facing right Van.setFrame(3); // move the van to the right cx += vanSpeed; } // update the vans position Van.setPosition(cx, cy); //check if the van hits a block

if (Van.collidesWith(blockMap, true)){ //reset the van to the original position Van.setPosition(tx, ty); } //restore the clipping rectangle to full screen g.setClip(0, 0, screenW, screenH); /* comment out or remove this code set drawing color to black g.setColor(0x000000); fill the screen with blackness g.fillRect(0, 0, screenW, screenH); */ //draw the road roadMap.paint(g); //draw the blocks blockMap.paint(g); // draw the sprite Van.paint(g); flushGraphics(); try{ Thread.sleep(30); } catch (Exception ex){ } } g = null; unload(); fParent.destroyApp(false); fParent = null; } }

The way you define the map data depends entirely on you. I chose to use a binary file because of it's size and extracting the data is pretty straight

forward. No need for custom parsers and such. You can also find a bunch of tile map editors online with more powerful features like saving the map data to an XML file and more. Links to some 2d Map Editors:

Tiled - Link Tile Studio - Link Mappy - Link Tat Tile Map Editor - Link

Finally, you can also have a look at the new Game Builder that comes with the latest NetBeans 6 and Mobility bundle. Here's a preview from a tutorial in the NetBeans Community Docs: Using Game Builder for Java ME development. Good luck and have fun!

Handling Text Input or Accepting Character/Player Names

Your game would be more interesting if players can enter their name. It would make them feel like they really are part of the game. For instance, you can make certain dialog text address the player's name so it would seem like the game is "talking" to the player. It would also allow the player to associate his name with his score as proof of his victory and bragging rights. What's the fuzz all about? You can always use a TextBox, right? No, my young padawan. Well, you could probably get away with it. But there's a couple of reasons why you should avoid using a TextBox in a fullscreen game. In most phones, you're game is hidden while the TextBox is displayed. It is usually drawn using the phones U.I. theme and would not look like it's

part of the game. Once again, it's all about aesthetics. We will be using the code from the tutorial Using Custom Fonts or Bitmap Fonts to display the characters on the screen. So head over there if you haven't already. If you prefer, you can still use the normal Graphics.drawChar() method in place of the clsFont.drawChar() method. But you at least need to have the project code which you can download from : Clean MIDP 2.0 Game Templates.

Lack of Buttons In this tutorial, we're going to mimic the text input used in arcade consoles of old. Those coin/token-eating machines only had a 4 way joystick, 6 function buttons, a start button, and a reset button. But these were sufficient enough to let the player input names or initials for the scoreboard. While a typical mobile phone has more buttons for us to play with, not all of them map to the same key codes on different phones. Fortunately, the GameCanvas class gives us a few standard keys which more or less works on most phones. Standard Keys:

8 2 4 6 5

key key key key key

or or or or or

UP Button DOWN Button LEFT Button RIGHT Button FIRE Button

We're going to assign functions to these keys as follows: LEFT - select the character to the left of the current selected character in the string.

RIGHT - select the character to the right of the current selected character in the string. UP - Scroll through the characters upwards. DOWN - Scroll through the characters downwards. FIRE - commit changes

The Variables Let's begin by opening the project from NetBeans and navigate your way to the clsCanvas code. Add the following global variables before the clsCanvas constructor:

private clsFont myFont; // this will hold the resulting text private String prText = "AAAAAA"; // the currently selected character private int prSelected = 0; // width and height of the characters private int prWidth = 9; private int prHeight = 10; // character spacing - includes width of character private int prSpacing = 12; // vars private private private for timing the long prStart = long prDelay = boolean prShow blinking cursor 0; 100; = true;

/** Creates a new instance of clsCanvas */ public clsCanvas(midMain m) {

The String prText will contain the text entered by the player and at the same time provide feed back of the letters the player has chosen. The length of the string assign to prText will also determine the maximum length of the text the player can enter and will also be the first characters displayed on

the screen. The integer prSelected will hold the index of the currently selected character from the string assigned to prText. The variables prWidth and prHeight holds the width and height of a character. These variables will be passed to the clsFont.drawChar() method for resizing the clipping rectangle. The integer prSpacing defines the interval or space in pixels between each characters drawing position (X coordinate). The value assigned to prSpacing should be the sum of the width of a character and the space between each character.

Replacing a Single Character Next, we'll add a new method called replaceCharAt() that will let us change a single character in a given string. Add the new method above the run() method:

public String replaceCharAt(String s, int pos, char c) { if (pos < (s.length() - 1)){ return s.substring(0, pos) + c + s.substring(pos + 1); } else { return s.substring(0, pos) + c; } } public void run() {

The replaceCharAt() method requires 3 parameters:


String s - the original string int pos - the position of the character to be replaced Char c - the replacement character

User Controls We also need a new method for detecting/responding to key presses and toggle the cursors visibility when needed. Add the updatePompt() method above the run() method:

public void updatePrompt(long currTick){ // get text length int len = prText.length(); // get current selected characters ASCII value int prOrd = (int)prText.charAt(prSelected); // is it time to change the cursors visibility if ((currTick - prStart) >= prDelay){ // update starting time prStart = currTick; // toggle cursor visibility prShow = !prShow; } if (isDown[leftKey]){ // if not first character if (prSelected > 0){ // select previous character in string prSelected--; } } else if (isDown[rightKey]){ // if not last character if (prSelected < (len - 1)){ // select next character in string prSelected++; } } else if (isDown[upKey]){ if (prOrd == 97){ // small leter a prOrd = 90; // jump to capital letter Z } else if (prOrd == 65){ // capital letter A prOrd = 32; // jump to space character } else if (prOrd == 32){ // space character prOrd = 122; // jump to small leter z } else if ((prOrd > 97) || (prOrd > 65)){ prOrd--; // previous character } // replace the selected character with the new

character prText = replaceCharAt(prText, prSelected, (char)prOrd); } else if (isDown[downKey]){ if (prOrd == 32){ // space character prOrd = 65; // jump to capilat letter A } else if (prOrd == 90){ // capital letter Z prOrd = 97; // jump to small letter a } else if (prOrd == 122){ // small letter z prOrd = 32; // jump to space character } else if ((prOrd < 90) || (prOrd < 122)){ prOrd++; // next character } // replace the selected character with the new character prText = replaceCharAt(prText, prSelected, (char)prOrd); } } public void run() {

The updatePrompt() method takes a single Long parameter currTick which is the current time in milliseconds. The value from the parameter is just used to check if the time that passed between prStart and the currTick is greater than or equal to the delay defined in prDelay. If so, the boolean prShow which controls if the cursor is show or hidden is toggled between true and false. Notice that the updatePrompt() method allows you to select capital letters, small letters, and the space character. It wouldn't be too hard to add numbers there too or even the entire displayable character set. But you would usually only use capital letters. You might want to keep the space character though, because the player can use it as placeholders for unused spaces if the length of your prompt is too long. This way you can trim the spaces from the value of prText when the player is finished entering his name.

Displaying the Text Finally, we'll add a method for drawing the text and the cursor. Again, add the drawPrompt() method above the run() method:

public void drawPrompt(Graphics g, int x, int y){ // get length of the string int len = prText.length(); // loop through the characters for (int i = 0; i < len; i++){ // get ASCII value int cIndex = (int)prText.charAt(i); // compute next drawing position - X int cx = x + (i * prSpacing); // draw the character myFont.drawChar(g, cIndex, cx, y, prWidth, prHeight); // compute cusor position - Y int cy = y + 6; // if current char is selected if (i == prSelected){ // if cursor shoud be visible if (prShow) { //draw the cursor using char 95 or underscore character myFont.drawChar(g, 95, cx, cy, prWidth, prHeight); } } else { //draw the cursor using char 95 or underscore character myFont.drawChar(g, 95, cx, cy, prWidth, prHeight); } } } public void run() {

The drawPrompt() method draws each character in the prText string and cursor underneath each character. The cursor under the selected character blinks to let the player know which character he is currently editing. The markers/cursors under the non-selected characters will show the user how many characters he can enter. The drawPrompt() method takes 3 parameters: Graphics g - the Graphics object used for drawing. It will be passed to the clsFont.drawChar() method int x - the horizontal starting drawing position int y - the vertical drawing position

Putting It All Together Let's modify the code in our run() method to make use of the new methods we just added. First, add the call to the updatePrompt() method below the call to checkKeys() like so:

checkKeys(iKey, lCurrTick); updatePrompt(lCurrTick); if (isDown[fireKey]){

Then, add the following lines above the call to flushGraphics() like so:

g.fillRect(0, 0, screenW, screenH); myFont.drawString(g, "Enter your name:", 10, 20); drawPrompt(g, 10, 40);

myFont.drawString(g, "LEFT/RIGHT - move cursor", 10, 80); myFont.drawString(g, "UP/DOWN - change letter", 10, 100); myFont.drawString(g, "Hello " + prText.trim() + "!", 10, 140); flushGraphics();

You can now run the project and should see something like this:

Here's the completed clsCanvas source code so you can check your work:

package MyGame; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.game.GameCanvas; public class clsCanvas extends GameCanvas implements Runnable { // key repeat rate in milliseconds public static final int keyDelay = 250;

//key constants public static final public static final public static final public static final public static final

int int int int int

upKey = 0; leftKey = 1; downKey = 2; rightKey = 3; fireKey = 4;

//key states for up, left, down, right, and fire key private boolean[] isDown = { false, false, false, false, false }; //last time the key changed state private long[] keyTick = { 0, 0, 0, 0, 0 }; //lookup table for key constants :P private int[] keyValue = { GameCanvas.UP_PRESSED, GameCanvas.LEFT_PRESSED, GameCanvas.DOWN_PRESSED, GameCanvas.RIGHT_PRESSED, GameCanvas.FIRE_PRESSED }; private boolean isRunning = true; private Graphics g; private midMain fParent; private clsFont myFont; // this will hold the resulting text private String prText = "AAAAAA"; // the currently selected character private int prSelected = 0; // width and height of the characters private int prWidth = 9; private int prHeight = 10; // character spacing - includes width of character private int prSpacing = 12; // vars private private private for timing the long prStart = long prDelay = boolean prShow blinking cursor 0; 100; = true;

/** Creates a new instance of clsCanvas */

public clsCanvas(midMain m) { super(true); fParent = m; setFullScreenMode(true); } public void start(){ Thread runner = new Thread(this); runner.start(); } public void load(){ try{ // load the images here }catch(Exception ex){ // exit the app if it fails to load the image isRunning = false; return; } myFont = new clsFont(); myFont.load("/images/fonts.png"); } public void unload(){ // make sure the object gets destroyed myFont.unload(); myFont = null; } public void checkKeys(int iKey, long currTick){ long elapsedTick = 0; //loop through the keys for (int i = 0; i < 5; i++){ // by default, key not pressed by user isDown[i] = false; // is user pressing the key if ((iKey & keyValue[i]) != 0){ elapsedTick = currTick - keyTick[i]; //is it time to toggle key state? if (elapsedTick >= keyDelay){ // save the current time keyTick[i] = currTick; // toggle the state to down or pressed isDown[i] = true; } }

} } public String replaceCharAt(String s, int pos, char c) { if (pos < (s.length() - 1)){ return s.substring(0, pos) + c + s.substring(pos + 1); } else { return s.substring(0, pos) + c; } } public void updatePrompt(long currTick){ // get text length int len = prText.length(); // get current selected characters ASCII value int prOrd = (int)prText.charAt(prSelected); // is it time to change to cursors visibility if ((currTick - prStart) >= prDelay){ // update starting time prStart = currTick; // toggle cursor visibility prShow = !prShow; } if (isDown[leftKey]){ // if not first character if (prSelected > 0){ // select previous character in string prSelected--; } } else if (isDown[rightKey]){ // if not last character if (prSelected < (len - 1)){ // select next character in string prSelected++; } } else if (isDown[upKey]){ if (prOrd == 97){ // small leter a prOrd = 90; // jump to capital letter Z } else if (prOrd == 65){ // capital letter A prOrd = 32; // jump to space character } else if (prOrd == 32){ // space character prOrd = 122; // jump to small leter z } else if ((prOrd > 97) || (prOrd > 65)){ prOrd--; // previous character }

// replace the selected character with the new character prText = replaceCharAt(prText, prSelected, (char)prOrd); } else if (isDown[downKey]){ if (prOrd == 32){ // space character prOrd = 65; // jump to capilat letter A } else if (prOrd == 90){ // capital letter Z prOrd = 97; // jump to small letter a } else if (prOrd == 122){ // small letter z prOrd = 32; // jump to space character } else if ((prOrd < 90) || (prOrd < 122)){ prOrd++; // next character } // replace the selected character with the new character prText = replaceCharAt(prText, prSelected, (char)prOrd); } } public void drawPrompt(Graphics g, int x, int y){ // get length of the string int len = prText.length(); // loop through the characters for (int i = 0; i < len; i++){ // get ASCII value int cIndex = (int)prText.charAt(i); // compute next drawing position - X int cx = x + (i * prSpacing); // draw the character myFont.drawChar(g, cIndex, cx, y, prWidth, prHeight); // compute cusor position - Y int cy = y + 6; // if current char is selected if (i == prSelected){ // if cursor shoud be visible if (prShow) { //draw the cursor using char 95 or underscore character myFont.drawChar(g, 95, cx, cy, prWidth, prHeight);

} } else { //draw the cursor using char 95 or underscore character myFont.drawChar(g, 95, cx, cy, prWidth, prHeight); } } } public void run() { int iKey = 0; int screenW = getWidth(); int screenH = getHeight(); long lCurrTick = 0; // current system time in milliseconds; String sName = ""; load(); g = getGraphics(); while(isRunning){ lCurrTick = System.currentTimeMillis(); iKey = getKeyStates(); checkKeys(iKey, lCurrTick); updatePrompt(lCurrTick); if (isDown[fireKey]){ isRunning = false; } //restore the clipping rectangle to full screen g.setClip(0, 0, screenW, screenH); //set drawing color to black g.setColor(0x000000); //fill the screen with blackness g.fillRect(0, 0, screenW, screenH); myFont.drawString(g, "Enter your name:", 10, 20); drawPrompt(g, 10, 40); myFont.drawString(g, "LEFT/RIGHT - move cursor", 10, 80);

myFont.drawString(g, "UP/DOWN - change letter", 10, 100); myFont.drawString(g, "Hello " + prText.trim() + "!", 10, 140); flushGraphics(); try{ Thread.sleep(30); } catch (Exception ex){ } } g = null; unload(); fParent.destroyApp(false); fParent = null; } }

The code presented in this tutorial show a very simple way for your game to accept text input from the user. Using only directional keys/buttons, the player is able to enter his name which is useful for game customization and recording scores. One thing to remember is that you can adjust the maximum length of text the user can enter simply by assigning a string of the required length to the prText variable. You can even use a bunch of spaces to initialize it. If you have ever played games on other hand-held devices with a minimal set of controls like the GameBoy or even console systems like the PlayStation, you have probably run across other techniques to get text input from the player. Those techniques have been tried and tested so it wouldn't hurt to adapt them for you own use. Just remember to keep it simple

Vous aimerez peut-être aussi