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

Lesson 5) Stopping the camera from moving when you reach the edge of the map

Testing Boundaries

Games handle boundaries in different ways. In Sonic the Hedgehog, the camera stays centered on Sonic most of the time, but when you reach the edge, the camera stops moving and Sonic as able to move to the edge of your screen.

In the earlier GTA games, if you took a boat too far out to sea you automatically turned back. The water ahead of that point may well have been part of the game area, technically, but you can't get to it. We could easily do this in our game by putting water around the edge and preventing movement beyond certain coordinates.

Instead though, we'll take the Sonic approach.

Freezing the cameras

To stop the camera moving when it hits the edges of the game area, we first need to know where these edges are.

One way to do this would be to have an object containing the game area's height and width. But what if later we decide we want to add more tiles? We'd have to update this object whenever we made a change to the map, or if we built another level. The solution is to calculate these variables when the game loads up based on the size of the map array:
var mapSize = {
h: (map.length * tileSize),
w: (map[0].length * tileSize),
}
This is pretty straightforward - since every array stored in map is a row of tiles, so map.length * tileSize = the height of the map (that is, the number of rows).

I calculated the width in the same way - I took the length of the first row (any row would do) and multiplied it by tileSize.

Cut!

Now let's stop the cameras from moving when it reaches these boundaries. To do this, we just add conditionals to updateCamera that only move it if it's a certain distance away from the edge. Because the player is always in the middle of the screen, we'll use his position to determine that distance: Here's how this looks in the code:

function updateCamera () {
if ( Player1.x > (camera.w / 2) && Player1.x < mapSize.w - (camera.w / 2) ) {
camera.x = Player1.x - (camera.w / 2);
}
if ( Player1.y > (camera.h / 2) && Player1.y < mapSize.h - (camera.h / 2) ) {
camera.y = Player1.y - (camera.h / 2);
}
}
So just using x as an example, Player1.x needs to be greater than half the camera's width (stopping the camera when he's near the left side) AND less than the length of the map minus half the camera's width (stopping the camera when he's near the right side). Otherwise the camera's x position won't update.

The y axis works the same way but using the camera's height.

Find your edge

You may remember from the non-scrolling game we made, stopping the player moving off the screen was pretty easy. We just added conditions to the playerMove function that only allow movement if the Player's x and y coordinates are greater than zero, and also less than the width and height of the canvas respectively.

That works, but you might get the player stopping a little short of the edge, or there might be a little bit of the sprite overlapping. That's usually OK, it happens in a lot of games and people seem quite tolerant of it. But I'll show you another way of doing it here, which looks better when your hero is as right-angled as ours!
function playerMove(e){
if (keys[87]) {
Player1.y -= Player1.speed;
if (Player1.y - (Player1.h / 2) < camera.y) {
Player1.y = camera.y + (Player1.h / 2);
}
}
if (keys[83]) {
Player1.y += Player1.speed;
if (Player1.y + (Player1.h / 2) >= camera.y + camera.h) {
Player1.y = camera.y + camera.h - (Player1.h / 2);
}
}
if (keys[65]) {
Player1.x -= Player1.speed;
if (Player1.x - (Player1.w / 2) < camera.x) {
Player1.x = camera.x + (Player1.w / 2);
}
}
if (keys[68]) { Player1.x += Player1.speed;
if (Player1.x + (Player1.w / 2) >= mapSize.w) {
Player1.x = camera.x + camera.w - (Player1.w / 2);
}
}
return false;
}
Let's look at keys[87], the rest follow the same principle. If the player wants to move up, we adjust his y value as normal. But then we throw in an if statement checking if his y, minus half his height (remember Player1.y is dead-center of the player now, not in the top-left corner), is less than the camera's y position. Since we freeze the camera if it's at the top of the map, this will be at the top of the game world. If it is, we set his y position to the camera's y, plus half the player's height. This locks the player against the top edge of the camera, and it will work whether his speed is 5, 27 or 527. whatever.

The big reveal

In the last lesson you will recall we defined onXTile and onYTile as follows:
var onXTile = Math.floor((camera.x + (camera.w / 2)) / tileSize);
var onYTile = Math.floor((camera.y + (camera.h / 2)) / tileSize);
Of course, camera.x + (camera.w / 2) is the same as Player1.x, and I promised you an explanation as to why I used that calculation instead of Player1.x. I won't keep you waiting any longer - I know you've been on the edge of your seat.

It's because when the player reaches the edge of the screen, he keeps moving but the camera doesn't! So if we used Player1.x, as he moves towards the edge of the screen the tiles behind him would start to disappear!

This is the end of our journey my friend. Let us bask in the glory of our accomplishment:


A scrolling world where the camera stops moving near the edges of the map.

I have some challenges for you. At the moment, the player walks the same speed over every terrain type. How could you make him slower over sand, and in water? Answers on a postcard please. Unless you're reading this in 2027 and there are no more postcards. Then a smoke signal will do.

Also, play around and see what else you can do with this basic structure. Can you make a simple game out of it?

OK, it's been real. I'll leave you with this link - this is where I've been going with this very structure: Neon City