21.4. Basic Collisions
Detecting when objects collide with one another is an important aspect of game programming. A collision could take place between a character and a barrier in the game, between two characters, between a character and a computer enemy, or between characters and projectiles. The way to detect these collision types can be simple and can result in an approximation, or it can be a much more complicated, yet precise, detection.
|
The simplest of these detection techniques is rectangular collision detection. The level of precision in rectangular collision detection depends on the accuracy of the bounding boxes being tested. Next, there is circular collision detection, which can approximate large projectiles better than a rectangle can. Finally, there is linear collision detection, which accurately detects a collision based on two lines intersecting. Of course, there are other techniques, but they become more complex and require a lot more mathematics and computer computation (requiring more computer hardware speed), and frankly, they are beyond the scope of this book. You can find more information on collision detection in AI for Game Developers by David M. Bourg and Glenn Seeman (O'Reilly).
For good collision detection, all of the objects and barriers in the game will have to have bounding boxes, circles, or lines to detect collision properly. To this end, our character must have collision bounding constraints to test against. This requires a simple addition to the character class:
var character = Class.create( );
character.prototype = {
collision: {
_boundings: [],
.
.
.
Objects and barriers would have something similar so that they too could be tested for collisions.
Now that we know the basics of collision detection, it's time to decide which method of collision detection we want to use in our game.
21.4.1. Rectangular Collision Detection
Rectangular collision detection determines whether there is an intersection between two boxes. This is actually a simple process. Each object to be tested will have a bounding box with an (x1, y1) coordinate that represents the upper-left corner of the box and an (x2, y2) coordinate that represents the lower-right corner of the box. You can also express this second coordinate as (x1 + width, y1 + height). You perform two tests on the boxes to see whether they have collided. First you check whether one box's right x coordinate is greater than the other box's left x coordinate and then whether one box's left x coordinate is less than the second box's right x coordinate. This determines whether they are intersected vertically, and you can express it as follows:
if ((ego.GetBoundX(1) < char[0].GetBoundX(2)) &&
(ego.GetBoundX(2) > char[0].GetBoundX(1)))
|
Once you have found that the two bounding boxes have intersected vertically, you would run these tests on the two boxes' y coordinates to determine whether the boxes have intersected horizontally as well. You would express this as follows:
if ((ego.GetBoundY(1) < char[0].GetBoundY(2)) &&
(ego.GetBoundY(2) > char[0].GetBoundY(1)))
When both statements are found to be true, the two bounding boxes have collided. Unfortunately, as you will notice in Figure 21-12, though the boxes have collided with each other, they are not accurately describing a true collision between the two characters (Tux is not really colliding with the BSD daemon).
Figure 21-12. An example of two bounding boxes colliding, yet not accurately describing an actual collision
Tightening the bounding boxes will provide more accurate collision detection, though this can be only so accurate, as a rectangle cannot describe most sprites to a high degree of accuracy. Example 21-6 shows what the addition to the Logic class would entail for this test.
Example 21-6. Additions to the Logic class for testing rectangular collisions
Code View:
|
Once this is in place, the tests can happen within the character class, as shown in Example 21-7.
Example 21-7. The character class with collision detection and other necessary functionality added
Code View:
|
|
This is simple collision detection with easy and fast calculations. Sometimes, however, to get more accurate results, a different bounding technique may be required.
21.4.2. Circular Collision Detection
Circular collision detection requires more calculations than rectangular collision detection does; therefore, you should use it only when you have a larger object that would benefit from collision detection with a circle as its bounding area. The object must have a radius and a center coordinate so that it can be tested correctly. For example:
var object = Class.create( );
object.prototype = {
collision: {
_boundingRadius: 0,
_boundingCenter: [0, 0],
//.
//.
//.
}
};
We will consider anything within the object's radius as something with which we have collided. To calculate this, we will use the Pythagorean theorem (http://en.wikipedia.org/wiki/Pythagorean_theorem) to determine a point's distance to the center of the object.
The Pythagorean theorem says that for a right triangle, the square of the hypotenuse is equal to the sum of the squares of the other two sides. That is, x2 + y2 = r2. To solve for r (the radius) we take (x2 + y2). Testing a point (x, y) to see whether it is within a certain number of pixels from our center, we calculate the following: ((x - centerX)2 + (y - centerY)2).
|
Now that we have this distance, we check it against the object's radius and see whether this value is less than the calculated radius. When the calculated radius is less than the object's radius, the objects have collided.
The bad thing about this method of testing is that it can be a bit slow because of the square root function, which takes some time to calculate. This can be bad if several detections must be checked at every interval. To overcome this speed issue, the radius of the object squared can also be stored in the object for the sole purpose of testing against collisions. Then, the test is checking the object's radius squared against the calculation, ((x - centerX)2 + (y - centerY)2). This speeds up the calculation, but it is still slower than rectangular collision detection.
Example 21-8 shows this collision testing as part of the Logic object. As an alternative, you could use a lookup table, or LUT (http://en.wikipedia.org/wiki/Lookup_table), of square root values to calculate more quickly than you can with the sqrt( ) function.
Example 21-8. The circular collision test added to the Logic object
Code View:
|
Keep in mind that this only tests a point and a circle for collision. What if we want to test the circle with another circle? Such a test would be only slightly more complicated than our circle-point test. To test two circles for collision, you calculate the distance between their center points, and if this is less than the sum of their radii, the two circles have collided. For example:
var r2 = Math.pow((obj1.GetCenterX( ) - obj2.GetCenterX( )), 2) +
Math.pow((obj1.GetCenterY( ) - obj2.GetCenterY( )), 2);
/* Is there a collision between the two circles? */
if (r2 < Math.pow((obj1.GetRadius( ) + obj2.GetRadius( )), 2))
return (true);
return (false);
Our final test is a test for a circle and a rectangle. This will require a few more calculations: the circle must be tested for a collision against all four of the rectangle's corners, all four of the rectangle's sides, and the rectangle's inside area. Figure 21-13 gives an example of each case.
Figure 21-13. Detecting collisions between circles and rectangles
The following test will need to be checked against all four corners of the rectangle for collision against a corner of the rectangle:
var r2 = Math.pow((obj.GetCenterX( ) - ego.GetBoundX(1)), 2) +
Math.pow((obj.GetCenterX( ) - ego.GetBoundY(1)), 2);
/* Is there a collision between the corner and the circle? */
if (r2 < obj.GetRadiusSquared( ))
return (true);
return (false);
Once the corners have been checked—(x1, y1), (x1, y2), (x2, y1), and (x2, y2)—we must test all four sides for a collision with the circle:
Code View:
/* Does the circle's center collide vertically with the bounding box? */
if ((obj.GetCenterX( ) > ego.GetBoundX(1)) && obj.GetCenterX( ) < ego.GetBoundX(2))
/*
* Does the circle's center collide horizontally with the bottom side of the
* bounding box?
*/
if (Math.abs((ego.GetBoundY(2) - obj.GetCenterY( ))) < obj.GetRadius( ))
return (true);
/*
* Does the circle's center collide horizontally with the top side of the
* bounding box?
*/
else if (Math.abs((ego.GetBoundY(1) - obj.GetCenterY( ))) < obj.GetRadius( ))
return (true);
return (false);
This tests the top and bottom sides of the bounding box, and the right and left sides would have similar calculations. The easiest part of this test is to check whether the circle collided somewhere inside the rectangle. The center of the circle must be tested against the rectangle. This is the point against a bounding box check I discussed in the preceding section.
That is all there is to circular collision detection. It is not too complicated, and certainly not too processor-intensive. However, if you will be conducting hundreds or thousands of these checks, you will have a problem. If this is the case, bounding all objects with bounding rectangles will result in much faster (albeit less accurate) testing for collisions.
21.4.3. Linear Collision Detection
When you need a higher degree of accuracy for collision detection, a good solution is to test for the intersection of two lines (or line segments for our purposes). More computations are involved with this sort of test, unfortunately, so if you can get by without this accuracy, do.
OK, kids, break out your math books, because this is some fun algebra! For our algorithm, we will assume that we have two line segments, L1 and L2, where L1 goes from P1 to P2 and L2 goes from P3 to P4. The equations of these lines are:
Pa = P1 + ua(P2 - P1)
Pb = P3 + ub(P4 - P3)
Solving for the point where Pa = Pb will give us the following two equations in two unknowns (ua and ub):
x1 + ua(x2 - x1) = x3 + ub(x4 - x3)
y1 + ua(y2 - y1) = y3 + ub(y4 - y3)
The next step is to solve these two equations, which gives us expressions for ua and ub:
ua = ((x4 - x3)(y1 - y3) - (y4 - y3)(x1 - x3)) /
((y4 - y3)(x2 - x1) - (x4 - x3)(y2 - y1))
ub = ((x2 - x1)(y1 - y3) - (y2 - y1)(x1 - x3)) /
((y4 - y3)(x2 - x1) - (x4 - x3)(y2 - y1))
Substituting either of these expressions into the corresponding equation for the line gives the intersection point. Example 21-9 shows this algorithm as an addition to the Logic object.
Example 21-9. The Logic object with the linear collision detection added to it
Code View:
|
There are a few things to note about this algorithm. When the denominator for the expressions ua and ub is 0, the two lines are parallel. When the denominator and numerator for the expressions ua and ub are 0, the two lines are coincident (they lie on top of each other). When both ua and ub lie between 0 and 1, the intersection point is within both line segments.
|
That is linear collision detection at its finest. It involves quite a few calculations, so it would be a bad idea to use this technique if you had to execute several collision tests. However, it does result in more accurate collision detection, as characters and objects can have bounding polygons, and each line segment of the polygon can be tested (but then you are talking a lot of calculations!).
No comments:
Post a Comment