✚ Game development

Collisions in 2D games: AABB rectangles, circles and points

How to detect collisions in a 2D game with axis-aligned rectangles, circles and points: formulas, example code and the common mistakes to avoid.

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.
MM
Marco Marzaduri

Marco Marzaduri works in cybersecurity, digital forensics and web development. A computer science graduate, he combines rigorous technical analysis with attention to the people who use technology.

Read also