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:
- horizontal axis: apply
velocity.x, check overlapping solid tiles and, on collision, put the character flush against the wall and zerovelocity.x; - vertical axis: apply
velocity.y, check the tiles again and, on collision, align the character with the floor or ceiling and zerovelocity.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.