Introduction to Collision Handling


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.

Location-Based Collisions

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.

Color-Based Collisions

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.

1.

Open the movie snake! from the Lesson13 folder on the CD.

As you can see, the Stage is blank. In fact, there are no sprites at all in the Score. But there is a behavior script at frame 5, in the Score's behavior channel. Before looking at the behavior, let's see what the movie does when it's played.

2.

Play the movie, using the cursor keys to steer the snake.

If you steer the snake so it crosses its own path, you will hear a beep in your speakers. Anytime the head of the snake comes into contact with a color that is not white, you'll hear the beep.

With no sprites to test for position, a color-based approach is a must for an imaging Lingo based game like this.

3.

Stop the movie, then double-click the behavior at frame 5 in the Score.

Let's start with the beginSprite handler:

 on beginSprite me  mySquare = image(5, 5, 24)  mySquare.fill(mySquare.rect, rgb(255, 0, 0))  myRect = rect(20, 295, 25, 300)  myDirection = #right  si = _movie.stage.image end 

First, mySquare is defined as a 5x5 pixel bitmap and filled with red. Next, a 5x5 pixel rectangle is created and stored in the myRect propertythis is the starting point for the snakewhich is centered vertically and 20 pixels from the left edge. The myDirection property is then set to a symbol, #right, which is used by the enterFrame handler to create the motion. Finally, the si property is used as a reference to the stage image in order to save on typing in the enterFrame handler.

Now, have a look at the keyDown handler that follows:

 on keyDown me  if _key.keyPressed(124) then myDirection = #right  if _key.keyPressed(123) then myDirection = #left  if _key.keyPressed(126) then myDirection = #up  if _key.keyPressed(125) then myDirection = #down end 

As you can see, this is a very simple handler that changes the state of myDirection depending on which cursor key has been pressed.

The snake's movement and collision detection happen in the enterFrame handler:

 on enterFrame me  case myDirection of   #right:    col = si.getPixel(myRect.right + 3, myRect.top + 3)    moveRect = rect(5,0,5,0)   #left:    col = si.getPixel(myRect.left - 3, myRect.top + 3)    moveRect = rect(-5,0,-5,0)   #up:    col = si.getPixel(myRect.left + 3, myRect.top - 3)    moveRect = rect(0,-5,0,-5)   #down:    col = si.getPixel(myRect.left + 3, myRect.bottom + 3)    moveRect = rect(0,5,0,5)  end case  if col <> color(255,255,255) then beep  myRect = myRect + moveRect  si.copyPixels(mySquare, myRect, mySquare.rect) end 

Let's say this is the first time through the enterFrame handler. Then myRect is rect (20, 295, 25, 300) and myDirection is #right, both of which were initialized in the beginSprite handler. So the case statement tests myDirection. It is #right so these two lines of code execute:

 col = si.getPixel(myRect.right + 3, myRect.top + 3) moveRect = rect(5,0,5,0) 

Remember, the order for a rect is left, top, right, bottom. Therefore the code can be restated as:

 col = si.getPixel(25 + 3, 295 + 3) moveRect = rect(5,0,5,0) 

or:

 col = si.getPixel(28, 298) moveRect = rect(5,0,5,0) 

You are now getting the color of the pixel at point (28, 298). Look at the following image to see how this works:

You can see that you're sampling the color of the pixel in front of the head of the snake. If the color under the pixel isn't white, the snake has run into something and you'll hear a beep:

 if col <> color(255,255,255) then beep 

The last two lines in the handler do the actual drawing onto the screen:

 myRect = myRect + moveRect si.copyPixels(mySquare, myRect, mySquare.rect) 

First, moveRect is added to myRect. Because moveRect is rect (5, 0, 5, 0), when you're moving right, myRect would become rect (25, 295, 30, 300). The copyPixels command then copies the red square to the new rectangle defined by myRect, causing the snake to grow by 5 pixels:

Here, color-based collision detection simply involves getting the color value of a particular pixel and using that to determine what you've run into. I will leave it to you to further experiment with this very basic start to the snake game.

It's time now to look briefly at one more method, before implementing the method you'll be using in the game.

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

"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.

1.

Open 2d_game.dir from your project_three folder, if it's not already open. If it's not available you can open 2d_game_start.dir from the Lesson13 folder on the CD.

2.

Click the first enemy ship sprite in channel 4, of level 1, to select it. Shift-click the last asteroid sprite in level 3. You should now have all the enemy ships, UFOs, and asteroids selected. In the Property inspector, choose the Sprite tab and change the Ink type to Matte.

Although there's no visible change because the sprites already have an alpha channel, internally the collisions will now be checked using the actual pixels of the bitmap instead of only the rectangle enclosing it.

3.

Select all three of the player ship sprites at once by clicking channel 20, as shown:

In the Property inspector's Sprite tab choose Matte for the sprites' Ink type. The player's ship and all the enemy ships will now perform pixel-accurate collisions against one another.

Now you need to check if the bullet intersects with each of the enemy sprites. To do that, you will need to keep a list of the sprites being used as enemies.



Macromedia Director MX 2004. Training from the Source
Macromedia Director MX 2004: Training from the Source
ISBN: 0321223659
EAN: 2147483647
Year: 2003
Pages: 166
Authors: Dave Mennenoh

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net