tilemap-parserv5.2.0GITHUB

Object Collision: the sprite-vs-sprite lane

The physics world resolves sprites against tiles and bodies. It does not resolve sprite against sprite; that's a separate lane, on purpose. Two characters touching is ObjectCollisionManager's job.

WHY IT'S SEPARATE

  • The world's job is movement resolution (don't let sprites walk into solids).
  • The manager's job is contact detection between moving things (who touched who, how deep).
  • Different queries, different cadence: the world runs every move_*; the manager runs one check_all_collisions() pass per frame.

The manager is a uniform spatial grid (cell_size=128.0 by default, rebuilt per query): objects only narrowphase against objects in their own or adjacent cells. Mixed shapes all work: rect, circle, capsule, polygon, and multi-shape objects (via a collision_shapes attribute).

THE API

MethodDoes
add_object(obj)register; duplicates warn and are skipped
remove_object(obj)unregister; missing objects warn
clear()remove everything
check_all_collisions()all-vs-all via the grid; each pair reported once
check_object(obj)one object vs all others (linear scan; need not be managed)
check_object_first(obj)first hit only, in insertion order

Each hit is a CollisionHit(object_a, object_b, normal, depth):

MemberDoes
normaldirection to separate (from A to B)
depthpenetration depth
resolve()separates both objects by half the depth along the normal
slide_velocity(vx, vy)projects a velocity onto the surface; approach component stripped
involves(obj) / other(obj)who's in this hit

WIRING IT IN

the sprite-vs-sprite lanePYTHON
manager = ObjectCollisionManager(cell_size=128.0)   # uniform spatial grid
manager.add_object(player)
manager.add_object(enemy1)
manager.add_object(enemy2)

# per frame: all-vs-all
for hit in manager.check_all_collisions():
    hit.resolve()          # separate both objects along the normal
    # or: vx, vy = hit.slide_velocity(player.vx, player.vy)  # strip approach

# or one-vs-all (enemy against everything, linear scan)
for hit in manager.check_object(enemy1):
    if hit.involves(player):
        print("enemy touched the player")
        other = hit.other(enemy1)      # -> player

CELL SIZE TUNING

cell_size is a cost trade-off. Too small: many empty cells, grid rebuild overhead. Too big: every object ends up in the same cell and the broadphase is a lie. For 32px tiles, 128 is a sane default; the comparison example spatial-cell-size-tuning.py benchmarks this empirically. cell_size must be finite and positive; anything else raises ValueError.