Foundation Game Design with ActionScript 3.0, Second Edition (63 page)

BOOK: Foundation Game Design with ActionScript 3.0, Second Edition
12.01Mb size Format: txt, pdf, ePub
Collision.block(mouse, stream);

The method is written in general way so that it doesn't need to know specifically which objects it will be asked to block, just that they'll be two Sprite objects. You can reuse exactly the same code anywhere in any context.

Taking a look at the Collision.block method

So now you have no excuse for claiming not to understand what method arguments and parameters are! Are you ready for a bigger challenge? Let's take a look at the
Collision
class and figure out exactly how the code is working.

Warning! The code is a little complex, and you almost certainly won't understand it right away. It may actually be more beneficial for you to come back to this section of the chapter when your programming skills are slightly more refined, a few chapters down the road.

Before you do, flip back a few pages to the section called “Preventing objects from overlapping” and try to become familiar with the problem you need to solve. If you understand the problem and the logic used to solve it, all the new code does is turn that logic into AS3.0 code. Let's walk the through the
Collision
class and have a look at what's new and how it works.

Here's the entire
Collision
class. You'll see a lot of code that you won't understand, but don't worry about that yet. I'll walk you through all of it in the pages ahead. Note that
r1
and
r2
stand for “rectangle one” and “rectangle two.” In the example you just looked at,
r1
is the character and
r2
is the box.

package
{
  import flash.display.Sprite;
  public class Collision
  {
    static public var collisionSide:String = "";
    public function Collision()
    {
    }
    static public function block(r1:Sprite, r2:Sprite):void
    {
      
//Calculate the distance vector
      var vx:Number
        = (r1.x + (r1.width / 2))
        - (r2.x + (r2.width / 2));
      var vy:Number
        = (r1.y + (r1.height / 2))
        - (r2.y + (r2.height / 2));
      
//Check whether vx
      
//is less than the combined half widths
      if(Math.abs(vx) < r1.width / 2 + r2.width / 2)
      {
        
//A collision might be occurring! Check
        
//whether vy is less than the combined half heights
        if(Math.abs(vy) < r1.height / 2 + r2.height / 2)
        {
          
//A collision has occurred! This is good!
          
//Find out the size of the overlap
          
//on both the X and Y axes
          var overlap_X:Number
            = r1.width / 2
            + r2.width / 2
            - Math.abs(vx);
          var overlap_Y:Number
            = r1.height / 2
            + r2.height / 2
            - Math.abs(vy);
          
//The collision has occurred on the axis with the
          
//*smallest* amount of overlap. Let's figure out which
          
//axis that is
          if(overlap_X >=  overlap_Y)
          {
            
//The collision is happening on the X axis
            
//But on which side? vy can tell us
            if(vy > 0)
            {
              collisionSide = "Top";
              
//Move the rectangle out of the collision
              r1.y = r1.y + overlap_Y;
            }
            else
            {
              collisionSide = "Bottom";
              
//Move the rectangle out of the collision
              r1.y = r1.y - overlap_Y;
            }
          }
          else
          {
            
//The collision is happening on the Y axis
            
//But on which side? vx can tell us
            if(vx > 0)
            {
              collisionSide = "Left";
              
//Move the rectangle out of the collision
              r1.x = r1.x + overlap_X;
            }
            else
            {
              collisionSide = "Right";
              
//Move the rectangle out of the collision
              r1.x = r1.x - overlap_X;
            }
          }
        }
        else
        {
          
//No collision
          collisionSide = "No collision";
        }
      }
      else
      {
        
//No collision
        collisionSide = "No collision";
      }
    }
  }
}

This is the most complex code that you've come across in the book so far, but I'll break it apart to look at one little piece at a time. The first odd thing is the
Collision
class's constructor method.

public function Collision()
{
}

Remember that a class
has
to have a constructor method, and the constructor method name always has to be the same name as the class name. Any directives inside the constructor method run immediately when the class is instantiated.

This constructor method is completely empty, which might seem strange. The reason is that you have no initialization directives that you want to run. It's perfectly fine to have a constructor method that's empty like this. In fact, you can even leave out the entire constructor method if you want to, although it's generally considered bad programming form to do so. (If you do leave it out, AS3.0 will add it automatically when it compiles the program.)

Next is the
block
method's function definition.

static public function block(r1:Sprite, r2:Sprite):void
{…

The
static
keyword means that you can use this method directly in any other class, using this format:

Collision.block

That's convenient, because if this method wasn't declared as static, you would first need to make an instance of the class with the
new
keyword, like this:

public var collision:Collision = new Collision();

And you'd then be able to use the block method, like this:

collision.block(character, box);

By declaring the method as static, you don't have to go to the trouble of making an instance of the class first.

The other important things about this function definition are its parameters.

(r1:Sprite, r2:Sprite)

These are local variables that are typed as Sprites. In the
BlockingMovement
application class, you used this line of code to call the method:

Collision.block(character, box);

Both
character
and
box
are Sprite objects, so it makes sense that
r1
and
r2
should be typed as Sprite objects, too. When the method is called, a reference to the
character
object is copied into the
r1
variable, and a reference to the
box
object is copied into the
r2
variable. Whenever you see
r1
and
r2
in the body of the method, you can replace them with
character
and
box
in your mind if that helps you better understand how the code is working. (The “r” is just short for “rectangle.” Keeping the variable names short in this bit of code keeps it compact and makes it a little easier to read.)

The first few lines of code inside the
block
method create the
vx
and
vy
variables that define the distance vector between the objects.

var vx:Number
  = (r1.x + (r1.width / 2))
  - (r2.x + (r2.width / 2));
var vy:Number
  = (r1.y + (r1.height / 2))
  - (r2.y + (r2.height / 2));

This is the imaginary line that runs between the character and the box. The vector is being drawn from the
centers
of the objects. To find the centers, you need to add half the value of their widths and heights to their top left corner x and y positions. What you end up with is a vector that runs from the center of the character to the center of the box. Remember that a pair of
vx
and
vy
variables always describes a single vector. (Check back to
Figure 6-20
to review this if you're a bit hazy as to why).

Now that you have the vector defined, the code goes through a methodical process of checking whether the
vx
and
vy
variables are less than the combined half widths. Remember, if they are, then a collision is occurring. This is all done with a series of nested if statements. Here's the first one:

if(Math.abs(vx) < r1.width / 2 + r2.width / 2)
{…

This code is not as daunting as it seems! All it means is this:

if(the vx value is less than the combined half-width of r1 and half-width of r2)
{…

Take a second look at that if statement. Do you see it now? Just like I promised, it's no more than a bit of easy math.

But one new thing is this bit of code:

Math.abs(vx)

Math.abs
is one of AS3.0's built-in methods. (And, yes, it's a static method!) Its job is to find out the
absolute value
of a number. Absolute values can only be positive; negative numbers that are forced to be absolute have their sign dropped. Let's imagine that you use
Math.abs
in a line of code that looks like this:
Math.abs(-27);

It would return this:

27

Why is this useful for you? If the first object has a lower x position value than the second object, the value of
vx
will be negative. The negative value would overly complicate the code because you'd need an additional if statement to check for it. It's simpler for you just to deal with positive values.

So what happens if the code discovers that
vx
is less than the combined half-widths? It checks whether
vy
is less than the combined half-heights.

if(Math.abs(vy) < r1.height / 2 + r2.height / 2)
{…

If this turns out to be true, then you know that a collision absolutely must be occurring. This is the condition illustrated in
Figure 6-24
. The next step is to calculate the amount of overlap on the x and y axis, and then use those amounts to figure out on which side of the object the collision is happening: top, left, bottom, or right.

The amount of overlap is stored in two variables called
overlap_X
and
overlap_Y
. Here's how they're calculated:

var overlap_X:Number
  = r1.width / 2 + r2.width / 2 - Math.abs(vx);
var overlap_Y:Number
  = r1.height / 2 + r2.height / 2 - Math.abs(vy);

You'll notice that these variables are declared without the keyword
public
. That's because they're variables that are only used inside the method and don't belong to the class. They can't be used outside of the
block
method. These are known as
local variables.
You'll see many more examples of local variables at work in over the rest of the book.

Now that you know the amount of overlap, you just have to compare the overlap values against the
vx
and
vy
values to find out on which side of the first object the collision is occurring. When the code knows that, it can use the correct overlap value to reposition the object so that it's no longer touching the second object.

if(overlap_X >=  overlap_Y)
{
  
//The collision is happening on the X axis
  
//But on which side? vy can tell us
  if(vy > 0)
  {
    collisionSide = "Top";
    
//Move the rectangle out of the collision
    r1.y = r1.y + overlap_Y;
  }
  else
  {
    collisionSide = "Bottom";
    
//Move the rectangle out of the collision
    r1.y = r1.y - overlap_Y;
  }
}
else
{
  
//The collision is happening on the Y axis
  
//But on which side? vx can tell us
  if(vx > 0)
  {
    collisionSide = "Left";
    
//Move the rectangle out of the collision
    r1.x = r1.x + overlap_X;
  }
  else
  {
    collisionSide = "Right";
    
//Move the rectangle out of the collision
    r1.x = r1.x - overlap_X;
  }
}

And that's really all there is to it. You can see that the logic is slightly intricate, but if you spend a few minutes carefully stepping through the code in your own mind, you'll also see that it's pretty straightforward.

As a bonus, the
Collision
class has a String variable called
collisionSide
.

static public var collisionSide:String = "";

Its job is to record the side on which the collision is occurring. It may be useful for some of your games to know this information. In fact, knowing this information is crucial to programming a platform game, as you'll see in
Chapter 9
.

collisionSide
has been declared as a
static
variable. That means you can access this variable in the main application class like this:

Collision.collisionside

That will return the current text of the
collisionSide
variable. You could possibly use it in a game scenario like this:

if(Collision.collisionSide == “Bottom”)
{
  //The character is standing on top of the box,
  //so release the swarms of killer bees!
}

I'll leave it up to your skill and imagination to actually program this game. (I can't wait to play it!)

Summary

Collision detection is quite a big subject in game design. Hopefully the introductory taste you've had of it here is enough whet your appetite for what's to come.

Other books

Switched by Jessica Wollman
Caught in the Middle by Regina Jennings
Blackouts and Breakdowns by Rosenberg, Mark Brennan
The Rain Began to Fall by A. K. Hartline
Mood Riders by Theresa Tomlinson
Wake the Dawn by Lauraine Snelling