Collision detection in game programming comes in two main flavors: AABB (axis-aligned bounding boxes, rectangles that don't rotate) and circle collision. Both are fast to compute and cover the majority of arcade and action game needs.
AABB Collision
Two rectangles overlap if neither is fully to the left, right, above, or below the other. A rectangle is described by its top-left corner (x, y) and its dimensions (w, h).
defp aabb(ax :: float, ay :: float, aw :: float, ah :: float,
bx :: float, by :: float, bw :: float, bh :: float) :: int do
if ax + aw <= bx do return 0 end # a is left of b
if bx + bw <= ax do return 0 end # b is left of a
if ay + ah <= by do return 0 end # a is above b
if by + bh <= ay do return 0 end # b is above a
return 1
endUsage:
if aabb(@player_x, @player_y, 32.0, 32.0,
coin_x, coin_y, 16.0, 16.0) == 1 do
collect_coin()
endWhy This Works
The early-return pattern checks four separating axes. If the rectangles are separated on any axis, they cannot overlap. Only when all four checks pass do we know they intersect.
This is the same underlying logic as the Separating Axis Theorem (SAT), just simplified for axis-aligned boxes.
Circle Collision
Two circles overlap when the distance between their centers is less than the sum of their radii. Computing a square root is expensive, so we compare squared distances instead, mathematically identical, no sqrt needed.
defp circle_overlap(ax :: float, ay :: float, ar :: float,
bx :: float, by :: float, br :: float) :: int do
dx = ax - bx
dy = ay - by
dist_sq = dx * dx + dy * dy
sum_r = ar + br
if dist_sq < sum_r * sum_r do
return 1
end
return 0
endUsage:
if circle_overlap(@player_x, @player_y, 16.0,
enemy_x, enemy_y, 20.0) == 1 do
take_damage()
endCombining with Object Pools
Check every active bullet against every active enemy:
defp check_bullet_enemy_collisions() do
bi = 0
while bi < @bullets.len do
b = @bullets.get(bi)
if b.active == 1 do
ei = 0
while ei < @enemies.len do
e = @enemies.get(ei)
if e.active == 1 do
if aabb(b.x - 3.0, b.y - 6.0, 6.0, 12.0,
e.x - e.radius, e.y - e.radius,
e.radius * 2.0, e.radius * 2.0) == 1 do
# Hit, deactivate both
@bullets.set(bi, b with { active: 0 })
@enemies.set(ei, e with { hp: e.hp - 1 })
end
end
ei = ei + 1
end
end
bi = bi + 1
end
endNote: this is O(bullets × enemies). For small counts (< 100 each) this is fast. For larger counts, spatial partitioning (grids, quadtrees) reduces it to near-linear.
Resolving Overlap
Detection tells you if objects overlap. Resolution moves them apart. For player–wall collision, push the player out by the overlap amount:
defstruct Rect do
x :: float = 0.0
y :: float = 0.0
w :: float = 0.0
h :: float = 0.0
end
defp resolve_aabb(player :: Rect, wall :: Rect) :: Rect do
# Only resolve if overlapping
if aabb(player.x, player.y, player.w, player.h,
wall.x, wall.y, wall.w, wall.h) == 0 do
return player
end
# Find overlap depth on each axis
overlap_left = (player.x + player.w) - wall.x
overlap_right = (wall.x + wall.w) - player.x
overlap_top = (player.y + player.h) - wall.y
overlap_bot = (wall.y + wall.h) - player.y
# Push out on the shallowest axis
min_x = overlap_left
if overlap_right < min_x do min_x = overlap_right end
min_y = overlap_top
if overlap_bot < min_y do min_y = overlap_bot end
if min_x < min_y do
# Horizontal push
if overlap_left < overlap_right do
return player with { x: player.x - overlap_left }
end
return player with { x: player.x + overlap_right }
end
# Vertical push
if overlap_top < overlap_bot do
return player with { y: player.y - overlap_top }
end
return player with { y: player.y + overlap_bot }
endTile Maps
For a grid-based map, only check tiles near the player instead of every tile:
@tile_size :: script = 32.0
@map_w :: script = 20
@map_h :: script = 15
@tiles :: script = array_fixed(300, 0) # 0 = empty, 1 = solid
defp is_solid(tx :: int, ty :: int) :: int do
if tx < 0 or tx >= @map_w or ty < 0 or ty >= @map_h do
return 1 # out of bounds = solid wall
end
return @tiles.get(ty * @map_w + tx)
end
defp collide_player_tiles(px :: float, py :: float, pw :: float, ph :: float) :: float do
# Check the tile columns the player overlaps
left_tile = to_int(px / @tile_size)
right_tile = to_int((px + pw - 1.0) / @tile_size)
top_tile = to_int(py / @tile_size)
bot_tile = to_int((py + ph - 1.0) / @tile_size)
tx = left_tile
while tx <= right_tile do
ty = top_tile
while ty <= bot_tile do
if is_solid(tx, ty) == 1 do
tile_x = to_float(tx) * @tile_size
tile_y = to_float(ty) * @tile_size
# resolve and update px, py here
end
ty = ty + 1
end
tx = tx + 1
end
return px
endTile collision is fast because you only check the 4–9 tiles the player could possibly touch.
Point-in-Rectangle
Useful for UI clicks or checking if a projectile origin is inside an area:
defp point_in_rect(px :: float, py :: float,
rx :: float, ry :: float, rw :: float, rh :: float) :: int do
if px >= rx and px <= rx + rw and py >= ry and py <= ry + rh do
return 1
end
return 0
endExample, button click detection:
if mouse_pressed(:left) do
if point_in_rect(mouse_x(), mouse_y(), 100.0, 200.0, 160.0, 40.0) == 1 do
start_game()
end
end