Collision Detection in Ruby2D
Axis-aligned bounding boxes establish overlap; axis-specific resolution restores position, velocity, and contact state after a collision.
Collision has two distinct phases. Detection answers whether geometric regions overlap. Resolution determines the side of contact, returns the moving object to a valid position, and removes the velocity that caused the penetration. Detection without resolution leaves an object inside a wall; resolution without a reliable geometric test produces jitter and arbitrary corrections.
Axis-Aligned Bounding Boxes
An axis-aligned bounding box (AABB) is a rectangle that does not rotate. It is represented by a top-left coordinate, width, and height. Its edges are derived values:
left = x
right = x + width
top = y
bottom = y + height
Two AABBs overlap when their projections overlap on both axes:
def overlaps?(a, b)
a[:x] < b[:x] + b[:w] &&
a[:x] + a[:w] > b[:x] &&
a[:y] < b[:y] + b[:h] &&
a[:y] + a[:h] > b[:y]
end
Strict comparisons matter. Two edges that merely touch do not overlap. A player standing exactly on a platform is therefore not continuously treated as penetrating it; downward movement in the following update produces the contact that needs resolution.
Resolution Uses Movement Direction
An overlap alone does not identify its side. A player overlapping a platform might have entered from above, below, left, or right. The preceding movement supplies that information. Moving the player along one axis at a time makes the response unambiguous.
player[:x] += player[:vx] * dt
resolve_horizontal_collisions(player, solids)
player[:y] += player[:vy] * dt
resolve_vertical_collisions(player, solids)
After horizontal movement, an object with positive vx that overlaps a solid entered from the left. Set its x-coordinate to solid[:x] - player[:w] and set vx to zero. Negative vx uses the solid’s right edge instead. The correction places the edges flush together rather than merely moving the player “some distance” away.
Floors, Ceilings, and Grounded State
The vertical pass distinguishes floor and ceiling contact through vy. A positive vertical velocity means falling in screen coordinates. A falling player is placed on the solid’s top edge:
player[:y] = solid[:y] - player[:h]
player[:vy] = 0
player[:grounded] = true
An upward-moving player is placed below the solid and has vy cleared, but grounded remains false. Clearing grounded before the vertical pass prevents stale contact state from permitting a jump after the player has stepped off a ledge.
Collision Order Is a Design Choice
Resolving x before y produces predictable corner behaviour. A player moving diagonally into the side of a platform resolves horizontal penetration first, then falls or lands during the vertical pass. Reversing the order produces different behaviour, but either order must remain consistent across the game.
Iterating through multiple solids requires the same discipline. Resolve each detected overlap immediately before testing the next solid. Leaving multiple penetrations unresolved until the end obscures which position and velocity produced the next collision.
Tunnelling and Time Steps
Discrete overlap tests can miss thin geometry. If a player moves 40 pixels in one update and a wall is 16 pixels wide, the player can begin on one side and end on the other without ever overlapping it. This is tunnelling.
Velocity limits, thicker collision geometry, and smaller fixed physics steps bound the problem. Continuous collision detection computes the first contact time along the movement path and is necessary for very fast projectiles, but it adds complexity. A platformer with controlled speeds is usually stable with axis-by-axis AABB resolution and a bounded update step.
Collision as a World Invariant
The postcondition of a physics update is stronger than “collisions were checked”: no dynamic body overlaps a solid rectangle. Position correction enforces that condition; velocity correction prevents the same invalid movement from recurring on the next update. Rendering then reflects a world that has already been made physically valid.