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-spectrum programmingphysicstechnique 1975–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.

ProCon
Extremely cheapDoesn't match non-rectangular shapes well
Easy to implement"Empty corners" of the box still register as collision
Hardware support on some platformsRequires 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.

ProCon
Rotation-invariant — same answer at any angleRequires multiplication; 6502 needs lookup tables
Natural for spherical objectsDoesn'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.

StepProcess
1Bounding-box test (broad phase) — quickly reject far-apart objects
2If overlap, iterate the overlap region pixel-by-pixel
3At each pixel, test both sprites for non-transparency
4Any "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.

ProCon
Pixel-accurateExpensive
Works for arbitrary shapesSprite-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:

RegisterFunction
$D01ESprite-sprite collision: each bit set when two sprites' opaque pixels overlap
$D01FSprite-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.

AdvantageDetail
Grid lookupCheck tile at position — O(1)
Tile propertiesSolid, empty, hazard, ladder — encoded per tile
PerformanceVery fast; shifts and table lookups only
LimitationGrid-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:

TechniquePurpose
Broad phase / narrow phaseCheap test first (AABB), expensive test second (per-pixel)
Spatial partitioningQuadtree, grid bucket, sweep-and-prune — only test pairs that share a region
BitmasksFast group/layer queries — "only test enemies vs player bullets"
Spatial hashingBucket objects by hashing position; lookup neighbours
Frame skippingCheap-to-resolve collisions can be checked every N frames
Hierarchical groupingTest 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

ProblemCauseSolution
TunnellingFast object skips through thin obstacle in one frameSwept collision; or smaller timesteps
False positivesBounding box larger than visual spriteTighter custom hitboxes
False negativesCheck too infrequent (frame-skipping mistake)Test every frame for fast objects
Stacking glitchesPushed-out characters re-collideCarefully resolve contacts via penetration depth
Edge stickingPlayer snags on tile edges when slidingBevel corners or use coyote-time logic

See also