Skip to content
Techniques & Technology

Collision Detection

When objects meet

Collision detection determined when game objects touched, using techniques from simple bounding boxes to pixel-perfect checks, each trading accuracy for performance.

commodore-64nintendo-entertainment-systemcommodore-amigasinclair-zx-spectrumprogrammingphysicstechnique1975–present

Overview

Did the bullet hit? Did the player land? Collision detection answers gaming’s fundamental questions — when objects occupy the same space. Simple boxes worked for most cases; fighting games needed precision; bullet hells need a tiny single-pixel hurtbox in the middle of a busy character. Each method trades accuracy against CPU cost, and clever programmers chose wisely based on game requirements.

This entry covers the fundamental algorithms. For the design of hitboxes (what they should be, where to place them) see Hitboxes; for tile-grid-specific collision see Tile-Based Collision.

Fast facts

  • Purpose: Detect object intersection.
  • Trade-off: Accuracy vs performance vs determinism.
  • Methods: Bounding box, circle, pixel-perfect, polygon, swept.
  • Application: Every action game, every physics simulation, every UI hit-test.

Bounding box (AABB) collision

The workhorse of 2D games. Two axis-aligned rectangles overlap if and only if they overlap on both axes:

// AABB intersection test
if (a.x < b.x + b.width &&
    a.x + a.width > b.x &&
    a.y < b.y + b.height &&
    a.y + a.height > b.y)
{
    // Collision
}

Four comparisons. Cheap on every CPU. Standard for 8-bit and 16-bit games where everything is rectangular.

Pro Con
Extremely cheap Doesn’t match non-rectangular shapes well
Easy to implement “Empty corners” of the box still register as collision
Hardware support on some platforms Requires careful tuning to feel fair

Circle collision

Two circles intersect if the distance between their centres is less than the sum of their radii:

// Circle-circle intersection (using squared distance to avoid sqrt)
float dx = a.cx - b.cx;
float dy = a.cy - b.cy;
float radiusSum = a.r + b.r;
if (dx*dx + dy*dy < radiusSum*radiusSum) {
    // Collision
}

Squaring both sides avoids the expensive sqrt call.

Pro Con
Rotation-invariant — same answer at any angle Requires multiplication; 6502 needs lookup tables
Natural for spherical objects Doesn’t fit elongated shapes

Good for: balls, bullets in some shooters, approximate range checks.

Pixel-perfect collision

When two bounding boxes overlap, examine the actual sprite pixels in the overlap region. Both objects’ pixels are checked at each shared position; if both are non-transparent, it’s a collision.

Step Process
1 Bounding-box test (broad phase) — quickly reject far-apart objects
2 If overlap, iterate the overlap region pixel-by-pixel
3 At each pixel, test both sprites for non-transparency
4 Any “both non-transparent” pixel = collision

Expensive on retro hardware — typically tens of comparisons per overlapping pair. Used by Street Fighter II, the Worms series (where the world is a bitmap), classic Mortal Kombat. Modern games rarely use it because GPU-side rendering doesn’t expose per-pixel alpha to gameplay code; physics colliders do the job differently.

Pro Con
Pixel-accurate Expensive
Works for arbitrary shapes Sprite-tied — changes with animation

Hardware-assisted collision (C64)

The VIC-II latches collision events into hardware registers — read once per frame to find which sprites collided:

Register Function
$D01E Sprite-sprite collision: each bit set when two sprites’ opaque pixels overlap
$D01F Sprite-background collision: each bit set when a sprite collides with a non-zero playfield pixel

The bits are latched — they accumulate until you read the register, which clears them. Combined with sprite-priority registers, a fast collision engine on C64 looks like:

    lda $d01e          ; sprite-sprite collisions; sets bits + clears on read
    sta sprite_coll
    lda $d01f          ; sprite-background
    sta bg_coll
    
    ; Then bit-test which sprite(s) collided
    lda sprite_coll
    and #%00000010     ; sprite 1
    beq .no_sprite1_collision
    ; ... handle ...

Free hardware collision is one of the C64’s underrated features. The NES has a single sprite-zero hit ($2002 bit 6) — useful but much more limited.

Tile-based collision

When the world is a tile grid, collision against the world is a tile lookup. See Tile-Based Collision for the deep dive.

Advantage Detail
Grid lookup Check tile at position — O(1)
Tile properties Solid, empty, hazard, ladder — encoded per tile
Performance Very fast; shifts and table lookups only
Limitation Grid-aligned only; not for free-rotation worlds

Polygon collision

For arbitrary shapes — separating axis theorem (SAT), GJK algorithm, or polygon clipping. Standard in 3D games and modern 2D physics engines (Box2D, Chipmunk).

Out of scope for retro hardware (the per-pair cost is too high), but used by Doom’s BSP-based collision and modern engines.

Swept collision

Standard collision tests the current positions. Swept collision tests the path an object took since the last frame — preventing tunnelling, where a fast object skips through a thin obstacle:

Frame N:    [bullet]       [wall]
Frame N+1:               [bullet]   (passed through!)

Swept collision sweeps the bullet’s bounding box from old to new position, intersecting against the wall — catches the collision that the static test misses.

Common in fast-projectile games: hitscan weapons, bullet sprites, fast-moving platformer characters.

Optimisation techniques

Modern games have hundreds of objects; brute-force pair testing is O(n²). The standard optimisations:

Technique Purpose
Broad phase / narrow phase Cheap test first (AABB), expensive test second (per-pixel)
Spatial partitioning Quadtree, grid bucket, sweep-and-prune — only test pairs that share a region
Bitmasks Fast group/layer queries — “only test enemies vs player bullets”
Spatial hashing Bucket objects by hashing position; lookup neighbours
Frame skipping Cheap-to-resolve collisions can be checked every N frames
Hierarchical grouping Test the parent body’s box first, then per-bone if it hits

For 8-bit games with ~20 objects max, brute-force AABB is usually fine. The optimisations matter once you hit hundreds.

Common issues

Problem Cause Solution
Tunnelling Fast object skips through thin obstacle in one frame Swept collision; or smaller timesteps
False positives Bounding box larger than visual sprite Tighter custom hitboxes
False negatives Check too infrequent (frame-skipping mistake) Test every frame for fast objects
Stacking glitches Pushed-out characters re-collide Carefully resolve contacts via penetration depth
Edge sticking Player snags on tile edges when sliding Bevel corners or use coyote-time logic

See also