Every game needs to know when things touch. A bullet hits an enemy, a player lands on a platform, a ball bounces off a wall. Collision detection is the system that answers "did these two things just overlap?" and physics is what happens next.
Browser games have a unique constraint: everything runs in JavaScript on the main thread (or a web worker), so your physics code competes with rendering, input, and networking for CPU time. You need algorithms that are fast enough to check hundreds of objects every frame at 60 FPS — roughly 16 milliseconds per frame total.
The fastest collision check is the one you never make. Spatial partitioning — dividing your game world into regions and only checking objects in the same region — eliminates 90% of checks before they happen.
The simplest and most common collision shape is the AABB — a rectangle aligned with the X and Y axes. Every game object gets a bounding box defined by its position, width, and height. Two AABBs overlap if and only if they overlap on both axes simultaneously.
function aabbOverlap(a, b) {
return a.x < b.x + b.w &&
a.x + a.w > b.x &&
a.y < b.y + b.h &&
a.y + a.h > b.y;
}
This check is extremely fast — four comparisons, no square roots, no trigonometry. For most 2D games, AABB is the only collision shape you need. Even games with rotated sprites often use AABB for gameplay collisions and only switch to more precise shapes for specific interactions.
For round objects (balls, bullets, explosions), circle collisions are more accurate than AABB. Two circles overlap when the distance between their centers is less than the sum of their radii.
function circleOverlap(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
const dist = dx * dx + dy * dy;
const radii = a.r + b.r;
return dist < radii * radii; // avoid sqrt
}
The key optimization: compare squared distances instead of actual distances. Computing a square root is expensive; squaring the sum of radii is a single multiplication. This makes circle collision nearly as fast as AABB.
With 200 objects, brute-force checking every pair means 19,900 comparisons per frame. Spatial hashing reduces this dramatically by dividing the world into a grid of cells. Each frame, every object registers in the cell(s) it occupies. When checking collisions, you only compare objects in the same cell.
class SpatialHash {
constructor(cellSize) {
this.cellSize = cellSize;
this.cells = new Map();
}
clear() { this.cells.clear(); }
_key(x, y) {
const cx = Math.floor(x / this.cellSize);
const cy = Math.floor(y / this.cellSize);
return cx + ',' + cy;
}
insert(obj) {
const key = this._key(obj.x, obj.y);
if (!this.cells.has(key)) this.cells.set(key, []);
this.cells.get(key).push(obj);
}
query(obj) {
const key = this._key(obj.x, obj.y);
return this.cells.get(key) || [];
}
}
Choose a cell size roughly 2x the size of your largest game object. Too small and objects span multiple cells (more bookkeeping). Too large and cells contain too many objects (defeating the purpose).
Platformer physics are deceptively complex. The basics: gravity accelerates the player downward, platforms stop downward movement, jumping applies upward velocity. But getting it to feel right requires careful tuning.
In real platformers, the jump height depends on how long the player holds the button. Short tap = short hop, long press = full jump. Implement this by applying higher gravity when the jump button is released early:
function updateJump(player, dt) {
if (!jumpHeld && player.vy < 0) {
player.vy += gravity * 2.5 * dt; // faster fall
} else {
player.vy += gravity * dt;
}
}
Give the player a few frames of grace after walking off a ledge where they can still jump. This feels forgiving without being noticeable. 6-8 frames (100-130ms) is the sweet spot.
When resolving collisions with platforms, always resolve the axis with the smallest overlap first. This prevents the player from "snapping" to weird positions when hitting a corner.
For games with bouncing, rolling, or stacking objects, you need basic rigid body physics: velocity, acceleration, mass, restitution (bounciness), and friction.
For simple platformers or top-down games, writing your own collision code (AABB + spatial hash) is straightforward and gives you full control. For games with complex physics (ragdolls, chains, soft bodies, realistic stacking), use a library: