Let's begin by talking about what a collision detection system needs to do. Imagine the stream of bullets that the ship shoots, as well as the ten or so enemy ships on a level. To know if a bullet has hit a ship, each bullet has to be checked against each enemy ship, on each frame. If this seems like a fairly processor-intensive task, it is. And that's probably the main reason so many different ways of implementing collision detection have emerged. The faster collision checks can be processed, the more can be going on in the game. Let's look at a few different methods of detecting collisions between sprites.
In location-based collision detection, you find out how far apart two sprites are by comparing their positions. If they are closer than a predetermined buffer amount, they have collided. Look at the following image, and note the location of the highlighted bullet versus the position of the enemy ship.
Assume that the bullet is sprite (23) and the enemy ship is sprite (4). If you compare the sprites' horizontal positions you would get something like this:
trace(sprite(23).locH sprite(4).locH) -- -3
The sprites are just 3 pixels apart horizontally. But notice that the difference produces a negative number. Because the bullet is further from the right edge of the screen, its locH is less than the ship's, which produces a negative value when you do the subtraction. This is fine; the sprites are still 3 pixels apart. Using Lingo's abs() function, or absolute value, you can always get a positive value.
But although the bullet is only 3 pixels away from the ship horizontally, it is above the ship and wouldn't hit it. Therefore, you need to also compare the sprites' vertical positions. While easy enough to do, it's another comparison that slows down the test. As an example, study the following chunk of Lingo that will return true if two sprites are within 10 pixels of one another in both directions:
on didCollide sp1, sp2 if abs(sp1.locH sp2.locH) < 10 then if abs(sp1.locV sp2.locV) < 10 then return true end if end if return false end
While this function will work perfectly well, there's an issue with accuracy using this method, as illustrated here:
The bullet is well within even a 5-pixel buffer zone, but it shouldn't be causing a collision, because of the asteroid's shape.
This method is also really not suited for a fast-paced game environment. Imagine that each bullet would need to call this method as many times per frame as there are enemies. If ten bullets are on screen, with ten enemies, the method would be called 100 times per frame! So we need to look at something a bit quicker.
Using imaging Lingo you can get the color of individual pixels, and use that for a collision-detection scheme. The getPixel method, available for image objects, will return the RGB value for any pixel in the image. This can allow for pixel-accurate collision detection, and is also quite fast.
Though it could be made to work, using imaging Lingo isn't particularly suited to this game. Take for instance a bullet. As it travels along, you get the pixel color underneath it. If the color is not black, the bullet has hit something. Let's even assume it hit an enemy ship. The problem is then trying to determine exactly which enemy ship was hit.
Remember that old game Snake? You had this "snake" that would continuously move and grow in length, as you steered it around the screen. An opponent's snake would do the same, and the object was to trap the other player, forcing them to run into their own tail and, of course, die. It was actually a fairly fun game for its timeand visually spectacular, and you can see:
As it turns out, Snake is a good game for us to create using Director and imaging Lingo, and a perfect application of color-based collision detection.
Rectangle Based Collisions
You can use Lingo's intersect function to get the rect created by the intersection of two other rects. If the rect created is rect(0, 0, 0, 0), there is no intersection, and thus no collision. Look at the following image showing two intersecting rectangles:
Can you tell what the intersection of sprite (10) and sprite (11) will be? Using the Message window for example:
trace(sprite(10).rect) -- rect(50, 60, 120, 115) trace(sprite(11).rect) -- rect(110, 80, 180, 130) trace(intersect(sprite(10).rect, sprite(11).rect) -- rect(110, 80, 120, 115)
As long as the rect formed by the intersection is not all zeroes, the sprites are colliding and you can react appropriately. However, much like the location-based example given earlier, this method works on the bounding box of the sprite, and irregularly shaped objects can produce collisions when they shouldn't. Still, this method is fairly fast and quite easy to implement. If nothing else, it's worth knowing about. Now, let's look at the method you'll be using in the game, before we implement it.
"Sprite.intersects" may sound like the intersect function, but the intersects method will return true if two sprites are overlapping, and false if they are not. A nice feature of this method is that if the sprites have an alpha channel, and are using matte ink, their actual boundaries will be used, instead of their bounding rectangles. Because of this, and because the method is quite fast, it is well suited for use in our game. For example, look at the following illustration:
As you can see, the bullet is well within the bounding rectangle of the asteroid and will produce a collision in the location-based method, as well as the method that produces the intersection of the two rects. The getPixel method would work, but it then becomes more work to find out which enemy was hit. Assuming that the bullet and asteroid are sprites 10 and 11, respectively, testing the intersects in the Message window yields false, as it should:
trace(sprite(10).intersects(11)) -- 0
Remember that even though the bitmaps have an alpha channel, you still need to set the sprites to use matte ink for the collision detection to be pixel accurate.