Tutorial: How to make a tile-based 2D scrolling game world in JavaScript

Lesson 4) Drawing the map

Tile Size

I know you're super eager to start drawing the map! But slow down cowboy, first you have to define your tile size. We can store this as a variable - personally I'm going for 25 pixels:
var tileSize = 25;

Going loopy

OK next bit - we figure out which tiles fall within the view of the camera and loop through them, drawing as we go. Although we're familiar with the forEach loop, it's not appropriate here because we don't want to loop through every single tile in the arrays. So we'll use nested for loops instead.

Pay attention 007, this is where it gets a bit tricky:

function drawMap() {
var onXTile = Math.floor((camera.x + (camera.w / 2)) / tileSize);
var onYTile = Math.floor((camera.y + (camera.h / 2)) / tileSize);
c.beginPath();
for (j = onYTile - 13; j < onYTile + 13; j++) {
for (l = onXTile - 17; l < onXTile + 17; l++) {
if (j >= 0 && l >= 0 && j < map.length && l < map[j].length) {
c.fillStyle = map[j][l];
c.fillRect(l * tileSize - camera.x, j * tileSize - camera.y, tileSize, tileSize);
tileCount++;
} // check tile is in bounds
} // for loop - x axis
} // for loop - y axis
}
Let go through it line-by-line:

1) We label the function drawMap. It takes no parameters.

2) We create a variable called onXTile. To get it's value, take the camera's x coordinate and add half of it's width (this happens to be the player's x coordinate too - but there's a reason we don't use Player1.x here, which I will reveal in Lesson 5). Divide this by the size of the tile and round down - this is the x coordinate of the player's current tile in the map array.

3) Do the same with the camera's y position to get onYTile, the y coordinate of the player's current tile in the map array.

4) There be drawing ahead! So let's begin a path.

5) We've created a for loop and set it's conditions as follows:
• The starting value of the counter, j, is onYTile - 13. Why 13? The camera is 600 pixels high, the player is in the middle, so there's 300 pixels above him. At 25 pixels per tile, that's 12 tiles. We'll draw 13 just to be safe.
• The test: is the counting variable less than onYTile + 13? As soon as the counter reaches this, the loop will break.
• We add 1 to the counter each loop. So we're looping though numbers from onTileY - 13 to onTileY + 13.
6) We've created a second for loop. This time it's going from onXTile - 17 to onXTile + 17. Same logic here - there are 16 tiles either side of the player, but we'll draw 17 just to be safe.

I hope this makes sense so far. We are looping through the coordinates in the map array of the tiles that are inside the camera's boundary - j being the y coordinate and l being the x coordinate.

7) This if statement checks a few things:
• j and l are equal to or greater than zero
• j is less than the length of the map array
• l is less than the length of the current row of the map array that we are looping through
This ensures we don't reference positions in the array that don't exist. If the player was on tile x=1, y=1, our j counter would start from -12 and the l would start from -16. Referencing the array at map[-12][-16] will cause an error - this conditional prevents that.

8) We set fillStyle to the value in the map array at map[j][l]. As the map array is filled with the names of colours, this is perfectly valid.

9) Finally, we draw the tile! To get the correct coordinates of the tile on the canvas, we can multiply the tile's position in the array by the size of the tiles. For example, the tile at map[40][40] will be at canvas coordinates x=1000, y=1000. Why? Because 40 * 25 = 1000. But if we draw a rectangle at x=1000 y=1000, it won't appear on the screen because the canvas is only 800px by 600px.

We need a way to shift the tiles we draw so that they start drawing from the upper left corner of the canvas. To do that all we need to do is subtract the camera's x and y coordinates, because these numbers just so happen to be the exact distance between the edge of the map and the edge of the drawing area!

10) Close off the if statement.

11) The end of the second loop. Unless we've reached the end of the present row of tiles, the program will loop through again and draw the next one.

12) The end of the first for loop. Unless we've looped through all the rows in the drawing area, the program will move on to the next one.

13) The end of the drawMap function.

The only thing left is to call the drapMap function from the mainDraw loop - but remember to call this before you draw the player, otherwise you'll just draw over him. Take out the rectangle we drew around the camera border too, we don't need it now. I've ordered the functions as follows:
function mainDraw() {
c.clearRect(0, 0, WIDTH, HEIGHT);
playerMove();
updateCamera();
drawMap();
playerDraw();
} //mainDraw
You end up with this:

It works! But notice, adventurer, that you can walk off the edge of the map and keep on going into the green abyss beyond. In the next and final lesson we will add some boundaries to this town.

Whew! That was a tough one, wasn't it? Go have a nap my friend, clear your head a bit. Or don't. I'm not your boss.

Go to Lesson 5 - Stopping the camera from moving when you reach the edge of the map