Wednesday, July 18, 2012

Lasers And Player Collision

Previously, I described how lasers collide with the environment in Bullet Time Ninja. Today, let's talk about how to detect collisions between lasers and the player.

The trick I used was to treat the player as a circle, and do a line-circle collision test.



I chose a circle since it fills out most of the ninja, while also leaving a bit of wiggle room to make the gameplay more forgiving. Alternatively, I could have done laser-box collision, but that one is a little more complicated.

Not to say that the circle one isn't.



The main idea is that you raycast an infinite line towards the circle, and then constrain the line to the correct segment length. If there is a hit, the line usually intersects with 2 points on the circle.



Below is the line-circle collision test algorithm. The laser line is represented by the "start" and "end" points. The player is represented by "circlePos" and "radius". The results of the collision are complicated, so we store all the juicy details in the "result" object.

  /**
   * Modifies a RayCircleResult object specifying the points of intersection (if they exist) 
   * @param start
   * @param end
   * @param circlePos
   * @param rad
   * @param result Results of the collision are stored here.
   * 
   */
  public static function lineSegmentCircle(start:Point, end:Point, circlePos:Point, rad:Number, result:RayCircleResult):void
  { 
   result.reset();

   var dir:Point = end.subtract(start);
   var dirLength:Number = dir.length;
   dir.normalize(1);
   var m:Point = start.subtract(circlePos);

   var b:Number = RapidU.dot(m, dir);
   var c:Number = RapidU.dot(m, m) - (rad * rad);

   if(c > 0 && b > 0)
   {
    // ray's orgin is outside the circle (c > 0)
    // and ray is pointing away from the circle (b > 0)
    result.intersectCount = 0;
    return;
   }

   var disc:Number = b*b - c;
   var t:Number;

   if(disc < 0)
   {
    // no intersection
    result.intersectCount = 0;
    return; 
   }
   // find earliest time of intersection
   t = -b - Math.sqrt(disc);
   if(t < 0) 
    t = 0;

   if(t > dirLength)
   {
    // beyond the end of the line segment
    result.intersectCount = 0;
    return; 
   }
   result.point1.x = start.x + dir.x * t;
   result.point1.y = start.y + dir.y * t;

   if(disc == 0)
   {
    // tangent intersection, make both points the same
    result.intersectCount = 1;
    result.point2.x = result.point1.x;
    result.point2.y = result.point1.y;
    return;
   }

   // second point
   result.intersectCount = 2;
   t = -b + Math.sqrt(disc);

   if(t > dirLength)
   {
    // the line segment partially penetrates the circle
    result.point2.x = end.x; 
    result.point2.y = end.y;
    return; 
   }

   // end of the line is outside of the circle
   result.point2.x = start.x + dir.x * t;
   result.point2.y = start.y + dir.y * t;

   return;
  }

This code is all part of the open-source Rapid Physics engine that I wrote in parallel with Bullet Time Ninja. Feel free to dig deeper into the collision code on github, if you're into that kind of thing.

No comments:

Post a Comment