Need More Time
My apologies for not having new tutorials for you at this time. I'm working on a project that's taking up most of my time. Hopefully, I'll be done by the end of the month. It will then be evaluated/tested. So I'm praying for zero revisions (this fairytale ending never happens). After that, I'll continue with the tutorials in the drafts.

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 key or UP Button
  • 2 key or DOWN Button
  • 4 key or LEFT Button
  • 6 key or RIGHT Button
  • 5 key or 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 for timing the blinking cursor
private long prStart = 0;
private long prDelay = 100;
private boolean prShow = 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:

User input sample output.


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

// 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 for timing the blinking cursor
private long prStart = 0;
private long prDelay = 100;
private boolean prShow = 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.

Was this post helpful? I made a mistake again? The words are to confusing? Please, don't hesitate to post a comment.

Latest Posts Feed Animator
I added a Latest Posts Feed Animator to my flash logo. It displays the last 5 posts as links from the posts feed I "burned" with FeedBurner.

In case your interested on how it was done, I used some ActionScript which I found here: rssParser 0.22.

I took out the draggable ClustrMap from the flash logo too (I've had enough of it, heh).

I hope this proves useful to you.

Post a comment if it's not working.

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:

Click to Enlarge


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:

Screen shot of project running in the emulator


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 java.io.DataInputStream;
import java.io.InputStream;

import java.util.Random;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.game.Sprite;
import 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 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 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 to be used for each map cell
byte[][] blockData = {
{10, 8 , 7 , 6 , 10, 9 , 8 , 7 , 6 , 10, 9 },
{6 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 8 },
{7 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 7 },
{8 , 0 , 0 , 10, 6 , 0 , 0 , 7 , 0 , 0 , 6 },
{9 , 0 , 0 , 0 , 0 , 0 , 0 , 8 , 0 , 0 , 10},
{10, 0 , 0 , 0 , 0 , 0 , 0 , 9 , 0 , 0 , 9 },
{6 , 0 , 0 , 8 , 0 , 0 , 0 , 0 , 0 , 0 , 8 },
{7 , 0 , 0 , 7 , 0 , 0 , 0 , 0 , 0 , 0 , 7 },
{8 , 0 , 0 , 6 , 0 , 0 , 0 , 10, 0 , 0 , 6 },
{9 , 0 , 0 , 10, 0 , 0 , 7 , 6 , 0 , 0 , 10},
{10, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 9 },
{6 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 8 },
{7 , 8 , 9 , 10, 6 , 7 , 8 , 9 , 10, 6 , 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:

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!

You know a better way? Found an error? Got a question? Please don't hesitate to post a comment.

Simple Tile Map Editor

Latest build : 1.1.0.20 Tuesday, October 7, 2008 3:58:20 PM
- Jump to Downloads

The Simple Tile Map Editor let's you design your tile maps visually and save them as small binary files that you can use in your MIDP game projects. That's all it does...

I made this application for my own use but I figured it might be useful to someone else. Here, have a screenshot:


Click to Enlarge - kinda reminds me of all the email spam I'm getting XD heh


Choosing a tile from the Tile Panel
The Tile Panel on the right side of the window let's you pick a tile from the tileset image you loaded. You can assign a tile to the left mouse button and the right mouse button by using either to pick a tile from the Tile Panel. An enlarged preview of the selected tiles will be displayed at the bottom part of the Tile Panel.


Tools and Basic Usage
Selection Tool:
Let's you select a group of tiles for use in Selection Mode.

Pen Tool:
Let's you draw a single tile.

Fill Tool:
Fills an area with the tile assigned to either the left or right mouse button.

Single Tile Mode - Same effect as the Pen Tool.
Selection Mode - Affects only the area inside or outside the selection rectangle, depending on which part of the map you clicked on.
Full Map Mode - Affects the entire map.

Replacer Tool:
Replaces the tile you clicked on with the tile assigned to the left or right mouse button.

Single Tile Mode - Same effect as the Pen Tool.
Selection Mode - Affects only the area inside or outside the selection rectangle, depending on which part of the map you clicked on.
Full Map Mode - Affects the entire map.

Toggle Grid:
Displays a grid on the map so you can see what tiles you're actually clicking on.

View Data:
Displays the map data (including block map data) of the currently open map as comma delimited byte values in a new tab window. Useful for embedding the map data in your source code.

WORD Resolution Map Width and Height:
The "Use WORD values for map dimensions" checkbox allows you to define map sizes larger than 256x256. You can find it in the "Map Dimensions Dialog" whenever you create a new map.

Map Dimensions Dialog
Map Dimensions Dialog



Block Map Mode:
Allows you to assign extra byte data to each map tile. Useful for defining look-up tables and optimizing code. Block map data is saved in a separate file with a ".BMD" extension.

Resize Map:
Allows you to resize the currently open map.

Crop Map to Selection:
Allows you to crop (or resize) the map leaving only the currently selected tiles. You must have the Selection Tool checked to enable this option.

Zoom In/Out:
Allows you to magnify the map rendered on the screen for easier editing specially when working with small tile sizes. The magnification values are in fixed increments:
25%, 50%, 75%, 100%, 150%, 200%, 300%, 400%, 800%.

Grid Color - New:
Allows you to change the maps grid color.

Toggle Tileset Index Numbers - New:
Allows you to hide or show the index numbers of the tiles in the tileset panel.


The Simple Map File Information
The map editor uses ".SMP" as the default extension. One important thing to remember is that the editor uses the byte data type for all map data. This means that the value of any data in the map has a range of 0 to 255. This limits you to the following maximum values:

map width - 255
map height - 255
tile width - 255
tile height - 255
number of tiles - 255

Note: tile index 0 should be a blank tile

I just thought I should let you know even though I doubt you'll be hitting the max values in your map if your just using it for MIDP games.

SMP File Structure:

Field Bytes Description
--------------------------------------------------------
Descriptor** 7 nothing really just skip this
Usually contains the string:
"STME1.0"

UseWord 1 an ASCII character that
determines if the map width and
map height is in BYTE or WORD
values. "B" for BYTE and "W"
for WORD.

MapWidth 1 the map width or number of
tile columns.
(2 bytes if you choose WORD
values)

MapHeight 1 the map height or number of
tile rows
(2 bytes if you choose WORD
values)

TileWidth 1 the width of a tile in pixels

TileHeight 1 the height of a tile in pixels

MapData X the actual map data. Read from
top to bottom, left to right.
Multiply the MapWidth and the
MapHeight to get actual length
of the data in bytes.
--------------------------------------------------------
**Note:
Starting from build 1.1.0.14, the descriptor has been
changed to "STME1.0".

Your program can determine if it should read WORD values
or BYTE values for the map width and map height by first
checking the 8th byte. It should contain the ASCII
character "B" for Byte and "W" for Word.

Ex:
Descriptor
STME1.0B - read map width and map height as
byte values
STME1.0W - read map width and map height as
word values

The rest of the map data including tile width and tile
height are still in bytes.

The map editor itself only checks for the "W" character
so it will still work on maps created with previous
versions of the map editor.

You can still open the old maps that have integer map
dimensions (4 bytes) created with version 1.1.0.14 but
the map will be saved with the map width and map height
as WORD values (2 bytes) instead.


**BMD File Structure:

Field Bytes Description
--------------------------------------------------------
BlockMapData X Block map data. Read from
top to bottom, left to right.
Multiply the MapWidth and the
MapHeight from the SMP file to
get the actual length of the
data in bytes.
--------------------------------------------------------
**Note:
The BMD file and Block Map Mode functions are available
starting with version 1.1.0.16 of STME.


Here's a sample method that shows how to read an SMP file:


/* required libraries - start
place this before your class declaration

import java.io.DataInputStream;
import java.io.InputStream;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.TiledLayer;

required libraries - end
*/

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;
}


The getMap() method reads the map file and returns a new TiledLayer object based on the contents of the map file. It has 2 parameters:
  • String fpath - the path to the map file
  • Image ftiles - the tileset image

You probably noticed that we're adding 1 to the actual value loaded from the map file. This is because the map editor counts the tiles starting at 0 while the TiledLayer class starts at 1. Hence, the tile data in the map file must be offset by 1 before being assigned to the TiledLayer.


Supported Image Formats
The Simple Tile Map Editor uses the GraphicsEx library written by Mike Lischke so I guess it supports the same image formats. You can head over to the new website for more information : Soft-Gems GraphicsEx page.



Download Build 1.1.0.20

10/07/2008
SimpleTileMapEditor1.1.0.20.zip
(470 KB) - Download it.
(Win32 binary)
MD5 : c2b8e14e95ab76bccbd8aec50a96839e
*SimpleTileMapEditor.exe
You can find a small list of changes here : List of Changes


Older Versions
02/17/2008
SimpleTileMapEditor1.1.0.19.zip
(468.06 KB) - Download it.
(Win32 binary)
MD5 : fc1984afb0f004301df2af087f1419f9
*SimpleTileMapEditor.exe
You can find a small list of changes here : List of Changes

02/17/2008
SimpleTileMapEditor1.1.0.19.zip
(468.06 KB) - Download it.
(Win32 binary)
MD5 : fc1984afb0f004301df2af087f1419f9
*SimpleTileMapEditor.exe
You can find a small list of changes here : List of Changes

02/06/2008
SimpleTileMapEditor1.1.0.17.zip
(471.57 KB) - Download it.
(Win32 binary)
MD5 : 4bb894f057faa9617704ffd24063a498
*SimpleTileMapEditor.exe
You can find a small list of changes here : List of Changes

02/03/2008
SimpleTileMapEditor1.1.0.16.zip
(470.94 KB) - Download it.
(Win32 binary)
MD5 : e4ec440c7814be5bef5bd25b0b52ad59
*SimpleTileMapEditor.exe
You can find a small list of changes here : List of Changes

01/25/2008
SimpleTileMapEditor1.1.0.15.zip
(538.38 KB) - Download it.
(Still Windows binaries only)
MD5 : d779a5c733d59c4c95d173c5ac38fc0f
*SimpleTileMapEditor.exe
You can find a small list of changes here : List of Changes

01/24/2008
SimpleTileMapEditor1.1.0.14.zip
(539.88 KB) - Download it.
(Still Win32 only XD)
MD5 : de2cb119d08090eca02103d331676b9b
*SimpleTileMapEditor.exe
You can find a small list of changes here : List of Changes

Initial Release
SimpleTileMapEditor.zip
(528.2 KB) - Download it.
(Win32 binaries only. Sorry guys.)
MD5 : 2173abf6f33c8cd96711df63fe820f74
*SimpleTileMapEditor.zip


Installation
No need to install. Just extract to your favorite folder, double click on the executable and your in business.

I will try to update this as much as time allows. In the meantime, if you have some questions or suggestions, don't hesitate to leave a comment.

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:

Herbarrio Screenshot
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:

TileMap Diagram

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
Tileset Image
Dimensions: 160x16 pixels
Tile Size: 16x16 pixels

Here's a larger view:

Tileset : 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 to be used for each map cell
byte[][] blockData = {
{10, 8 , 7 , 6 , 10, 9 , 8 , 7 , 6 , 10, 9 },
{6 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 8 },
{7 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 7 },
{8 , 0 , 0 , 10, 6 , 0 , 0 , 7 , 0 , 0 , 6 },
{9 , 0 , 0 , 0 , 0 , 0 , 0 , 8 , 0 , 0 , 10},
{10, 0 , 0 , 0 , 0 , 0 , 0 , 9 , 0 , 0 , 9 },
{6 , 0 , 0 , 8 , 0 , 0 , 0 , 0 , 0 , 0 , 8 },
{7 , 0 , 0 , 7 , 0 , 0 , 0 , 0 , 0 , 0 , 7 },
{8 , 0 , 0 , 6 , 0 , 0 , 0 , 10, 0 , 0 , 6 },
{9 , 0 , 0 , 10, 0 , 0 , 7 , 6 , 0 , 0 , 10},
{10, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 9 },
{6 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 8 },
{7 , 8 , 9 , 10, 6 , 7 , 8 , 9 , 10, 6 , 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 non-transparent 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:

TiledLayer Demo


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


package MyGame;

import java.util.Random;

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.game.Sprite;
import 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 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 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 to be used for each map cell
byte[][] blockData = {
{10, 8 , 7 , 6 , 10, 9 , 8 , 7 , 6 , 10, 9 },
{6 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 8 },
{7 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 7 },
{8 , 0 , 0 , 10, 6 , 0 , 0 , 7 , 0 , 0 , 6 },
{9 , 0 , 0 , 0 , 0 , 0 , 0 , 8 , 0 , 0 , 10},
{10, 0 , 0 , 0 , 0 , 0 , 0 , 9 , 0 , 0 , 9 },
{6 , 0 , 0 , 8 , 0 , 0 , 0 , 0 , 0 , 0 , 8 },
{7 , 0 , 0 , 7 , 0 , 0 , 0 , 0 , 0 , 0 , 7 },
{8 , 0 , 0 , 6 , 0 , 0 , 0 , 10, 0 , 0 , 6 },
{9 , 0 , 0 , 10, 0 , 0 , 7 , 6 , 0 , 0 , 10},
{10, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 9 },
{6 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 8 },
{7 , 8 , 9 , 10, 6 , 7 , 8 , 9 , 10, 6 , 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.

Something is missing? Wrong grammar? Got a question? Please don't hesitate to post a comment.

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.

It's an 18x18 pixel white van!
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:

Van Sprite on Emulator


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 javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.GameCanvas;
import 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 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 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.

You got a question? I made a typo? Something is amiss? Please don't hesitate to post a comment.