Collision Detection

[ your browser does not support the canvas tag ]

Refresh your browser for random squares!

MOVING TO OBJECT-ORIENTED COLLISION

Congrats! You've made it through a lot of collision-detection code. But these examples are meant as simple demonstrations of how the algorithms work. Combining them into bigger projects probably means moving your code to an object-oriented approach. (For an excellent introduction to object-oriented programming, see Daniel Shiffman's book Nature Of Code.)

Why? Let's say we have a circle and a bunch of rectangles (like above). We could store separate positions, sizes, and collisions for each, but that would quickly get messy. Instead, a Circle and Rectangle class will give our code a lot more power and flexibility.

Let's start with our Circle class:

class Circle {
  float x, y;    // position
  float r;       // radius

  Circle (float _x, float _y, float _r) {
    x = _x;
    y = _y;
    r = _r;
  }

  // move into mouse position
  void update() {
    x = mouseX;
    y = mouseY;
  }

  // draw
  void display() {
    fill(0, 150);
    noStroke();
    ellipse(x,y, r*2, r*2);
  }
}

Pretty straightforward. We can also make a basic Rectangle class:

class Rectangle {
  float x, y;            // position
  float w, h;            // size
  boolean hit = false;   // is it hit?

  Rectangle (float _x, float _y, float _w, float _h) {
    x = _x;
    y = _y;
    w = _w;
    h = _h;
  }

  // draw the rectangle
  // if hit, change the fill color
  void display() {
    if (hit) fill(255,150,0);
    else fill(0,150,255);
    noStroke();
    rect(x,y, w,h);
  }
}

Notice we have a variable for the Rectangle called hit. This way we can keep track of whether or not the circle has hit a particular rectangle and change its fill color accordingly. By default, the value is set to false.

We have just one Circle, but we create an ArrayList of Rectangle objects. To run everything, here's what our main draw() loop looks like:

void draw() {
  background(255);

  // go through all rectangles
  // and draw them onscreen
  for (Rectangle r : rects) {
    r.display();
  }

  // update circle's position and draw
  circle.update();
  circle.display();
}

So how do we test if the circle has hit something? Let's create a method (an internal function) of the Rectangle class called checkCollision(). We'll pass the Circle object as an argument, then do a basic Circle/Rectangle collision test.

void checkCollision(Circle c) {
  hit = circleRect(c.x,c.y,c.r, x,y,w,h);
}

The result of circleRect() sets hit to be true or false, which in turn changes the fill color. Now we just add the test to the draw() loop:

for (Rectangle r : rects) {
  r.checkCollision(circle);  // check for collision
  r.display();               // and draw
}

Pretty cool! Here's the full code:

// a single Circle object, controlled by the mouse
Circle circle;

// a list of rectangles
Rectangle[] rects = new Rectangle[8];


void setup() {
  size(600,400);

  // create a new Circle with 30px radius
  circle = new Circle(0,0, 30);

  // generate rectangles in random locations
  // but snap to grid!
  for (int i=0; i<rects.length; i++) {
    float x = int(random(50,width-50)/50) * 50;
    float y = int(random(50,height-50)/50) * 50;
    rects[i] = new Rectangle(x,y, 50,50);
  }
}


void draw() {
  background(255);

  // go through all rectangles...
  for (Rectangle r : rects) {
    r.checkCollision(circle);  // check for collision
    r.display();               // and draw
  }

  // update circle's position and draw
  circle.update();
  circle.display();
}


class Circle {
  float x, y;    // position
  float r;       // radius

  Circle (float _x, float _y, float _r) {
    x = _x;
    y = _y;
    r = _r;
  }

  // move into mouse position
  void update() {
    x = mouseX;
    y = mouseY;
  }

  // draw
  void display() {
    fill(0, 150);
    noStroke();
    ellipse(x,y, r*2, r*2);
  }
}


class Rectangle {
  float x, y;            // position
  float w, h;            // size
  boolean hit = false;   // is it hit?

  Rectangle (float _x, float _y, float _w, float _h) {
    x = _x;
    y = _y;
    w = _w;
    h = _h;
  }

  // check for collision with the circle using the
  // Circle/Rect function we made in the beginning
  void checkCollision(Circle c) {
    hit = circleRect(c.x,c.y,c.r, x,y,w,h);
  }

  // draw the rectangle
  // if hit, change the fill color
  void display() {
    if (hit) fill(255,150,0);
    else fill(0,150,255);
    noStroke();
    rect(x,y, w,h);
  }
}


// CIRCLE/RECTANGLE
boolean circleRect(float cx, float cy, float radius, float rx, float ry, float rw, float rh) {

  // temporary variables to set edges for testing
  float testX = cx;
  float testY = cy;

  // which edge is closest?
  if (cx < rx)         testX = rx;      // compare left edge
  else if (cx > rx+rw) testX = rx+rw;   // right edge
  if (cy < ry)         testY = ry;      // top edge
  else if (cy > ry+rh) testY = ry+rh;   // bottom edge

  // get distance from closest edges
  float distX = cx-testX;
  float distY = cy-testY;
  float distance = sqrt( (distX*distX) + (distY*distY) );

  // if the distance is less than the radius, collision!
  if (distance <= radius) {
    return true;
  }
  return false;
}

Note that our code is a bit long with all the classes, so the actual Processing file is broken up into separate tabs. This would be a good idea for projects that require several collision functions. You could name the tab CollisionFunctions and keep all the code there.

You can see another, more complex example of object-oriented collision in the Introduction. It uses a class for circles, rectangles, and lines.

NEXT: Dealing With Matrix Transformations