Overview
AABB (Axis-Aligned Bounding Box) collision detects when two rectangles overlap. It's fast, simple, and good enough for most games. Each object has a position (x, y) and size (width, height). Two boxes collide if they overlap on both axes.
This is the same algorithm on every platform — only the implementation details change.
Algorithm
Two rectangles overlap if and only if:
- Box A's left edge is left of Box B's right edge, AND
- Box A's right edge is right of Box B's left edge, AND
- Box A's top edge is above Box B's bottom edge, AND
- Box A's bottom edge is below Box B's top edge
; Returns: true if collision, false otherwise
check_collision(ax, ay, aw, ah, bx, by, bw, bh):
if ax >= bx + bw: return false ; A is right of B
if ax + aw <= bx: return false ; A is left of B
if ay >= by + bh: return false ; A is below B
if ay + ah <= by: return false ; A is above B
return true ; Overlap!
Pseudocode
; Object structure
player_x, player_y: byte
player_w, player_h: byte = 16, 16
enemy_x, enemy_y: byte
enemy_w, enemy_h: byte = 16, 16
; Check player vs enemy collision
check_player_enemy:
; Check X axis overlap
load player_x
add player_w
if result <= enemy_x: return NO_COLLISION
load enemy_x
add enemy_w
if result <= player_x: return NO_COLLISION
; Check Y axis overlap
load player_y
add player_h
if result <= enemy_y: return NO_COLLISION
load enemy_y
add enemy_h
if result <= player_y: return NO_COLLISION
return COLLISION
Implementation Notes
Optimised check order: Test the most likely failure first. If objects usually approach from the sides, check X before Y. This lets you exit early most of the time.
Sprite centre vs corner: Hardware sprites often use top-left as origin. If your collision box should be centred, offset the check coordinates:
; For a 16x16 sprite with 8x8 collision box centred
collision_x = sprite_x + 4
collision_y = sprite_y + 4
collision_w = 8
collision_h = 8
Multiple enemies: Loop through enemy array, checking each against player:
for i = 0 to num_enemies - 1:
if check_collision(player, enemies[i]):
handle_hit(i)
Trade-offs
| Aspect | Cost |
|---|---|
| CPU | ~30-60 cycles per check (platform dependent) |
| Memory | 4 bytes per object (x, y, w, h) |
| Accuracy | Rectangle only — diagonal corners may miss or false-hit |
When to use: Most games — shooters, platformers, action games.
When to avoid: Games requiring pixel-perfect collision (use sprite mask AND instead).
Variations
- Point-in-box: Simplified check for bullets (w=1, h=1)
- Expanded box: Add a few pixels for more forgiving collision
- Shrunk box: Smaller than sprite for tighter, fairer hits
Many-object optimisation: spatial bucketing
A naive "player vs all enemies" loop does N checks per frame; "all enemies vs all enemies" does N². At 16 enemies this is already 256 checks per frame, eating cycle budget on 8-bit CPUs.
The standard mitigation is spatial bucketing: divide the screen into a coarse grid (e.g. 8×8 cells of 32×32 pixels each on a Spectrum). Each frame, sort objects into buckets by their position. Then for each object, only test against others in the same bucket (and adjacent buckets if the object spans a boundary).
; 8x8 bucket grid for a 256x192 Spectrum screen
buckets: array[64] of object_list
bucket_size_x = 32
bucket_size_y = 24
rebucket_objects:
clear all buckets
for each object o:
col = o.x / bucket_size_x
row = o.y / bucket_size_y
bucket = row * 8 + col
add o to buckets[bucket]
check_collisions:
for each bucket b:
check all object pairs within b ; small N
check b against right neighbour
check b against bottom neighbour
check b against bottom-right neighbour
Bucketing trades memory (the bucket arrays) for cycles. Worthwhile once N exceeds 8-12 objects.