You are on page 1of 3

This article covers how to implement scrolling square tilemaps using the Canvas API.

The camera is an object that holds information about which section of the game world or level
is currently being shown. Cameras can either be free-form, controlled by the player (such as in
strategy games) or follow an object (such as the main character in platform games.)

Regardless of the type of camera, we would always need information regarding its current
position, viewport size, etc. In the demo provided along with this article, these are the
parameters the camera has:

x and y : The current position of the camera. In this implementation, we are assuming
that (x,y) points to the top left corner of visible portion of the map.
width and height : The size of the camera's viewport.
maxX and maxY : The limit for the camera's position — The lower limit will nearly always
be (0,0), and in this case the upper limit is equal to the size of the world minus the size of
the camera's viewport.

There are two main differences between rendering scrolling maps vs. static maps:

Partial tiles might be shown. In static maps, usually the rendering starts at the top left

1 de 3
corner of a tile situated at the top left corner of a viewport. While rendering scrolling
tilemaps, the first tile will often be clipped.

TODO: show a diagram here explaining this.

Only a section of the map will be rendered. If the map is bigger than the viewport, we
can obviously only dispart a part of it at a time, whereas non-scrolling maps are usually
rendered wholly.

To handle these issues, we need to slightly modify the rendering algorithm. Let's imagine that
we have the camera pointing at (5,10) . That means that the first tile would be 0x0 . In the
demo code, the starting point is stored at startCol and startRow . It's convenient to also pre-
calculate the last tile to be rendered.

1 var startCol = Math.floor(this.camera.x / map.tsize);


2 var endCol = startCol + (this.camera.width / map.tsize);
3 var startRow = Math.floor(this.camera.y / map.tsize);
4 var endRow = startRow + (this.camera.height / map.tsize);

Once we have the first tile, we need to calculate how much its rendering (and therefore the
rendering of the other tiles) is offset by. Since the camera is pointing at (5, 10) , we know that
the first tile should be shifted by (-5,-10) pixels. In our demo the shifting amount is stored in
the offsetX and offsetY variables.

1 var offsetX = -this.camera.x + startCol * map.tsize;


2 var offsetY = -this.camera.y + startRow * map.tsize;

With these values in place, the loop that renders the map is quite similar to the one used for
rendering static tilemaps. The main difference is that we are adding the offsetX and offsetY
values to the target x and y coordinates, and these values are rounded, to avoid artifacts that
would result from the camera pointing at positions with floating point numbers.

1 for (var c = startCol; c <= endCol; c++) {


2 for (var r = startRow; r <= endRow; r++) {
3 var tile = map.getTile(c, r);
4 var x = (c - startCol) * map.tsize + offsetX;
5 var y = (r - startRow) * map.tsize + offsetY;
6 if (tile !== 0) { // 0 => empty tile
7 this.ctx.drawImage(
8 this.tileAtlas, // image
9
2 de 3
(tile - 1) * map.tsize, // source x
0, // source y
map.tsize, // source width
map.tsize, // source height
Math.round(x), // target x
Math.round(y), // target y
map.tsize, // target width
map.tsize // target height
);
}
}
}

3 de 3

You might also like