Finding out when a moving object has hit another object is a crucial part of any action-based game. Since I don't play (or write) a whole lot of action-based games, I haven't spent much time worrying about collision detection. But the other day I was working on a way to make dynamic text move along a dynamically-defined curve (which I'll post a sample of when I get it cleaned up), and I remembered that Gary Fixler had posted something about using a line of "sentries" along the border of a movieclip to provide precise collision detection, which is related in a way. I went looking at his site and found the sample you see above. I love it! I like the way the things bounce and flip around, and the way if you throw one upwards hard, it disappears and then eventually comes thudding back down and bounces around some more, and it's just icing on the cake (ok, I guess it's actually the whole point of the thing) that the alpha of both clips change as soon as any part of one hits the other. Gary has a good way of thinking about how a thing works physically (like how off-center is an object when it hits an edge = how much the thing should rotate when it bounces) and making equations that produce the same effect in Flash, without being overly mathematically complicated. He graciously agreed to send over the fla and allow me to post it here, so I've had a good time digging around in it, commenting, rearranging and trying to make sense of it.
Anyone who's tried to find out when two objects collide in Flash knows that there are two things you can detect: when the bounding box of one movieclip touches the bounding box of another, or when the contents of a movieclip (not its bounding box) touches a defined point on the stage, or on another clip that's on the stage. The former is done with this kind of code, for two movieclips onstage, mcShape1 and mcShape2:
mcShape1.onPress = mcShape2.onPress = function() {
this.startDrag();
}
mcShape1.onRelease = mcShape2.onRelease = function() {
this.stopDrag();
}
function basicHitTest() {
if (mcShape1.hitTest(mcShape2)) {
trace("hit " + i++);
}
}
this.onEnterFrame = basicHitTest;
That works nicely if mcShape1 and mcShape2 are both of a fairly rectangular shape (and thus fill their respective bounding boxes). If they are not so rectangularly shaped, and you want to check when any part of the content of one (say, mcShape1) touches a particular point on the other (say, a dot placed 5 pixels to the right and 7 down from the registration point on mcShape2), you can do so like this:
mcShape1.onPress = mcShape2.onPress = function() {
this.startDrag();
}
mcShape1.onRelease = mcShape2.onRelease = function() {
this.stopDrag();
}
function shapeHitTest() {
if (mcShape1.hitTest(mcShape2._x+5, mcShape2._y+7, true)) {
trace("shape hit " + i++);
}
}
this.onEnterFrame = shapeHitTest;
But if you want to know when any part of the content (not bounding box) of mcShape1 hits any part of the content (not bounding box) of mcShape2, you'll need to do something like Gary did in his sample: use multiple blank clips to define the border.
If you look at movieclip symbols blue and green in the library of the above fla, you'll see that each contains a movieclip named perim. Perim contains a series of blank movieclips, arranged shoulder to shoulder (the more clips, the more precision, but also the more processing power required to detect a collision, of course) around the border of the clip's shape. Then, instead of checking if clip a has collided with clip b, we check whether clip a has collided with any of the sentries (treated as single points) in clip b:
// for each sentry in this clip ("clip b")
for (i in this.perim) {
// get the sentry's point on the stage
this.perim.localToGlobal(p={x:this.perim[i]._x,y:this.perim[i]._y});
// see if it's touching o ("clip a")
if(o.hitTest(p.x, p.y, true)){
o._alpha=50;
}
}
The localToGlobal method allows us to find the location of the sentry relative to the stage (rather than to perim, which it is inside of). That statement says: take the current x and y values of the sentry (eg, this.perim[i]._x) and make an object p out of those values. Assign p.x the sentry's x value within perim, and assign p.y its y value. Then use localToGlobal to convert that to a new p object, whose x value is the point on the stage where the sentry is, and whose y value is the sentry's y value on the stage.
If you've looked at the fla, you'll see that other things are happening inside the collision detection movieclip method (col) besides detecting a collision and setting the alpha to 50. Within the loop, the location of each sentry on the stage (p.x, p.y) is calculated for collision detection purposes, and also used
Physical effects such as acceleration and deceleration are applied by simply adding an increasing or decreasing x or y amount to the object over time (each frame). Eg, acceleration (gravity) is applied by adding a constant y amount (constant gcGravity) to the object on each frame when it's moving downward. Similarly, deceleration is achieved by adding a decreasing amount (multiplying the amount to be added by a factor less than 1, or dividing it by a factor > 1) on each frame to slow the object down (eg, friction, damping). For a better look at how each effect was achieved, we suggest starting with the fla (all code is in frame 1 of the main timeline, and has inline comments to explain what is being done in each segment) and then changing numbers and/or commenting out lines to see what effect is produced. The fla may be downloaded here.
Discussed on this page:
collision detection, point on shape hits any point on other shape's border, localToGlobal, hittest, simulating physics (gravity, damping)
Files:
fla and as files to create sample shown at left
(free download)
A list of all files currently available at the site may be viewed here.