✚ Game development

Tile collisions in a platformer: separate axes and slopes

How to handle collisions between the character and a tile map in a 2D platformer: per-axis movement, one-way platforms, slopes and visual debugging.

In a 2D platformer the character spends most of its time in contact with the level: walking on the floor, bumping into walls, hitting its head on the ceiling. If the level is built from a grid of tiles, collisions with the map can be handled far more simply and efficiently than those between free-moving objects. The secret lies in one rule: move one axis at a time.

Why the grid simplifies everything

In a tile map every cell has a fixed size, for example 16 × 16 pixels, and a type: empty, solid, passable from below, slope. To find out which tiles the character's rectangle overlaps, you do not need to check the whole map: you just convert the rectangle's edges into cell indices.

left_column  = floor(x / tile_size)
right_column = floor((x + width - 1) / tile_size)
top_row      = floor(y / tile_size)
bottom_row   = floor((y + height - 1) / tile_size)

You examine only the cells in this small range, usually two to six. The cost does not depend on the size of the level.

The corner problem

If you move the character diagonally and then try to resolve the overlap, an ambiguity arises. When the rectangle enters the corner of a block, should it be pushed back horizontally or vertically? A wrong choice produces flaws familiar to anyone who has programmed a platformer: the character snags on floor edges while running, or slides along walls when it should stop.

Moving one axis at a time

The standard solution splits movement into two phases:

  1. horizontal axis: apply velocity.x, check overlapping solid tiles and, on collision, put the character flush against the wall and zero velocity.x;
  2. vertical axis: apply velocity.y, check the tiles again and, on collision, align the character with the floor or ceiling and zero velocity.y.
x += velocity.x * dt
if collides_with_solids():
    if velocity.x > 0: x = tile_left_edge - width
    else:              x = tile_right_edge
    velocity.x = 0

y += velocity.y * dt
if collides_with_solids():
    if velocity.y > 0:
        y = tile_top_edge - height
        on_ground = true
    else:
        y = tile_bottom_edge
    velocity.y = 0

Because each phase moves along a single axis, the direction of the response is always obvious. The on_ground flag is set when the vertical collision happens while descending, and it is used to decide whether the character can jump.

High speeds

If the character moves more than one tile in a single step, it could skip over a thin block without ever overlapping it. Two remedies:

  • cap the maximum speed at less than one tile per step, which with a 60 Hz update and 16-pixel tiles means under 960 pixels per second;
  • subdivide the movement into several small steps when speed is high, checking for collision at each one.

Platforms you can jump through

Many platformers have platforms you pass through when jumping from below and can land on from above (one-way platforms). The rule is simple: the tile is solid only if

  • the character is moving downwards, and
  • in the previous step the character's bottom edge was above the tile's top edge.

The second condition prevents the character from being «teleported» on top of the platform while it is still passing through. To let the player drop down on purpose, you can ignore the platform for a few frames when down and jump are pressed together.

Slopes

Slopes need different treatment, because the floor is no longer at the same height across the whole cell. You associate the sloped tile with a function that returns the ground height based on the x coordinate within the cell:

Slope type Ground height in the cell
45° rising to the right size - local_x
45° rising to the left local_x
22.5° over two tiles half the incline, with an offset for the second tile

Usually the character's bottom-centre point is used as its «foot»: you compute the ground height at that point and, if the foot is below it, move the character back up. Going downhill needs one more trick: if the character was on the ground in the previous step, you «snap» it to the slope even if it has lifted off by a few pixels, otherwise it would descend in a series of small hops.

Check with visual debugging

Drawing the character's rectangle, the cells being checked and the colliding cells over the game, in different colours, reveals within seconds errors that would otherwise take hours. It is a tool worth preparing from the very start.

In short

  • With a tile map you only need to check the few cells under the character's rectangle.
  • Moving horizontally first and then vertically removes the corner ambiguity.
  • Excessive speeds must be capped or split into smaller steps.
  • One-way platforms are solid only when descending onto them from above.
  • Slopes use a ground-height function and snapping when going downhill.
MM
Marco Marzaduri

Marco Marzaduri works in cybersecurity, digital forensics and web development. A computer science graduate, he combines rigorous technical analysis with attention to the people who use technology.

Read also