Almost every game has to answer the same question hundreds of times per second: are these two objects touching? Did the bullet hit the enemy, did the player pick up the coin, did the character land on the platform? For 2D games a handful of simple shapes is nearly always enough, and knowing their formulas lets you write reliable collisions even without a full physics engine.
Why use simple shapes
A character's sprite may have a complex outline, but comparing two images pixel by pixel is expensive and rarely necessary. Instead, each object is given a simplified collision shape, often called a hitbox or collider. The most common are:
- the axis-aligned rectangle (AABB, axis-aligned bounding box);
- the circle;
- the point, useful for tiny projectiles or the mouse cursor.
The player never sees the collision shape, but feels its effects. A hitbox slightly smaller than the player's sprite makes the game feel more «generous», while enemy hitboxes larger than their sprites make hits taken feel unfair.
Rectangle versus rectangle
An AABB is described by the position of its top-left corner plus width and height, or by a centre and half-extents. Its sides are always parallel to the screen axes, and that is what makes the test so cheap.
Two rectangles overlap if and only if their projections overlap on both axes:
function aabb_overlap(a, b):
return a.x < b.x + b.width and
a.x + a.width > b.x and
a.y < b.y + b.height and
a.y + a.height > b.y
If just one of the four conditions is false, the rectangles are separate. This is the most widely used check in 2D games, from platformers to top-down role-playing games.
Pay attention to the choice between < and <=: with strict less-than, two rectangles touching exactly along an edge do not count as colliding. For a character standing on the ground that may be exactly what you want; for a coin to collect, perhaps not. What matters is to decide and stay consistent.
Circle versus circle
Two circles touch if the distance between their centres is less than the sum of their radii. To avoid the square root, a relatively expensive operation, you compare squares:
function circles_overlap(a, b):
dx = a.cx - b.cx
dy = a.cy - b.cy
r = a.radius + b.radius
return dx*dx + dy*dy < r*r
Circles are ideal for round or rotating objects, such as asteroids, balls and top-down characters. They have an important advantage: rotation does not change the shape, so the test stays the same whatever the orientation.
Circle versus rectangle
The mixed case is very common, for example a ball bouncing off bricks in a Breakout clone. The method is to find the point of the rectangle closest to the circle's centre, by clamping the centre's coordinates to the inside of the rectangle:
function circle_rect(c, r):
px = clamp(c.cx, r.x, r.x + r.width)
py = clamp(c.cy, r.y, r.y + r.height)
dx = c.cx - px
dy = c.cy - py
return dx*dx + dy*dy < c.radius * c.radius
If the circle's centre is inside the rectangle, the closest point coincides with the centre itself and the distance is zero: the collision is detected correctly.
Point inside a shape
The simplest test of all is used for mouse clicks, screen taps and tiny projectiles:
- point in an AABB: the x coordinate lies between the left and right edges, the y between the top and bottom edges;
- point in a circle: the squared distance from the centre is less than the squared radius.
Detecting is not enough: responding
Knowing that two objects touch is only half the job. Then you must decide what happens, and the responses differ widely:
| Situation | Typical response |
|---|---|
| Player and coin | Remove the coin, increase the score |
| Bullet and enemy | Apply damage, destroy the bullet |
| Player and wall | Separate the objects, zero the velocity on that axis |
| Ball and brick | Reverse the velocity component perpendicular to the side hit |
To separate two AABBs you compute the penetration on each axis and move the object along the axis with the smaller penetration. It is the simplest way to work out which side the contact happened on.
Frequent mistakes
- Objects that are too fast: a bullet that moves further than the target's width in one update can pass through it without ever overlapping. The fix is smaller steps, a test along the travelled segment, or a raycast.
- Sprite origin different from hitbox origin: if the sprite is drawn from its centre and the rectangle from its corner, collisions look offset. Drawing hitboxes in debug mode saves hours of searching.
- Rotated rectangles treated as AABBs: if the object rotates, an AABB is no longer suitable. You need a test for oriented rectangles, for example using the separating axis theorem, or a circle.
- Checking every object against every other: with many objects the number of pairs grows quickly; space-partitioning techniques are needed to discard distant pairs straight away.
In short
- Collisions are computed on simplified shapes, not on sprite pixels.
- Two AABBs collide if their projections overlap on both axes.
- For circles, compare the squared distance with the square of the sum of the radii.
- The circle-rectangle case is solved with the rectangle point closest to the centre.
- Detection must be followed by a response: pickup, damage, separation or bounce.