Pong - Collision Detection

Sat, May 14, 2011

Ball Movement

In order to have some challenge, we need the ball to accelerate. Given a position, a speed, an acceleration and a time interval dt, the new position and speed of the ball can be calculated as follows:

accelerate: function(x, y, dx, dy, accel, dt) {
  var x2  = x + (dt * dx) + (accel * dt * dt * 0.5);
  var y2  = y + (dt * dy) + (accel * dt * dt * 0.5);
  var dx2 = dx + (accel * dt) * (dx > 0 ? 1 : -1);
  var dy2 = dy + (accel * dt) * (dy > 0 ? 1 : -1);
  return { nx: (x2-x), ny: (y2-y), x: x2, y: y2, dx: dx2, dy: dy2 };
},

Once we can accelerate, the ball’s update() method needs to:

  • calculate the new position and speed of the ball.
  • detect if it bounced off the top or bottom wall (simple bounds check)
  • detect if it bounced off a paddle (see next section)
  • tweak the y speed to simulate spin if the ball hit a moving paddle
update: function(dt, leftPaddle, rightPaddle) {

  pos = Pong.Helper.accelerate(this.x, this.y, this.dx, this.dy, this.accel, dt);

  if ((pos.dy > 0) && (pos.y > this.maxY)) {
    pos.y = this.maxY;
    pos.dy = -pos.dy;
  }
  else if ((pos.dy < 0) && (pos.y < this.minY)) {
    pos.y = this.minY;
    pos.dy = -pos.dy;
  }

  var paddle = (pos.dx < 0) ? leftPaddle : rightPaddle;
  var pt     = Pong.Helper.ballIntercept(this, paddle, pos.nx, pos.ny);

  if (pt) {
    switch(pt.d) {
      case 'left':
      case 'right':
        pos.x = pt.x;
        pos.dx = -pos.dx;
        break;
      case 'top':
      case 'bottom':
        pos.y = pt.y;
        pos.dy = -pos.dy;
        break;
    }

    // add/remove spin based on paddle direction
    if (paddle.up)
      pos.dy = pos.dy * (pos.dy < 0 ? 0.5 : 1.5);
    else if (paddle.down)
      pos.dy = pos.dy * (pos.dy > 0 ? 0.5 : 1.5);
  }

  this.setpos(pos.x,  pos.y);
  this.setdir(pos.dx, pos.dy);
},

Ball and Paddle Intersection

The ballIntercept() method used in the update() method above needs to accurately detect if the ball will intercept with the paddle during this time frame interval (dt).

The ball moves from p1 to p2 during the time interval (dt) and the paddle edge stretches from p3 to p4 and we need to see if these line segments intersect.

We want to be able to see if the ball intercepts the obvious side of the bat.

  • If the ball is moving left, check the right edge of player 1’s bat.
  • If the ball is moving right, check the left edge of player 2’s bat.

But we also want to check the non-obvious sides, such as the top and bottom to see if the ball ‘just missed’ the obvious edge but will bounce off the top or bottom before going out for a goal. Most pong games dont bother with this, but we are trying to be complete, so, assuming the existence of a simple line intercept() method, ballIntercept() can be implemented as follows:

ballIntercept: function(ball, rect, nx, ny) {
  var pt;
  if (nx < 0) {
    pt = Pong.Helper.intercept(ball.x, ball.y, ball.x + nx, ball.y + ny, 
                               rect.right  + ball.radius, 
                               rect.top    - ball.radius, 
                               rect.right  + ball.radius, 
                               rect.bottom + ball.radius, 
                               "right");
  }
  else if (nx > 0) {
    pt = Pong.Helper.intercept(ball.x, ball.y, ball.x + nx, ball.y + ny, 
                               rect.left   - ball.radius, 
                               rect.top    - ball.radius, 
                               rect.left   - ball.radius, 
                               rect.bottom + ball.radius,
                               "left");
  }
  if (!pt) {
    if (ny < 0) {
      pt = Pong.Helper.intercept(ball.x, ball.y, ball.x + nx, ball.y + ny, 
                                 rect.left   - ball.radius, 
                                 rect.bottom + ball.radius, 
                                 rect.right  + ball.radius, 
                                 rect.bottom + ball.radius,
                                 "bottom");
    }
    else if (ny > 0) {
      pt = Pong.Helper.intercept(ball.x, ball.y, ball.x + nx, ball.y + ny, 
                                 rect.left   - ball.radius, 
                                 rect.top    - ball.radius, 
                                 rect.right  + ball.radius, 
                                 rect.top    - ball.radius,
                                 "top");
    }
  }
  return pt;
}

Line Intersection

The ballIntercept() method relies on a generic line segment intersect method.

Assuming the ball moves from (x1,y1) to (x2,y2) and the paddle edge goes from (x3,y3) to (x4,y4), the intercept() method can be implemented as follows:

intercept: function(x1, y1, x2, y2, x3, y3, x4, y4, d) {
  var denom = ((y4-y3) * (x2-x1)) - ((x4-x3) * (y2-y1));
  if (denom != 0) {
    var ua = (((x4-x3) * (y1-y3)) - ((y4-y3) * (x1-x3))) / denom;
    if ((ua >= 0) && (ua <= 1)) {
      var ub = (((x2-x1) * (y1-y3)) - ((y2-y1) * (x1-x3))) / denom;
      if ((ub >= 0) && (ub <= 1)) {
        var x = x1 + (ua * (x2-x1));
        var y = y1 + (ua * (y2-y1));
        return { x: x, y: y, d: d};
      }
    }
  }
  return null;
},

The game is now playable as a 2 player game with this demo here

More…

You can find the final game here and the code is here