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.

9 comments   |   post a comment
said...
I combine this lesson and the other one with the menu screen
but I have a problem, I'm getting a NullPointerExeption
and I don't understand why
the menu screen works perfect, I call the Menu from the midMain (startApp)
but when I chose the new game and tell him like to go to the gameScreen it gives me the Exeption
this is the call to the gameScreen from the midMain:
protected void gameScreenShow() {
gameScreen = new GameScreen(this);
gameScreen.start();
display.setCurrent(gameScreen);
}

here is all the project, if you can, look at this please
http://www.2shared.com/file/3028046/287b3e04/PlayMe.html
thanks
said...
Hi there, Dev! Nice tutorial but I suggest you to go on and make a "Scrolling Tile Maps" explaining how to make a huge Tile Map, larger than 13x11. For example, a Super Mario level...

Dude, you rule! We're anxious waiting for more stuff!!! ;)
said...
I am having a problem with a game I am developing. Your tutorial has helped me alot. So onto the problem.

My game uses an accelerometer for the input of a balls movement, now when I use

Ball.collidesWith(blockMap,true){
Ball.setPosition(tx,ty);
}

it makes the ball kind of stick to the edge of the screen. Can you help me with this? My email is coffman34@hotmail.com.
said...
nice hah!
said...
cnu po my sprite na image,,pwd e email nyu xkn;; crunchy_roll30@yahoo.com or cbrian30@gmail.com,,tnx poh
kc gmgwa kmi ngayon ng games using sprite,,
said...
amh hello cnu may alm
Excellent post. I always wondered how they make those maps. Specially in RPGs. Thanks for the amazing post. I will give a try. Thanks to this I am a step closer to finish my Generic Viagra game.

Viagra Online Cheap Viagra
said...
i try t combine the tile collision with my program, but it doesn't work with my project. and i don't know what to do with it. Please help me. :)
said...
Hi Dev, You have made an excellent tutorial, andI am aware its been some time since you mae it, but if you could have a quick squiz at my code and tell me how I could make it better, I would be very greatful. It is your tuts up till here. menu option one is the font, and menu option 2 is this sprit and map game.

http://madeindurban.blogspot.com/p/programming.html

Im a computer science major that hasn't programmed a thing in years, this is my comeback. I am battling to jog my memory