Action and reaction in physics

Collision detection and physics

Learn JavaScript collision detection for your canvas game. Check if rectangles and circles overlap, and make objects bounce off each other with simple physics. By the end of this tutorial, you'll have a small physics simulation with mass, gravity and restitution. Along the way, you can try every step in a live demo.

Part of a series - In this series, you make an HTML5 game step by step. Here, the single rectangle from the part about smooth canvas animation becomes a whole bunch of objects that collide and bounce off each other.

Create some moving objects

Skip ahead - If you already know how to create moving objects and are only interested in collision detection or physics, scroll down to the next section. You'll still need the GameObject class and the createWorld() function from this section, though.

Before you can detect collisions between moving objects, you'll need some objects to begin with. In the previous tutorial, you learned how to move a single rectangle. Let's build on that logic and create a whole bunch of moving objects to fill your game. First, define a new type of game object. It's going to be a simple square that extends a GameObject class, which you'll create in a moment:

class Square extends GameObject {
    constructor(context, x, y, vx, vy) {
        super(context, x, y, vx, vy);

        // Set default width and height
        this.width = 50;
        this.height = 50;
    }

    draw() {
        // Draw a simple square
        this.context.fillStyle = this.isColliding ? '#ff8080' : '#0099b0';
        this.context.fillRect(this.x, this.y, this.width, this.height);
    }

    update(secondsPassed) {
        // Move at the current velocity
        this.x += this.vx * secondsPassed;
        this.y += this.vy * secondsPassed;
    }
}

This code might look a bit familiar. There's a draw() and an update() function, just like in the previous tutorial. Only this time, they're baked into a separate square class. This way you can create many instances of a square and they all use the same logic to draw and update. You'll have the behavior and looks of the square in one easy-to-manage place.

The fillStyle in this new class depends on isColliding. When a square collides with another object, isColliding is true, and the square turns from blue to red. You'll see this in action when the first collisions are detected. For now, all squares will be blue.

The Square class inherits from GameObject, which gives every game object a position and a velocity. This makes it easy to create new types of game objects. Every new type inherits the properties and methods of the GameObject class. The square is just an example, but you could also make objects like enemies or players for your game this way.

class GameObject {
    constructor(context, x, y, vx, vy) {
        this.context = context;
        this.x = x;
        this.y = y;
        this.vx = vx;
        this.vy = vy;

        this.isColliding = false;
    }
}

Put the GameObject class above the Square class in your script. A class has to exist before another class can extend it.

You can create a new instance of a class by using the new keyword. Make some squares to fill up your game world using this createWorld() function.

let gameObjects;

function createWorld() {
    gameObjects = [
        new Square(context, 250, 50, 0, 50),
        new Square(context, 250, 300, 0, -50),
        new Square(context, 150, 0, 50, 50),
        new Square(context, 250, 150, 50, 50),
        new Square(context, 350, 75, -50, 50),
        new Square(context, 300, 300, 50, -50)
    ];
}

The function creates a bunch of squares and stores them in the gameObjects array. Each square gets the context to draw on, a position (x, y) and a velocity (vx, vy) in pixels per second. For now, this function places the squares at fixed positions, but you could easily modify it to place them at random or use a spawning algorithm. The tutorial about random numbers in JavaScript shows how to pick a random position.

Everything is in place to draw squares now. Call createWorld() once in your init() function, before the game loop starts. Then update the game loop from the previous tutorial with the following code, so it loops over the new game objects and draws them on the screen:

function gameLoop(timeStamp) {
    // Calculate how much time has passed
    let secondsPassed = (timeStamp - oldTimeStamp) / 1000;
    oldTimeStamp = timeStamp;

    // Loop over all game objects
    for (let i = 0; i < gameObjects.length; i++) {
        gameObjects[i].update(secondsPassed);
    }

    // Clear the canvas
    context.clearRect(0, 0, canvas.width, canvas.height);

    // Draw all game objects
    for (let i = 0; i < gameObjects.length; i++) {
        gameObjects[i].draw();
    }

    window.requestAnimationFrame(gameLoop);
}

As you can see, update() and draw() are no longer called just once per frame. They're called once for every object on the screen, every frame.

This way the implementation of update() and draw() is object-specific. For the game loop, it doesn't matter what kind of objects you're trying to draw, as long as they have an update() and a draw() function.

For the square, that means drawing a simple square and moving it in a straight line. But imagine other types of objects that have their own implementation of the two functions and have behavior and looks of their own. This game loop can handle them all.

By the way, did you notice that the 'use strict' line from the earlier tutorials is missing from these new classes? That's because code inside a class is always in strict mode. So, there's no need to add 'use strict' to these classes yourself. Keep it at the top of your script for the rest of your code.

Take a look at the result:

You can see a bunch of squares getting drawn now. They each have their own starting position and move in a different direction, just as defined in the createWorld() function. You can tweak the numbers in createWorld() to create different squares. The demo starts over every 5 seconds, so the squares don't fly off the canvas. In your own version, they do fly off. Later in this tutorial, you'll keep them inside the canvas.

Why do you need collision detection?

The squares may overlap as they move, but nothing happens when they do. It would be cool if the squares could interact and behave like actual solid objects and bounce off of each other. To make this happen, they first need to know they're colliding with one another. That's where collision detection comes in. It's how your game knows when a bullet hits an enemy, a player lands on a platform or a ball touches a wall.

Collision detection is the art of detecting whether two objects are colliding, or have collided since the last frame. It's the first step in implementing physics in your game. The second step, making the objects react, is called collision response. You'll build both in this tutorial.

Check for collisions between objects

The squares are moving on the screen, but there's no interaction yet. It's like they don't notice each other. Let's do something about that.

You are going to check for collisions between the moving objects. That requires you to loop over all objects and check if any of them overlaps with another. You'll need a nested for loop for this. Take a look at the example:

function detectCollisions() {
    let obj1;
    let obj2;

    // Reset the collision state of all objects
    for (let i = 0; i < gameObjects.length; i++) {
        gameObjects[i].isColliding = false;
    }

    // Start checking for collisions
    for (let i = 0; i < gameObjects.length; i++) {
        obj1 = gameObjects[i];
        for (let j = i + 1; j < gameObjects.length; j++) {
            obj2 = gameObjects[j];

            // Compare object1 with object2
            if (rectIntersect(
                obj1.x, obj1.y, obj1.width, obj1.height,
                obj2.x, obj2.y, obj2.width, obj2.height
            )) {
                obj1.isColliding = true;
                obj2.isColliding = true;
            }
        }
    }
}

First, the function sets isColliding back to false for all objects, so a square that no longer overlaps anything turns blue again. Then it checks every object against every other object to see if they overlap. The inner loop starts at j = i + 1, so it skips every pair that has already been checked. There's no need to check the same pair twice. If they overlap the first time, they'll overlap the second time too. And of course, you don't have to check an object against itself. It would always overlap.

The function calls rectIntersect(), which you'll write later on, for every combination of objects. When it finds a collision, it sets isColliding to true for both objects involved.

Remember the square's draw() function? It will react to isColliding and draw the square in a different color. You can easily see when two objects overlap.

When do you check for collisions?

Just like you update all your game objects before you draw them, you also want to update them before checking for collisions. This way you'll always check for overlapping objects in their most recent state. If you do it the other way around and check for collisions before updating, you'll be checking for overlap using the positions from the previous frame. You'll always be one frame behind.

Another option would be to do the collision check in the right order, but one object at a time. You would update object-a, check object-a for overlap with all other objects, update object-b, check object-b for overlap with all other objects, and so on. This can go wrong too. Imagine object-a collides with object-b right after object-a's position is updated. The system would detect a collision, even though there might not have been one if object-b had moved as well. That's why you always have to update all objects before doing a collision check.

The correct order for your game loop is: update, collision check, clear canvas, draw. So, call detectCollisions() right after the loop that updates all game objects. Your game loop now has four steps:

Rectangle collision detection

The last piece of the puzzle is the rectIntersect() function. You can use it to check if two rectangles overlap. Rectangles that aren't rotated are called axis-aligned bounding boxes, or AABBs for short. Checking for overlap between two of them is pretty simple and straightforward. Two rectangles don't overlap when one of them is completely to the left of the other, to the right of it, above it or below it. That's four checks:

function rectIntersect(x1, y1, w1, h1, x2, y2, w2, h2) {
    // Check x and y for overlap
    if (x2 > w1 + x1 || x1 > w2 + x2 || y2 > h1 + y1 || y1 > h2 + y2) {
        return false;
    }
    return true;
}

The function takes the top-left corner, the width and the height of both rectangles. w1 + x1 is the right edge of the first rectangle, so x2 > w1 + x1 is true when the second rectangle lies completely to the right of it. The other three checks work the same way. On the canvas, the y-axis points down, so y2 > h1 + y1 means the second rectangle is completely below the first.

If none of the four checks is true, the rectangles overlap. This works when they partly overlap, and even when a small rectangle lies completely inside a larger one. Drag the rectangles on the canvas below to see which checks rule out a collision:

With this piece of code in place, you can finally check out the result. Here are the moving squares again, but this time they react to each other.

When a collision is detected, isColliding is set to true. This makes the squares turn red. You can clearly see when two objects overlap now.

Circle collision detection

You now have a method for checking collisions between unrotated rectangles. But what if you want to do the same for circles? Well, that's not that hard either.

Imagine you have two circles, each with their own radius. They're placed some distance apart. The circles overlap if the distance between their centers is smaller than the sum of their radii. Since circles are round, this even works when the objects rotate. They don't have to be axis-aligned. Drag one of the circles to see how the distance between the centers changes compared to the sum of the radii:

You can calculate the distance between two points with the following formula:

c = sqrt((x1 - x2)2 + (y1 - y2)2)

Δx is x1 - x2 and Δy is y1 - y2. If you think of them as the two short sides of a right triangle, the formula applies the Pythagorean theorem to compute c, the length of the straight line between the two points.

So, if this distance is less than or equal to the radius of circle-a plus the radius of circle-b, the circles overlap or touch. This principle is used in the following function:

function circleIntersect(x1, y1, r1, x2, y2, r2) {
    // Calculate the squared distance between the two centers
    let squareDistance = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);

    // When the distance is less than or equal to the sum
    // of the two radii, the circles touch or overlap
    return squareDistance <= ((r1 + r2) * (r1 + r2));
}

As you can see, the formula is tweaked a bit. Multiplication is faster than taking the square root with Math.sqrt(), so the code skips the square root and compares the squared distance with the sum of the radii multiplied by itself. Both numbers are positive, so if one is smaller than the other, its square is smaller too. The outcome stays the same, but the performance is better.

To try it out, you'll need a circle class, just like the one for the square. Note that for a circle, x and y are the center, while for the square they're the top-left corner:

class Circle extends GameObject {
    constructor(context, x, y, vx, vy) {
        super(context, x, y, vx, vy);

        // Set default radius
        this.radius = 25;
    }

    draw() {
        // Draw a simple circle
        this.context.fillStyle = this.isColliding ? '#ff8080' : '#0099b0';
        this.context.beginPath();
        this.context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
        this.context.fill();
    }

    update(secondsPassed) {
        // Move at the current velocity
        this.x += this.vx * secondsPassed;
        this.y += this.vy * secondsPassed;
    }
}

In createWorld(), create circles instead of squares. Then, in detectCollisions(), call circleIntersect() with the position and radius of both objects, instead of rectIntersect(). Here is the same example as before, but with circles this time:

Circle and rectangle collision

Circles and rectangles often end up in the same game: a ball and a paddle, or a round player and a wall. To check if a circle and a rectangle overlap, find the point in the rectangle that's closest to the center of the circle. If that point lies inside the circle, the two shapes overlap.

function circleRectIntersect(cx, cy, r, x, y, w, h) {
    // Find the point in the rectangle closest to the circle
    let closestX = Math.max(x, Math.min(cx, x + w));
    let closestY = Math.max(y, Math.min(cy, y + h));

    // Compare its distance to the radius
    let dx = cx - closestX;
    let dy = cy - closestY;
    return dx * dx + dy * dy <= r * r;
}

Math.max() and Math.min() keep the coordinates of the center within the edges of the rectangle. That's called clamping, and the result is the closest point. If the center is to the left of the rectangle, the closest x is the left edge. If the center is between the left and right edges, the closest x is the x of the center itself. The same goes for y. When the center of the circle is inside the rectangle, the closest point is the center, and the distance is 0. Drag the circle or the rectangle to see the closest point:

The closest point also tells you where the circle hits the rectangle. The line from that point to the center of the circle shows which way the circle bounces. If your game has both circles and rectangles, detectCollisions() has to call the right function for each pair. Check what shape each object is, for example with obj1 instanceof Circle.

Check if a point is inside a shape

Collision detection also works for the mouse. A click or a tap is just a point, so checking if it hits a shape is even easier than checking two shapes:

function pointInRect(px, py, x, y, w, h) {
    return px >= x && px <= x + w && py >= y && py <= y + h;
}

function pointInCircle(px, py, cx, cy, r) {
    let dx = px - cx;
    let dy = py - cy;
    return dx * dx + dy * dy <= r * r;
}

The demos above use these two functions to find out which shape you grab. Pass them the position of the mouse in canvas coordinates. The tutorial about setting up an HTML5 canvas explains how to get that position on a scaled canvas. For a shape that you draw with a path, the canvas context also has isPointInPath(), as explained in the tutorial about drawing shapes on the canvas.

Want to drag objects around, or let the player tap them on a phone? Learn how in the JavaScript game controls tutorial.

What about other shapes?

This tutorial covers collision detection for rectangles, circles and points. But what if your game objects consist of other, more complex shapes or even images, and you want to perform collision checks between them?

For rotated rectangles and other convex polygons, the usual method is the Separating Axis Theorem, or SAT. A convex polygon is a shape without any dents, like a triangle or a hexagon. The idea is simple: if you can draw a straight line between two shapes, they don't overlap. SAT looks for such a line parallel to one of the edges of either shape. It takes more math, but it's what most physics engines use under the hood. Here's a website that covers collision detection for a lot of different shapes, including polygons and lines.

In general, more complex shapes make collision detection more difficult. And for images, you could apply pixel-perfect collision detection. The downside of this is that it's a super CPU-heavy operation. Imagine having to check every pixel where the two images overlap. It would be a heck of a job.

That's why, to make things easier and put less stress on your system, developers often use hitboxes to detect collisions between game objects with complex shapes. Hitboxes use only basic geometric shapes, like the rectangles and circles covered in this tutorial. So, before you start building support for all kinds of complex shapes, try to think of a simple way to achieve the same effect, with basic shapes and hitboxes.

What are hitboxes and how do you use them?

Hitboxes are imaginary geometric shapes around game objects that are used for collision detection. Imagine you have a player character. Instead of checking its arms and legs for collisions, you just check a big imaginary rectangle around the player.

You could simply use the rectangle collision function you used before to check the hitboxes for collisions. It's far less CPU-intensive and makes supporting complex shapes in your game much easier. In some special cases, you could even use multiple hitboxes per game object. It would still outperform the pixel-perfect solution.

The demo below shows the difference, with a thorn that's an actual asset from our game Pixi Pop. A circle follows your mouse or finger. Move it over the thorn and switch between the three types of collision detection:

Check

Each type has its own advantages and disadvantages:

  1. One hitbox - Much better performance, but the collision detection can be pretty imprecise. In many game scenarios, though, this doesn't really matter.
  2. Multiple hitboxes - Less efficient than a single hitbox, but it still outperforms the pixel-perfect variant. And you can support complex shapes. This is a nice option to use for important game objects that need some extra precision, like the player character mentioned earlier. You could make one hitbox for the body and separate ones for the arms, legs and head.
  3. Pixel-perfect - Super precise collision detection, but it requires some serious system resources. In most cases, this is overkill.

In Pixi Pop, the thorn uses multiple hitboxes, because precise collision detection is important there and not too many other game tasks are running at the same time. Just pick the option that suits your game best.

React to collisions with physics

You now have game objects that can detect a collision and change color. But wouldn't it be much cooler if the objects bounced off each other, like real-life objects? It's time to apply some physics to your game.

To change the velocity of the moving objects, you'll need to find out in what direction and at what speed the collision took place. You can then apply a change in velocity to the colliding objects. The principles behind this work best for circles, but they also work reasonably well for rectangles. The code in the next two sections goes into detectCollisions(), in the inner loop, right after a collision between obj1 and obj2 is detected.

Find the direction and speed of the collision

Imagine the following collision between two game objects. Both objects have a speed and direction of their own. They don't hit each other head-on, but just happen to collide while each moves along its own path. Drag the objects around, or drag the tip of an arrow to change its velocity. The buttons show each step of the calculation in this section:

Step

You'll want to find out the speed and direction of the collision so you can apply it to the velocity of the game objects. Start by creating a vector for the collision that took place. This vector is nothing more than the difference in x and y between the two colliding objects. Think of it as an arrow with a length and a direction. For vectors, the length is also called the magnitude. Calculate the collision vector like this:

let vCollision = {x: obj2.x - obj1.x, y: obj2.y - obj1.y};

Choose Collision vector in the demo to see it. In this case, the magnitude is equal to the distance between the two colliding objects. It has nothing to do with speed yet. But you can use the direction of the vector. To get just the direction, you divide the vector by its length.

Let's first calculate the length of the collision vector, which is the distance between the two objects. You can use the same formula as you did before to calculate the distance between two colliding circles. This time, you need the real distance to divide by, so you can't skip the square root:

let distance = Math.sqrt(
    (obj2.x - obj1.x) * (obj2.x - obj1.x) +
    (obj2.y - obj1.y) * (obj2.y - obj1.y)
);

Now use the distance to compute a normalized collision vector. This normalized vector, or collision normal, has the same direction as the collision vector, but its length is 1. A vector with a length of 1 is also called a unit vector. You can calculate the normalized vector like this:

let vCollisionNorm = {
    x: vCollision.x / distance,
    y: vCollision.y / distance
};

This will basically leave you with just a direction for the collision. Choose Normal in the demo to see it.

You now have a direction. This is the direction in which the collision took place. All you need now is the collision speed and you'll be able to calculate how the velocity of the objects will be affected by the collision. You can calculate the speed of the collision like this:

let vRelativeVelocity = {x: obj1.vx - obj2.vx, y: obj1.vy - obj2.vy};
let speed =
    vRelativeVelocity.x * vCollisionNorm.x +
    vRelativeVelocity.y * vCollisionNorm.y;

The first line of the example code creates another vector with the relative velocity of the objects. It's the velocity of the first object as seen from the second one, as if the second one were standing still. You can read more about relative velocities here. It's easier to understand in the demo: choose Relative velocity. The demo draws the velocity of the first object, and the velocity of the second object turned around, tip to tail. Together they make the relative velocity vector.

Next, calculate the dot product of the relative velocity and the collision normal. That's the second line of the code: it multiplies the two x values, multiplies the two y values and adds them up. Because the collision normal has a length of 1, the dot product gives you the length of the projection of the relative velocity onto the normal. Think of the projection as the shadow that the relative velocity casts on the collision normal. In other words, the dot product tells you how much of the relative velocity points in the direction of the collision.

If the objects are moving apart, the projection points the opposite way from the normal, so the dot product gives you its length as a negative number. Choose Speed in the demo to see the projection. Learn more about dot products here. Learn more about vector operations here.

The dot product is equal to the speed of the collision. So that's it. You've got the speed and direction of the collision between the two objects. You can apply this to the velocity of the game objects and make them bounce off of each other.

Make the objects bounce off each other

The speed of the collision can be positive or negative. When it's positive, the objects are moving toward each other. When it's negative, they're moving apart. In that case, there's nothing else to do. They'll separate on their own. The continue statement skips the rest of the code for this pair, and the loop moves on to the next one:

if (speed < 0) {
    continue;
}

In the other case, when the objects are moving toward each other, apply the speed in the direction of the collision. Both objects get the same change in velocity from the collision, but in opposite directions. Subtract it from the velocity of one object and add it to the velocity of the other.

obj1.vx -= (speed * vCollisionNorm.x);
obj1.vy -= (speed * vCollisionNorm.y);
obj2.vx += (speed * vCollisionNorm.x);
obj2.vy += (speed * vCollisionNorm.y);

That's it. By multiplying the direction by the speed, you get the collision velocity. And the velocities of the objects involved are now updated with it. Your game objects should bounce in a natural-looking way.

Now take a look at the result:

Elastic collisions with mass, impulse and momentum

If you like, you can take the physics even further and bring mass into the equation by calculating the collision impulse from the speed and the masses. The impulse is the push the two objects give each other when they collide. Both get the same push, but in opposite directions. The same push changes the velocity of a light object more than that of a heavy one.

At the same speed, heavy objects have more momentum, which is mass times velocity, and push light ones aside. This is still an elastic collision: the objects bounce off each other without losing any energy. The following code replaces the four lines from the previous section:

let impulse = 2 * speed / (obj1.mass + obj2.mass);
obj1.vx -= (impulse * obj2.mass * vCollisionNorm.x);
obj1.vy -= (impulse * obj2.mass * vCollisionNorm.y);
obj2.vx += (impulse * obj1.mass * vCollisionNorm.x);
obj2.vy += (impulse * obj1.mass * vCollisionNorm.y);

If both objects have a mass of 1, the impulse is just equal to the speed. Otherwise, you basically split the speed into many small parts. Each object's velocity changes by a number of parts equal to the other object's mass. So when a light object hits a heavy one, the light one gets a lot of parts, and the heavy one only a few.

Don't forget to add mass to your game objects. Add a mass parameter to the constructors of GameObject, Circle and Square, and store it in this.mass in GameObject. Then pass a mass for each object in createWorld(). The complete code at the end of this tutorial shows how.

Here's the bouncing example again, modified to create a lot of small circles with a mass of 1 and two larger ones with a mass of 100. The small circles are placed at random positions, so some of them might already overlap when the demo starts.

In the example, the big circles have a very large mass compared to the smaller circles. They push everything out of their way. But when the two heavy objects hit each other, they bounce off each other too.

Get the heading of the objects

The objects are constantly colliding and changing direction. For a game, it would be helpful to know exactly which direction an object is moving in, so you can add rotated textures or build game logic based on it. Let's calculate it!

You can easily get the direction each object is moving in with Math.atan2(). Give it the y and x coordinates of a point, and it returns the angle between the x-axis and the line from (0, 0) to that point. Note that y comes first. Use the y- and x-velocity as the point, and you get the direction the object is moving in.

An angle of 0 means the object is moving to the right. On the canvas, the y-axis points down, so the angle increases clockwise: Math.PI / 2 is straight down. The result is in radians, from -Math.PI to Math.PI. To convert it to degrees, multiply it by 180 and divide it by Math.PI. Here's an example that calculates the angle in your update() function:

update(secondsPassed) {
    // Move at the current velocity
    this.x += this.vx * secondsPassed;
    this.y += this.vy * secondsPassed;

    // Calculate the angle (vy before vx)
    let radians = Math.atan2(this.vy, this.vx);

    // Convert to degrees
    let degrees = 180 * radians / Math.PI;
}

To use the angle later in your game, store it in a property, like this.angle. The next tutorial in this series, on images and sprites, does that to draw rotated images.

For now, rotation isn't very interesting, since a circle looks the same at every angle. You wouldn't notice a change in rotation. So, here's a simple implementation that shows the movement direction of the objects with a little line. The higher the speed, the longer the line. This represents the heading vector of the object. You can easily add this visualization to your drawing function.

draw() {
    // Other drawing operations
    // ...

    // Draw heading vector
    this.context.beginPath();
    this.context.moveTo(this.x, this.y);
    this.context.lineTo(this.x + this.vx, this.y + this.vy);
    this.context.stroke();
}

The demo below adds an arrowhead to each line, so you can see which way a circle is moving:

Add gravity

The examples so far contain just a basic implementation of physics. You could add more to your game to make it look even more realistic. Things like gravity or restitution aren't too hard to implement. Let's start by adding gravity to your simulation.

For gravity, add the gravitational acceleration, multiplied by the time that has passed since the last frame, to the y-velocity vy of your objects every frame. On Earth, it's about 9.81 meters per second per second. Your game works in pixels, though. If one meter on the screen is 20 pixels, the gravitational acceleration g is 9.81 * 20, or about 196 pixels per second per second. Define g once, at the top of your script, and apply it inside the update() function of your game objects. Every second, g is added to vy, which makes the object fall faster and faster.

// Gravity in pixels per second per second
const g = 9.81 * 20;

update(secondsPassed) {
    // Apply acceleration
    this.vy += g * secondsPassed;

    // Move at the current velocity
    this.x += this.vx * secondsPassed;
    this.y += this.vy * secondsPassed;
}

Update the velocity before you update the position. Every frame, your game calculates the new velocity and position from the old ones. This is called integration. Now that gravity changes the velocity every frame, the order matters. If you update the position first, the object moves at last frame's velocity. It ends up a tiny bit too high, but still moving just as fast. A higher position at the same speed means more energy, so a bouncing ball slowly gains energy and bounces a bit higher every time. If you update the velocity first, the energy doesn't grow, and the motion stays stable. This order is called semi-implicit Euler. This article about integrating the equations of motion explains it in more detail.

Keep objects inside the canvas

Without anything to stop them, your objects fall off the screen and never come back. To really see gravity at work, keep your objects inside the canvas. The canvas then acts like a closed box that the objects bounce off.

You can make it happen with a simple adjustment. Call the following function right after detectCollisions(), so object-edge collisions are checked together with the object-object collisions.

// Define the edges of the canvas
const canvasWidth = 750;
const canvasHeight = 400;

function detectEdgeCollisions() {
    let obj;
    for (let i = 0; i < gameObjects.length; i++) {
        obj = gameObjects[i];

        // Check for left and right
        if (obj.x < obj.radius) {
            obj.vx = Math.abs(obj.vx);
            obj.x = obj.radius;
        } else if (obj.x > canvasWidth - obj.radius) {
            obj.vx = -Math.abs(obj.vx);
            obj.x = canvasWidth - obj.radius;
        }

        // Check for top and bottom
        if (obj.y < obj.radius) {
            obj.vy = Math.abs(obj.vy);
            obj.y = obj.radius;
        } else if (obj.y > canvasHeight - obj.radius) {
            obj.vy = -Math.abs(obj.vy);
            obj.y = canvasHeight - obj.radius;
        }
    }
}

This function checks for objects beyond the edges of the canvas and moves them back inside. The part of their velocity that's perpendicular to the wall they hit is then flipped, so they move away from it. This only works for circles because it uses the radius. For a square, use its width and height instead.

It's a very basic implementation and only works this way because the edges of the canvas are predefined straight lines. You could do the same with a circle-line collision and set up dynamic lines, but that would be much more complicated than this quick example.

Absorb impact by implementing restitution

If you run the code you have so far, you'll see the game objects never come to rest. They will keep bouncing and bouncing and never lose any energy. To counter this, you can implement restitution.

Restitution describes how much speed is left after each collision. It affects how bouncy objects are. The ratio of the speed after a bounce to the speed before it is called the coefficient of restitution, or COR. Here's what different COR values mean:

  • COR of 0 - The object absorbs all its energy on impact, like a bag of sand hitting the floor.
  • COR between 0 and 1 - The object loses some speed with every bounce. The closer the COR is to 0, the less it bounces.
  • COR of 1 - The object is perfectly elastic, like a super bouncy ball.
  • COR above 1 - The object would gain speed with every bounce, which is impossible in real life.

Give every game object a restitution in the constructor of GameObject, like this.restitution = 0.9;. Then multiply the speed by it in each of the four checks in detectEdgeCollisions():

// Check for left and right
if (obj.x < obj.radius) {
    obj.vx = Math.abs(obj.vx) * obj.restitution;
    obj.x = obj.radius;
}
// ...

This will make the objects lose some energy after each bounce. It will make the simulation much more realistic. Without it, the objects would bounce on forever.

To complete the implementation of restitution, you'll need to apply it to the objects involved in an object-object collision as well. In the code, the COR is simply called restitution. Use it in the impulse calculation from the section on mass and impulse: 2 * speed / (obj1.mass + obj2.mass). Replace the 2 with (1 + restitution). With a restitution of 1, that's still 2, and the bounce stays elastic. With a lower restitution, every collision now loses some energy.

When two objects with different restitution settings collide, for instance when a bouncy ball hits a bag of sand, the lower restitution is used. In this case, the ball won't bounce off the bag. The collision uses the restitution of the bag.

function detectCollisions() {
    // ...
    // Use the lower restitution of the two objects
    let restitution = Math.min(obj1.restitution, obj2.restitution);

    // Apply restitution to the impulse
    let impulse = (1 + restitution) * speed / (obj1.mass + obj2.mass);
    // ...
}

The following live example shows gravity, restitution and collisions with the canvas edges in action. Pick a gravity setting and a restitution value, and click or tap the canvas to drop more circles:

Live demo The code updates as you play around
Gravity
restitution
// Gravity in pixels per second per second
const g = 9.81 * 20; // Earth

// In the constructor of GameObject
this.restitution = 0.9;

You can easily tweak the variables to create different scenarios. Change the gravity to simulate being on the Moon or another planet, or lower the restitution to make the objects bounce less, like bags of sand. With a restitution of 1, nothing loses energy, and the circles keep bouncing forever. But the energy does shift between the circles. The demo also has two big circles that are 100 times heavier than the small ones. They move about as fast as the small ones, so at the start, the big circles hold most of the energy. Every collision passes a bit of it on to the small circles. After a while, the big circles lie almost still at the bottom, and the small ones race around with all that energy.

Now drop a lot of circles and look closely at the bottom of the canvas. The circles at the bottom of the pile slowly sink into each other. Let's fix that.

Push overlapping objects apart

Changing the velocity only makes the objects move apart over the next few frames. In the meantime, they still overlap. Usually that's fine, but with gravity, the objects at the bottom of a pile get pushed down every frame, and they sink into each other. Sometimes two objects even get stuck together.

The fix is to move the objects apart as soon as you detect the collision. Move them along the collision normal, just far enough that they touch but no longer overlap. The lighter object moves more:

function separate(obj1, obj2, vCollisionNorm, distance) {
    // How far the circles overlap
    let overlap = obj1.radius + obj2.radius - distance;

    // Move them apart along the collision normal
    let totalMass = obj1.mass + obj2.mass;
    obj1.x -= vCollisionNorm.x * overlap * obj2.mass / totalMass;
    obj1.y -= vCollisionNorm.y * overlap * obj2.mass / totalMass;
    obj2.x += vCollisionNorm.x * overlap * obj1.mass / totalMass;
    obj2.y += vCollisionNorm.y * overlap * obj1.mass / totalMass;
}

Call separate() in detectCollisions(), right after you calculate the collision normal and before you check the speed. Overlapping objects have to be pushed apart, even when they're already moving away from each other:

// Push them apart, so they don't sink into each other
separate(obj1, obj2, vCollisionNorm, distance);

Turn it on and off in the demo below. Click or tap the canvas to add more circles and build a big pile:

separate()

This is a simple form of what physics engines call position correction. It isn't perfect: a big pile can still wobble a bit. For most games, it's good enough.

Ways to improve performance

You might not really notice it right now, but with many game objects on screen at once or with more complex shapes, the collision detection and response can put some serious stress on your system. As a game grows, it's easy to overlook even the obvious things. Here are some tips that might help to improve performance:

  • Use a broad phase - Only compare objects that are close enough to possibly collide. You could divide your game world into a grid and only compare objects in the same or neighboring cells. This quick check finds the pairs that might collide. Then the narrow phase, the exact checks from this tutorial, only has to look at those pairs. Learn more about broad-phase collision detection here.
  • Clean up the object list - Remove objects when they leave the game world or are destroyed.
  • Use an object pool - Reuse objects instead of creating new ones. Constantly spawning new objects, like bullets or particles, leaves garbage behind. Now and then the browser cleans it up, and that can cost a frame. So put the objects you no longer need into a pool, and take one out when you need it. The tutorial on smooth canvas animation explains why garbage can make your game stutter.
  • Skip static objects - Leave background decoration out of the loop, since it never reacts to collisions. Also leave out checks between two objects that don't move.
  • Use hitboxes - As explained before, they're a great way of optimizing collision detection and simplifying complex shapes.
  • Keep it simple - Adjust collision detection and physics to fit your game. You don't need a full physics engine when all you want to do is build a tic-tac-toe game. That's a bit of an extreme example, but you get the point. Strip your logic down to what you actually need.

Handle fast-moving objects

One final note about collision detection. The above examples detect collisions by checking if two objects overlap. This is a good solution in many cases. But it won't work when your objects move at high speed. When an object moves farther in a single frame than the size of your smallest object, the overlap check can miss the collision. The objects pass through each other. This is called tunneling.

Imagine you check for a collision between a bullet and an enemy in your game. In the first frame, the bullet is in front of the enemy. There's no overlap, so there's no hit. In the next frame, the bullet has moved so far that it's now behind the enemy. There's still no overlap, so no collision. But the bullet did pass right through the enemy, and there should've been a hit.

The demo below shows this happening. Every frame, the bullet takes a big jump forward, so it never overlaps with the enemy. Switch the check to Path to fix it:

Check

You need another approach for this kind of situation. The simplest way is to limit the speed of your game objects. In other words, make sure an object never moves farther in one frame than the size of the smallest game object, so it can't pass through. For many types of games this is a great solution and it requires minimal effort. A fixed timestep in your game loop helps too, because then every step is equally small, no matter the frame rate.

The other solution is to check for collisions along the projected path instead of at the current position of the two objects. Think of the path of a bullet as a line. The length of the line is equal to the distance the bullet travels in one frame. Now you can use a line-to-rectangle or line-to-circle collision check to find out if the bullet hits another object during that frame. The website we mentioned earlier, on collision detection for a lot of different shapes, covers both checks. For large bullets, you could use a rectangle instead of a line.

This is a simplified solution. You will probably run into other problems along the way, like finding the point of impact or determining which object in a larger group is hit first. But the steps mentioned here might help to point you in the right direction.

Common questions about collision detection

How does collision detection work in games?

Every frame, the game moves all objects and then checks which ones overlap. For simple shapes like rectangles and circles, that's a few comparisons per pair of objects. When two objects overlap, the game reacts: it changes their speed, takes away a life or plays a sound. That second part is called collision response.

What is AABB collision detection?

AABB stands for axis-aligned bounding box: a rectangle that isn't rotated. Two of them overlap unless one is completely to the left of the other, to the right of it, above it or below it. That's the rectIntersect() function in this tutorial, and it's one of the fastest checks there is.

How do I check collisions between rotated rectangles?

Use the Separating Axis Theorem, or SAT. For two rectangles, you check four directions: along the width and along the height of each one. For each direction, take the dot product of that direction with the x and y of every corner of both rectangles. The lowest and highest results give each rectangle a range. The rectangles collide when their ranges overlap in all four directions. Still, it's always worth asking yourself what kind of collision detection you really need. If an object is about as wide as it is tall, for example, you could use a circle hitbox instead.

Why do my objects stick together after a collision?

Usually because they still overlap in the next frame. The collision code sees the overlap again and makes them bounce a second time, which sends them back toward each other. To prevent this, the code checks the speed of the collision first. When speed is below 0, the objects are already moving apart, and the bounce is skipped. Also push overlapping objects apart, as explained above, so they no longer overlap in the next frame.

Should I use a physics engine like Matter.js?

For a game with simple shapes, the code in this tutorial is all you need, and you know exactly what it does. If you need rotating bodies, friction, joints or tall stacks of objects that stay put, a 2D physics engine like Matter.js or Planck.js saves you a lot of work.

How does collision detection work in PixiJS or three.js?

PixiJS draws your game, but it doesn't handle collisions between your sprites for you. You can use the functions from this tutorial with the positions and sizes of your sprites. three.js is for 3D, and it has a Box3 and a Sphere with intersectsBox() and intersectsSphere(). They work just like the rectangle and circle checks here, with a third axis. For real physics in 3D, add a physics engine.

The complete code

Here's everything put together. Lots of light circles and two heavy ones bounce around inside the canvas, with gravity and restitution. Overlapping circles are pushed apart, and you can click or tap the canvas to drop more circles. You can use it as a starting point for your own game:

What's next?

That's all for now on collisions and physics, from simple overlap checks to fast-moving objects. Your collision check is in place and your game objects are now interacting with each other in a semi-natural way. If you have any comments or questions, feel free to post them in the comment section below. You can

In the next part of this series, you'll learn how to use images in your game and create sprite animations.