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

BOOK: Foundation Game Design with ActionScript 3.0, Second Edition
2.47Mb size Format: txt, pdf, ePub
if(meterInsideImage.width < 1)
{
  trace("Game over");
}

In a proper game, you could instead display “Game over” in a text field. Or you could display a whole Game Over image over the stage and a button that lets the player play again.

Using scaleX to scale the meter based on a percentage

You can also use the scaleX property to change the meter's size. scaleX changes the size of an object based on its ratio. It lets you change the width of the meter based on a percentage instead of a fixed pixel amount.
This is often preferable because it means the meter is reduced at the same rate, no matter how long or short it is.

A scaleX value of 1 means the object is full size. You can reduce a meter by 1% by subtracting 0.01 from the scaleX property each frame.

Here's the code to use if you want to duplicate the health meter example using scaleX:

if (character.hitTestObject(monster))
{
  if(meterInsideImage.scaleX > 0)
  {
    meterInsideImage.scaleX -= 0.02;
  }
}
if(meterInsideImage.scaleX <= 0)
{
  trace("Game over");
}

A value of 0.02 reduces the meter by 2% each frame, so the meter will reach 0 in 50 frames.

Scaling by percentages is very useful because you can use meters to graph other data that might be using percentages in your game.

Updating a score

Most games keep track of whether a player has won or lost by updating a score based on how well the player is performing. The following example shows you how to update a score and end the game when a certain score has been reached. Open the UpdateScore project folder in the chapter's source files and try it out. Each time the cat bumps into the monster, the score is increased by 1. When the score reaches 5, a “Game over” trace message is displayed.
Figure 6-7
shows what you'll see.

Figure 6-7.
Use collision detection to increase a score.

There's a problem that this code has to solve. Remember that the collision detection code is running inside the
enterFrameHandler
. The
enterFrameHandler
is updated 60 times per second. If a collision occurs,
all the collision code will also be triggered 60 times per second. And if that code has the job of updating the score when a collision occurs, the score will be updated 60 times per second until the objects are no longer touching.

What does that mean? If the collision code updates the score by 1 each frame, over 3 seconds the score will race up to 180. You could see this effect happening in the previous example where the health meter was decreased continuously each frame while the objects touched.

That's not the way the score should be updated. It should only be updated once, the first time the objects touch, and not again, even if they remain touching. You have to find a way of detecting the very first collision and then preventing it from registering a second time—until the objects separate.

You can do this with a Boolean variable that switches the collision detection on and off depending on the conditions I've just described. Here's the important code that makes this work. First, you need to create two new variables in the class definition.

public var score:uint = 0;
public var collisionHasOccurred:Boolean = false;

score
is a number that tracks how many times the two objects collide. It's set to zero when the game starts.
collisionHasOccurred
has the job of tracking whether or not there's been a collision. It's set to
false
when the program starts. If there ever is a collision, it's going to be set to
true
to prevent a second collision being detected. This variable is extremely important for the scoring system to work properly, and you'll see why next.

The
enterFrameHandler
has the job of moving the character, checking for a collision, updating the score, and checking for the end of the game. Here's all the code from the
enterFrameHandler
:

public function enterFrameHandler(event:Event):void
{
  
//Move the player
  character.x += vx;
  character.y += vy;
  
//Collision detection
  if (character.hitTestObject(monster))
  {
    if(! collisionHasOccurred)
    {
      score++;
      output.text = String(score);
      collisionHasOccurred = true;
    }
  }
  else
  {
    collisionHasOccurred = false;
  }
  
//Check for the end of the game
  if(score == 5)
  {
    trace("Game over");
  }
}

Let's break this down and see what's happening, and why. First, the code checks for a collision between the character and the monster.

if (character.hitTestObject(monster))
{…

If there is a collision, the next if statement checks to see if the collision between the objects
hasn't occurred.
That's right, you didn't misread the previous sentence; you want to check to see whether the collision
has not
already happened. That's what this line is doing:

if(!collisionHasOccurred)
{…

It checks to see if the
collisionHasOccurred
variable is
false
. Usually, conditional statements check to see whether certain conditions or variables are true, but not this time. Instead, you used the not operator to check for a
false
condition. The not operator is an exclamation mark (!). When it's used in a conditional statement in front of a Boolean variable, it allows the directives inside the if statement to run as if the Boolean value were
false
.

So, is it
false
? You initialized
collisionHasOccurred
to
false
when the program started, so the first time the collision occurs, it is
false
, and all the directives inside the if statement will run.

score++;
output.text = String(score);
collisionHasOccurred = true;

This updates the score and the dynamic text field, but it also does something very important: it sets the
collisionHasOccurred
variable to
true
.

Why is that so important? Remember, all this code is running inside the
enterFrameHandler
. That means exactly 1/60th of a second later, this same if statement will be called upon a
second time
if the objects are still touching.

if(!collisionHasOccurred)
{
  score++;
  output.text = String(score);
  collisionHasOccurred = true;
}

You don't want this code to run a second time because you only want to update the score the first time the objects touch. Fortunately the
collisionHasOccurred
variable was set to
true
the first time this code
ran, so it will prevent it from running a second time. That means none of the directives run, and the score and the text field are only updated once.

Perfect—just what you wanted!

But there's another problem. You want the score to update
again
if the objects separate and happen to collide again some at future point in the game. This won't happen if
collisionHasOccurred
is still set to
true
. You have to find some way to reset it back to
false
so you can update the score for a future collision.

This is very simple; you just need to set
collisionHasOccurred
to
false
when the objects are
not colliding.
The second part of the if/else statement takes care of that, which the following code in bold shows:

if (character.hitTestObject(monster))
{
  if(! collisionHasOccurred)
  {
    score++;
    output.text = String(score);
    collisionHasOccurred = true;
  }
}
else
{
  
collisionHasOccurred = false;
}

Yes, I know what you're thinking. If you're new to programming, this logic can seem a little on the mind- bending side! This is the most complex use of logical operators and if statements you've seen so far. Don't feel too discouraged if you don't understand it right away or don't think you'll be able to write similarly complex code yourself any time soon. Look it over a few times, think about it while lying in bed at night, come back to it in a few days, and try it with some of your own games. It will gradually start to make sense'trust me! Seeing how others have solved problems and then trying out those solutions in your own games is an extremely important part of learning how to program. To help you out,
Figure 6-8
illustrates what's happening.

Figure 6-8.
Prevent the score from updating more than once per collision.

This bit of code also demonstrates another example of casting. You'll recall that casting is the system of converting one variable type into another.

The
score
variable was declared as a uint number type.

public var score:uint = 0;

You need the
score
variable to be a number so that you can update it by adding 1 each time there's a collision, like this:

score++;

So score could be 1, 5, 14, or any number, depending on how many times the objects have collided. A problem arises when you need to display this number in a text field.
TextField
objects can only display information in the form of Strings (letters or words). That means you need to convert the value of the score variable in to a string of characters that the text field can display. That's where casting comes in. Your code can force the
score
variable to be read as a String like this:

String(score)

The value of
score
can now be readily displayed in the output text field, like this:

output.text = String(score);

Checking for the end of the game

The score variable is also used for figuring out when the game ends. A very simple if statement inside the
enterFrameHandler
displays a trace message when the
score
reaches 5.

if(score == 5)
{
  trace("Game over");
}

But there's a problem with this system that you'll quickly notice. The program continues to add 1 to the score and display the “Game over” message well after the score reaches 5, as you can see in
Figure 6-9
. To work properly, the program should stop counting collisions when the score reaches 5 and the “Game over” message should only be displayed once.

Figure 6-9.
The program should stop counting collisions when the sore reaches 5, but it doesn't.

You can easily fix this by applying some simple logic.

Only check for a collision if the score is less than 5.

You know how to do this! It's just a matter of surrounding the collision code with one more if statement, like this:

Other books

The Cradle, the Cross, and the Crown by Andreas J. Köstenberger, Charles L Quarles
Unearthed by Gina Ranalli
Blur (Blur Trilogy) by Steven James
Rising Tides by Taylor Anderson
Lone Star Rancher by Laurie Paige
Gloria's Secret by Nelle L'Amour