More Tile Types for the Tile Map

More Tile types and Character movement methods

More Tiles!

We'll now add a few new tile types to our map. Whilst we could just do this by extending the switch statement in the tile drawing loop, and by adding to the tile value checks when checking if our character can move, this would result in horribly bloated code and make it difficult to add or remove tiles in the future. Instead, we'll add a method for defining characteristics of each tile type is an easy to read and modify way.

View example

We'll also add so methods to the Character class to make it simpler and easier to read when checking if the Character can move to a neighbouring tile.

First, let's modify the map. We'll add a few new types of tile and make it look a bit better (but just a bit!).


var gameMap = [
	0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 2, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 0,
	0, 2, 3, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 0,
	0, 2, 3, 1, 4, 4, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 0,
	0, 2, 3, 1, 1, 4, 4, 1, 2, 3, 3, 2, 1, 1, 2, 1, 0, 0, 0, 0,
	0, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0,
	0, 1, 1, 1, 1, 2, 4, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0,
	0, 1, 1, 1, 1, 2, 4, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0,
	0, 1, 1, 1, 1, 2, 4, 4, 4, 4, 4, 1, 1, 1, 2, 2, 2, 2, 1, 0,
	0, 1, 1, 1, 1, 2, 3, 2, 1, 1, 4, 1, 1, 1, 1, 3, 3, 2, 1, 0,
	0, 1, 2, 2, 2, 2, 1, 2, 1, 1, 4, 1, 1, 1, 1, 1, 3, 2, 1, 0,
	0, 1, 2, 3, 3, 2, 1, 2, 1, 1, 4, 4, 4, 4, 4, 4, 4, 2, 4, 4,
	0, 1, 2, 3, 3, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 0,
	0, 1, 2, 3, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 1, 2, 1, 0,
	0, 3, 2, 3, 4, 4, 1, 2, 2, 2, 2, 2, 2, 2, 1, 0, 1, 2, 1, 0,
	0, 3, 2, 3, 4, 4, 3, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 3, 0,
	0, 3, 2, 3, 4, 1, 3, 2, 1, 3, 1, 1, 1, 2, 1, 1, 1, 2, 3, 0,
	0, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 1, 1, 2, 2, 2, 2, 2, 3, 0,
	0, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 4, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
];

We also want to define some floor types. In Java or C++ we would use an enum for this, but in Javascript it's just another object. The floorTypes properties are just a series of unique numbers - these help to keep the code more readable when looking at the floor type of each tile to see if a Character can walk on it.


var floorTypes = {
	solid	: 0,
	path	: 1,
	water	: 2
};

We can now define our list of tileTypes. Again, in Java or C++ we'd use an enum here, but in Javascript we use an object. The tileTypes indexes are the values we can use in the gameMap array. Each index is assigned a list of properties; a colour, which is the colour that will be used to draw the corresponding tiles, and a floor, which is assigned a value from the floorTypes list.

We're creating 5 types of tile here, but no you can see it's easy just to add to this list when you want to add new tiles of different types to the gameMap array:


var tileTypes = {
	0 : { colour:"#685b48", floor:floorTypes.solid	},
	1 : { colour:"#5aa457", floor:floorTypes.path	},
	2 : { colour:"#e8bd7a", floor:floorTypes.path	},
	3 : { colour:"#286625", floor:floorTypes.solid	},
	4 : { colour:"#678fd9", floor:floorTypes.water	}
};

We could just directly assign the numbers 0, 1, or 2 to the floor property of the tileTypes.entries, but using floorTypes.path, floorTypes.solid, etc., is much more readable.

Character movement helper methods

We also want to make it simpler to check if a Character can move in a specific direction, and also to begin the Characters movement. Furthermore, in doing so we'll once again improve the readability of the code. To begin with, we'll add a new Character method canMoveTo, which will take the x and y position of the tile we're trying to move the Character to:


Character.prototype.canMoveTo = function(x, y)
{

In this method we'll perform 2 checks; first, we'll check if the x and y coordinates fall within the map bounds. If not, we cannot move the Character here, and so return false:


	if(x < 0 || x >= mapW || y < 0 || y >= mapH) { return false; }

Secondly, we are checking the tile in the gameMap array at the target position (by using toIndex to convert the x,y coordinates to the matching array index) in the tileTypes list for the floor property, to ensure it matches our floorTypes.path value; we're only letting the Character move to path tiles:


	if(tileTypes[gameMap[toIndex(x,y)]].floor!=floorTypes.path) { return false; }

If the target tile is not a path tile, we cannot move here, and so return false. If these checks have passed, we can return true and close the method.


	return true;
};

We will also create 4 shorthand methods to see if the Character can move Up, Down, Left, and Right. These methods will simply call the canMoveTo method, passing as arguments the Characters current position (tileFrom) with the x or y values modified according to the direction we're trying to move in, and return the result:


Character.prototype.canMoveUp		= function() { return this.canMoveTo(this.tileFrom[0], this.tileFrom[1]-1); };
Character.prototype.canMoveDown 	= function() { return this.canMoveTo(this.tileFrom[0], this.tileFrom[1]+1); };
Character.prototype.canMoveLeft 	= function() { return this.canMoveTo(this.tileFrom[0]-1, this.tileFrom[1]); };
Character.prototype.canMoveRight 	= function() { return this.canMoveTo(this.tileFrom[0]+1, this.tileFrom[1]); };

Now, to make improve readability when we actually set a destination (tileTo) for a Character, we're going to add methods for each direction which simply take the current game time (in milliseconds) as their argument, and modify the tileTo x or y properties as needed for the target direction. The timeMoved value for the Character is also updated to the passed game time.


Character.prototype.moveLeft	= function(t) { this.tileTo[0]-=1; this.timeMoved = t; };
Character.prototype.moveRight	= function(t) { this.tileTo[0]+=1; this.timeMoved = t; };
Character.prototype.moveUp	= function(t) { this.tileTo[1]-=1; this.timeMoved = t; };
Character.prototype.moveDown	= function(t) { this.tileTo[1]+=1; this.timeMoved = t; };

Improving user input

In our drawGame function we currently have a series of if/else statements that look at which arrow keys are pressed, and see if we can move in the corresponding directions. Now we have our cleaner methods we can replace these if/else statements, and also the if block immediately after them that we were using to update the player.timeMoved property value. Our new movement processing block for the player will look like this:


	if(!player.processMovement(currentFrameTime))
	{
		if(keysDown[38] && player.canMoveUp())		{ player.moveUp(currentFrameTime); }
		else if(keysDown[40] && player.canMoveDown())	{ player.moveDown(currentFrameTime); }
		else if(keysDown[37] && player.canMoveLeft())	{ player.moveLeft(currentFrameTime); }
		else if(keysDown[39] && player.canMoveRight())	{ player.moveRight(currentFrameTime); }
	}

Drawing tiles

We can also change the switch block we were using to change the colour for the tile being drawn in the nested tile drawing y and x loops. The whole switch statement can be removed, and replaced with this single line:


			ctx.fillStyle = tileTypes[gameMap[toIndex(x,y)]].colour;

This simply changes our Canvas drawing context (ctx) fill colour to the colour corresponding to the gameMap array tile value found at the index returned from the toIndex method for the current x,y loop values in the tileTypes array!

Much neater! Now, when we want to add new tile types, we will only need to modify the tileTypes list, and we can use the new values in the gameMap array.

Page loaded in 0.01 second(s).