Do you like my tutorials?

Then consider supporting me on Ko-fi

Talking about Game development, HTML5, Javascript, Phaser and TypeScript.

I had a lot of positive feedback with to post Understanding physics continuous collision detection using swept AABB method and Minkowski sum, but in the example there was only one moving body, while the other did not move.

What if there are two moving bodies? Does Minkowski sum still apply?

Let’s see what happens when two bodies move:

In the above picture, Body A is moving towards Body B, and Body Bis moving towards Body A, and it would be too difficult to say when – or even if – these two bodies collide, if we don’t pretend only one body is moving, with a relative velocity which we can get by subtracting, for instance, Body B velocity from Body A velocity, this way:

Now we can say we have only one body moving, and we can build the Minkowski Sum line explained in previous step:

Allright, we also have a collision point. Let’s strip everything but velocity vector and collision point:

If we define movement start with zero and movement end with one, collision point will be somewhere between 0 and 1 on the velocity vector.

This is the amount of velocity vector needed by both bodies to collide, this way:

So we can start the simulation, which at low velocities works well both with Arcade Physics and Swept AABB:

Click or touch the canvas to make simulation start. You’ll probably won’t notice differences between Arcade Physics and Swept AABB.

Things change at higher speeds:

Click or touch the canvas to make simulation start. While Swept AABB still handles the collision, Arcade Physics fails.

Let’s see the source code of this experiment, built around a simple class:

index.html

The webpage which hosts the game, just the bare bones of HTML.

Also look at the thegame div, this is where the game runs.

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
    <head>
        <script src = "main.js"></script>
    </head>
<body>
    <div id = "thegame"></div>
</body>
</html>

main.ts

The main TypeScript file, the one called by index.html.

Here we import most of the game libraries and define Scale Manager object.

Here we also initialize the game itself.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// MAIN GAME FILE
 
// modules to import
import Phaser from 'phaser';
import { PlayGame} from './playGame';
 
// object to initialize the Scale Manager
const scaleObject: Phaser.Types.Core.ScaleConfig = {
    mode: Phaser.Scale.FIT,
    autoCenter: Phaser.Scale.CENTER_BOTH,
    parent: 'thegame',
    width: 800,
    height: 416
}
 
// game configuration object
const configObject: Phaser.Types.Core.GameConfig = {
    type: Phaser.AUTO,
    scale: scaleObject,
    scene: [PlayGame],
    physics: {
        default: 'arcade'
    }
}
 
// the game itself
new Phaser.Game(configObject);

playGame.ts

In playGame class we place all stuff on the canvas, make all stuff work and handle user input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// THE GAME ITSELF
 
import { PhaserSweptAABB } from "./phaserSweptAABB";
import PhysicsBox from "./physicsBox";
 
// this class extends Scene class
export class PlayGame extends Phaser.Scene {
 
    // these are my physics box and wall
    box: PhysicsBox;
    wall: PhysicsBox;
 
    // these are the Arcade physics box and wall
    arcadeBox: Phaser.Physics.Arcade.Sprite;
    arcadeWall: Phaser.Physics.Arcade.Sprite;
 
    // speed of the projectile, in pixels per second
    projectileSpeed: number = 4000;
 
    // speed of the wall, in pixels per second
    wallSpeed: number = -2000;
 
    // our Swept AABB class
    AABB: PhaserSweptAABB;
 
    // constructor
    constructor() {
        super({
            key: 'PlayGame'
        });
    }
 
    // method to be called once the class preloads
    preload(): void {
 
        // load graphic assets
        this.load.image('dot', 'assets/dot.png');
        this.load.image('wall', 'assets/wall.png');
 
    }
 
    // method to be called once the class has been created
    create(): void {
 
        // create a new instance of Swept AABB class
        this.AABB = new PhaserSweptAABB();
 
        // creation of my physics box and wall
        this.box = new PhysicsBox(this, 20, 128, 'dot');
        this.wall = new PhysicsBox(this, 780, 128, 'wall');
 
        // creation of Arcade box and wall
        this.arcadeBox = this.physics.add.sprite(20, 288, 'dot');
        this.arcadeWall = this.physics.add.sprite(780, 288, 'wall');
 
        // set Arcade box and wall immovable
        this.arcadeWall.setImmovable(true);
        this.arcadeBox.setImmovable(true);
 
        // input listener to fire the bullet
        this.input.on('pointerdown', this.fireBullets, this);
 
        // just some text to display informaiton
        this.add.text(20, 195, 'Bullet Speed: ' + this.projectileSpeed + ' pixels/s, Wall speed: ' + this.wallSpeed + ' pixels/s', {
            fontSize: '20px'
        });
        this.add.text(20, 12, 'Swept AABB', {
            fontSize: '24px'
        });
        this.add.text(20, 380, 'Arcade Physics', {
            fontSize: '24px'
        });
    }
 
    // FIRE!!!
    fireBullets() {
 
        // fire both my box and Arcade physics box
        this.arcadeBox.setVelocity(this.projectileSpeed, 0);
        this.box.setVelocity(this.projectileSpeed, 0);
 
        // fire both my wall and Arcade physics wall
        this.arcadeWall.setVelocity(this.wallSpeed, 0);
        this.wall.setVelocity(this.wallSpeed, 0);
    }
 
    // method to be executed at each frame
    update(totalTime: number, deltaTime: number): void {
 
        // Swept AABB collider, to execute only if at least one body is moving
        if (this.box.isMoving() || this.wall.isMoving()) {
 
            // check collision time, can be any number between 0 (already colliding) and 1 (never colliding in this frame)
            let collisionTime: number = this.AABB.checkCollisionTime(this.box, this.wall, deltaTime);
             
            // update box and wall positions according to collision time
            this.box.updatePosition(deltaTime * collisionTime);
            this.wall.updatePosition(deltaTime * collisionTime);
 
            // if collision time is less than one, that is there was a collision...
            if (collisionTime < 1) {
 
                // stop both box and wall
                this.box.stopMoving();
                this.wall.stopMoving();
            }
        }
         
        // Arcade physics collider
        this.physics.world.collide(this.arcadeBox, this.arcadeWall, function(body1: Phaser.GameObjects.GameObject, body2: Phaser.GameObjects.GameObject) {
             
            // just stop both bodies
            let b1: Phaser.Physics.Arcade.Sprite = body1 as Phaser.Physics.Arcade.Sprite;
            let b2: Phaser.Physics.Arcade.Sprite = body2 as Phaser.Physics.Arcade.Sprite
            b1.setVelocity(0, 0);
            b2.setVelocity(0, 0)
        });
    }
}

physicsBox.ts

A simple extension of Phaser Sprite class to store velocity.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// PhysicsBox class extends Phaser Sprite class
export default class PhysicsBox extends Phaser.GameObjects.Sprite {
 
    // vector containing x and y velocity
    velocity: Phaser.Math.Vector2;
 
    // constructor - arguments: the scene, x and y position and texture key
    constructor(scene: Phaser.Scene, x: number, y: number, key: string) {
        super(scene, x, y, key);
 
        // add sprite to the scene
        scene.add.existing(this);
 
        // physics object has no velocity at the beginning
        this.velocity = new Phaser.Math.Vector2(0, 0);
    }
 
    // set body velocity - arguments: x and y velocity
    setVelocity(x: number, y: number): void {
 
        // update velocity property
        this.velocity.x = x;
        this.velocity.y = y;
    }
 
    // method to check if the body is moving
    isMoving(): boolean {
        return this.velocity.x != 0 || this.velocity.y != 0;
    }
 
    // method to stop the object
    stopMoving(): void {
        this.setVelocity(0, 0);
    }
 
    // method to update physics box position - arguments: the amount of milliseconds
    updatePosition(milliseconds: number): void {
 
        // adjust box velocity
        this.x += this.velocity.x * (milliseconds / 1000);
        this.y += this.velocity.y * (milliseconds / 1000);
    }
}

phaserSweptAABB.ts

The core of the script, the methods responsible of collision detection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import PhysicsBox from "./physicsBox";
 
export class PhaserSweptAABB {
 
    // method to check if two bodies collide within a certain time - arguments: the two bodies and a time
    checkCollisionTime(sprite1: PhysicsBox, sprite2: PhysicsBox, time: number): number {
         
        // determine relative speed subtracting the two speed vectors
        let relativeSpeed: Phaser.Math.Vector2 = new Phaser.Math.Vector2(sprite1.velocity.x, sprite1.velocity.y).subtract(new Phaser.Math.Vector2(sprite2.velocity.x, sprite2.velocity.y));
 
        // get movement line, from box origin to box relative destination
        let movementLine: Phaser.Geom.Line =  new Phaser.Geom.Line(sprite1.x, sprite1.y, sprite1.x + relativeSpeed.x * (time / 1000), sprite1.y + relativeSpeed.y * (time / 1000));
         
        // Minkowski rectangle built inflating the sprite bodies
        let minkowskiRectangle: Phaser.Geom.Rectangle = this.minkowskiSum(sprite1, sprite2);
 
        // array to store all intersection points between movement line and Minkowski rectangle
        let intersectionPoints: Phaser.Geom.Point[] = [];
         
        // get all intersection points between movement line and Minkowski rectangle, then store them into intersectionPoints array
        Phaser.Geom.Intersects.GetLineToRectangle(movementLine, minkowskiRectangle, intersectionPoints);
 
        // different cases according to intersection points vector length
        switch (intersectionPoints.length) {
 
            // no intersection points: return 1, that is objects can move for the entire interval
            case 0:
                return 1;
 
            // only one intersection point: this is the collision point   
            case 1:
 
                // get the collision line
                let collisionLine: Phaser.Geom.Line = new Phaser.Geom.Line(sprite1.x, sprite1.y, intersectionPoints[0].x, intersectionPoints[0].y);
                 
                // return the ratio between collision line and movement line
                return Phaser.Geom.Line.Length(collisionLine) / Phaser.Geom.Line.Length(movementLine);
             
            // more than one intersection point: collision point is the closest to moving body
            default:
                 
                // set the minimum distance to Infinity, the highest number
                let minDistance: number = Infinity;
 
                // set minimum index to zero (first element)
                let minIndex: number = 0;
 
                // looping through all instersection points
                for (let i: number = 0; i < intersectionPoints.length; i ++) {
 
                    // get distance between body and points
                    let distance: number = Phaser.Math.Distance.Between(sprite1.x, sprite2.y, intersectionPoints[i].x, intersectionPoints[i].y);
                     
                    // is distance less then minimum distance?
                    if (distance < minDistance) {
 
                        // update minimum index
                        minIndex = i;
 
                        // update minimum distance
                        minDistance = distance;
                    }
                }
 
                // get the collision line
                let collisionLine2: Phaser.Geom.Line = new Phaser.Geom.Line(sprite1.x, sprite1.y, intersectionPoints[minIndex].x, intersectionPoints[minIndex].y);
 
                // return the ratio between collision line and movement line
                return Phaser.Geom.Line.Length(collisionLine2) / Phaser.Geom.Line.Length(movementLine);
        }
    }
 
    // method to perform the Minkowski sum between two Sprites - argument: the sprites
    minkowskiSum(sprite1: PhysicsBox, sprite2: PhysicsBox): Phaser.Geom.Rectangle {
         
        // get bounding boxes
        let spriteBounds1: Phaser.Geom.Rectangle = sprite1.getBounds();
        let spriteBounds2: Phaser.Geom.Rectangle = sprite2.getBounds();
 
        // new rectangle leftmost point
        let newLeft: number = spriteBounds2.left - spriteBounds1.width / 2;
 
        // new rectangle upper point
        let newTop: number = spriteBounds2.top - spriteBounds1.height / 2;
 
        // new rectangle width
        let newWidth: number = spriteBounds1.width + spriteBounds2.width;
 
        // new rectangle height
        let newHeight: number = spriteBounds1.height + spriteBounds2.height;
 
        // return the inflated rectangle
        return new Phaser.Geom.Rectangle(newLeft, newTop, newWidth, newHeight);
    }
}

And we managed to build a continuous collision detection script capable of handling to fast moving objects. Now we have to put everything together and apply to a real world example, meanwhile download the source code of the entire project.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.