Basic Typescript Collision Detection

So in this tutorial, we're going to cover some of the basic collisions that can happen between similar shapes. Things like a circle colliding with another circle, a line colliding with another line, a rectangle colliding with another rectangle, and an oriented rectangle colliding with another oriented rectangle. The first thing we're going to do, is put together an enumeration for the collider type, and a collider interface that all the colliders in our game will implement.

enum COLLIDER {
   CIRCLE,
   RECTANGLE,
   ROTRECTANGLE,
   LINE,
   POLYGON,
   COMPOUND
}

interface iCollider {
   colliderType: COLLIDER;
   position: cVector;
}

All colliders are going to implement the iCollider interface. The colliderType attribute will let us know what kind of collider we are dealing with. It's type is the enumeration COLLIDER that we created above. If you don't know what an enumeration is, it's basically a way of defining a new type within TypeScript. The value of colliderType has to be something in the COLLIDER enumeration (like CIRCLE, or RECTANGLE). In previous tutorials, our shape interface had an x and y value. We're replacing that with a position vector in our collider. This will make it easier to do some of the trigonometry we will need to do on the position of the collider.

Circle Circle collision detection

The simplest, most computationally thrifty collision detection you can do is a circle colliding with a circle. A circle is defined by nothing more than it's center point, and a radius. From the collider's perspective, the center point is going to be the position. When we define our circle collider class, we will need to have a radius attribute as well.

class cCircleCollider implements iCollider {
   public position: cVector = new cVector();
   public radius: number = 1;

   public colliderType: COLLIDER = COLLIDER.CIRCLE;
}

The Circle Collider is only going to have one value that isn't in the iCollider interface, and that is radius. It is a very bare bones collider. Detecting a collision between two circle colliders is also fairly simple. You need to use a bit of trigonometry, but it's pretty simple.

Circle collision detection and the Pythagorean Theorem

You may remember from middle school that you can find the length of a hypotenuse by taking the square root of the of the sum of the squares of the other two sides. In middle school I remember wondering when would this ever be a useful activity. As it turns out, you use it in games all the time (and a lot of other places, I was a particularly cynical 12 year old). Finding that hypotenuse can be used to find the distance between 2 arbitrary pixels in your game. Here's how it would work in a cartesian coordinate system.

So the distance between any point and the origin (0,0) is the square root of X2 + Y2. So what if you have two points, let's call them P1 and P2, and you would like to know the distance between those two points. Well, you can get a Vector with an X and Y coordinate by subtracting P1 from P2 then get the magnitude of that new vector.

Distance between circles vs sum of the radii

Now we know how to find the distance between two center points of the circles. If this distance is greater than the sum of the two circles radii, than there has not been a collision. If the distance is less than the sum of the two radii, then there is a collision. In the image below d is the distance vector, r1 is the radius of the blue circle, and r2 is the radius of the red circle.

Coding it in TypeScript

I'm creating a Collision class that will be used to detect all of our different types of collisions. The methods in this class will be static, so you won't need to create an instance of the class to call that method. The Collision class will just be a convienent place to put all our collision functions.

class Collision {
   public static CircleCircle(a: cCircleCollider, b: cCircleCollider): boolean {
      var temp_vector: cVector = a.position.duplicate();
      _tempVector.subtract(b.position);

      if (_tempVector.magSq() <= (a.radius + b.radius) * (a.radius + b.radius)) {
         return true;
      }
      return false;
   }
}

A few things you may notice, is that I'm not taking any square roots. The reason is that I just need to make a comparison between the two lengths, not actually need to know what the lengths are. I could put in a square root for the value of the vector's magnitude, but it's a relatively slow process on the CPU. Also, if A > B, then A2 > B2, as long as A and B are both more than 0, and since I'm squaring my X and Y values, they have to be. So it all works out the same for our comparison.

You may have also notice that I've added a duplicate method to the cVector object that wasn't in the last version of the code. This allows you to make a new object with the same values as the object being passed in. It's not very complicated, I have the code below.

public duplicate = (): cVector => {
   var dup: cVector = new cVector(this.x, this.y);
   return dup;
}

So now we just need to come up with a way to test and make sure our collision detection is working. First we need a function to draw the circle.

function drawCircle(x: number, y: number, radius: number, color: string): void {
   ctx.save();
   ctx.beginPath();
   ctx.strokeStyle = color;
   ctx.lineWidth = 5;
   ctx.arc(x, y, radius, 0, 2 * Math.PI);
   ctx.stroke();
   ctx.restore();
}

We also need to change the game loop. We need to check if our two circles collide. If our circles collide we are going to draw the circles in red. Otherwise we are going to draw them blue.

var circle1: cCircleCollider = new cCircleCollider();
var circle2: cCircleCollider = new cCircleCollider();

function gameLoop() {
   requestAnimationFrame(gameLoop);
   ctx.fillStyle = "black";
   ctx.fillRect(0, 0, 1280, 720);

   if (Collision.CircleCircle(circle1, circle2)) {
      drawCircle(circle1.position.x, circle1.position.y, circle1.radius, "red");
      drawCircle(circle2.position.x, circle2.position.y, circle2.radius, "red");
   }
   else {
      drawCircle(circle1.position.x, circle1.position.y, circle1.radius, "blue");
      drawCircle(circle2.position.x, circle2.position.y, circle2.radius, "blue");
   }
}

We also need to add code to change the circles every time we hit the space bar.

function keyboardInput(event: KeyboardEvent) {
   // PRESS SPACE BAR
   if (event.keyCode == 32) {
      ResetCircles();
   }
}

function ResetCircles() {
   circle1.radius = Math.floor(Math.random() * 400);
   circle1.position.x = circle1.radius / 2 + Math.floor(Math.random() * (1280 - circle1.radius / 2));
   circle1.position.y = circle1.radius / 2 + Math.floor(Math.random() * (720 - circle1.radius / 2));

   circle2.radius = Math.floor(Math.random() * 400);
   circle2.position.x = circle2.radius / 2 + Math.floor(Math.random() * (1280 - circle2.radius / 2));
   circle2.position.y = circle2.radius / 2 + Math.floor(Math.random() * (720 - circle2.radius / 2));
}

So here is our app. Hit the SPACEBAR to change the circles randomly. The circles will appear in red if they collide and blue if they don't. Keep hitting the SPACEBAR to see different random circles and test if they collide.

Check out the circle collision detection source code.

Next -> Part 2 - Rectangle collision detection

Part 3 - Line line collision detection

Part 4 - Oriented rectangle collision detection

Part 5 - Polygon collision detection