Making a Tile-Based Platformer - Physics

Tile-based game engines are probably some of the simplest 2D engines out there due to the well-structured nature of the worlds they regulate, but they are not without complications. In this article I will introduce the basics of tile-based physics, focusing on collisions between the player (or any entity, for that matter) and the game world.

Let's assume some basic properties about our player. The player is the same size as one tile, and has the following properties: x-position (left), y-position (top), width, height, x-speed and y-speed. Nothing too controversial here. Every frame, the player will attempt to move according to his x-speed and y-speed. Our collision algorithm will stop him as appropriate whenever he reaches a wall.

A crucial implementation detail is that we will split the player's movement into 2 steps, x and y. Trying to move in both directions at once gets very messy, very quickly. This separation means that our algorithm is very simple indeed; we only need to consider one edge (left, right, top or bottom) of the player at any given time. If we are moving left, we will consider the left edge of the player. If we are moving up, we will consider the top edge of the player, etc. etc.

Collision Edges

Points of Collision

For example's sake, let's say our player is moving right. We have to make sure his right side doesn't go through any walls. However, rather than considering every single point along the player's side, we can just define "nodes" where we want to check for collisions. Since the player is no bigger than a tile, he can only ever collide with 2 tiles at a time, so these nodes will be his top-right and bottom-right corners. For larger entities (if we want to support them), we can just define other nodes along their edges to ensure that no tiles slip "through the bars" of their collision boundaries.

Collision Nodes

The Algorithm

Now that we have a set of points (our nodes), and an attempted distance to travel (our x-speed), we can just chuck each of these points at our collision-handling algorithm, written here in pseudo-code:

        handleCollisionX(x, y, dx){
            new_x = x + xSpeed;
            tile = getTileAt(new_x, y);
            if (tile.isSolid()){
                if (dx < 0){
                    # Moving left
                    # See below for an explanation as to how we find the right edge
                    right_edge_of_tile = new_x - (new_x % TILE_WIDTH) + TILE_WIDTH - 1;
                    dist_to_tile = right_edge_of_tile - x;
                    return dist_to_tile;
                } else {
                    # Moving right
                    left_edge_of_tile = new_x - (new_x % TILE_WIDTH);
                    dist_to_tile = left_edge_of_tile - x;
                    return dist_to_tile;
                }
            }
            return dx;
        }
    

After running all our points through this function, the smallest value returned represents the distance to the nearest collision. If there was no collision, this will just return our x-speed. The player should move this distance in the x-axis, and then we should repeat a similar process for the y-axis. And that's all there is to it.

A Note on Rectangle Bounds

One final note. When we talk about the position of a tile or entity, we are normally referring to the left edge. But what if we want to find the right edge?

We want our player to be able to fit exactly into one tile. This means that his width will be the same as the width of a tile. So to get the player's right edge, we should just use player.left + TILE_WIDTH, right? Wrong. The highlighted pixels in the image below show the incorrect right and bottom edges obtained using this method.

Incorrect Edges

Suddenly our player, who should be able to fit inside a single tile, is intersecting 4 separate tiles! Fortunately this is very easy to correct - the right edge should in fact be at player.left + TILE_WIDTH - 1.

Next: Making a Tile-Based Platformer - Slope Physics