Map Colliders

Overview

If a scene has an actor moving, we may update its position from frame to frame using the discrete approximation:

velocity = velocity + acceleration * dt
position = position + velocity * dt

That’s fine when the actor does not find an obstacle, like a wall.

If there is an obstacle, we usually want to
  • stop the position change so the actor touches the obstacle but does not overlap it.

  • do something sensible with the velocity (stop ?, bounce ?).

  • maybe do some action based on which obstacle (spikes ?, glass ?).

To do this we can represent the actor and the obstacles as rects with sides parallel to the axis, and do the calculations.

This is what map colliders do given:
  • the actor velocity, rect before and tentative rect after

  • a map layer that contains a number of obstacles

it will
  • Properly update the velocity and the position.

  • Call appropriate methods when it detects the actor bumped into an obstacle.

  • Tell if any collision in the x and / or y axis happened.

While some mapcolliders are meant to be used in tiled maps ( RectMapCollider, RectMapWithPropsCollider), others (TmxObjectMapCollider) can be used with no tiles at all.

Properly update the velocity and the position

The position and the velocity update can be written as:

vx, vy = actor.velocity

# using the player controls, gravity and other acceleration influences
# update the velocity
vx = (keyboard[key.RIGHT] - keyboard[key.LEFT]) * actor.MOVE_SPEED
vy += GRAVITY * dt
if actor.on_ground and keyboard[key.SPACE]:
    vy = actor.JUMP_SPEED

# with the updated velocity calculate the (tentative) displacement
dx = vx * dt
dy = vy * dt

# get the player's current bounding rectangle
last = actor.get_rect()

# build the tentative displaced rect
new = last.copy()
new.x += dx
new.y += dy

# account for hitting obstacles, it will adjust new and vx, vy
actor.velocity = mapcollider.collide_map(maplayer, last, new, vx, vy)

# update on_ground status
actor.on_ground = (new.y == last.y)

# update player position; player position is anchored at the center of the image rect
actor.position = new.center

How velocity changes in a collision is handled by method on_bump_handler(); it can be set to custom code or to one of the stock handlers:

For convenience, a stock handler can be specified at instantiation time with the velocity_on_bump parameter.

Call appropriate methods when it detects actor bumped into an obstacle

When mapcollider.collide_map detects that actor collides with obj ‘someobj’ from side ‘someside’, it will call mapcollider.collide_<someside>(someobj).

There ‘someside’ is one of ‘left’, ‘right’, ‘top’ and ‘bottom’.

This provides an opportunity to do things based on which object the actor touched, like ‘spike’ ? -> init the spikes animation and kill the actor.

Note that each mapcollider.collide_* can receive multiple calls in the same mapcollider.collide_map call.

Tell if any collision in the x and / or y axis happened

After collide_map returns, the flags mapcollider.bumped_x and mapcollider.bumped_y tells if there has been any collisions along the respective axis.

This can be handy to flip the actor’s animation direction, by example from ‘walk_left’ to ‘walk_right’.

Variants

Currently they are three map colliders variants:

RectMapCollider

Obstacles are rectangular tiles grouped into a RectMapLayer; all non empty cells are deemed solid.

implementation

RectMapWithPropsCollider

Obstacles are rectangular tiles grouped into a RectMapLayer, cells use properties “left”, “right”, “top” and “bottom” to signal which side(s) block movements.

A solid block would probably have all four sides set; a platform can set only the top so the player might jump up from underneath and pass through.

For convenience these properties would typically be set on the tiles themselves, rather than on individual cells. Of course for the cell which is the entrance to a secret area you could override a wall’s properties to set the side to False and allow ingress.

See Map, Cell and Tile Properties for information about properties.

implementation

TmxObjectMapCollider

Obstacles are TmxObjects grouped into a TmxObjectLayer, each object is meant to block movement.

A common use case is to do moving platforms in a platform game.

implementation

How to use

There are basically two ways to include this functionality into an actor class:

  • as a component, essentially passing (mapcollider, maplayer) in the actor’s __init__ method.

  • mixin style, by using RectMapCollider or a subclass as a secondary base class for the actor.

The component way is more decoupled while the Mixin style is more powerful because the collision code will have access to the entire actor through its self attribute.

To have a working instance, the behavior of the velocity in a collision must be defined, and that’s the job of the method on_bump_handler

  • if one of the stock on_bump_<variant> suits the requirements, suffices to write:

    mapcollider.on_bump_handler = mapcollider.on_bump_<desired variant>
    

    or passing a selector at instantiation time:

    mapcollider = MapCollider(<desired variant>)
    
  • for custom behavior define on_bump_handler in a subclass and instantiate it.