Read Foundation Game Design with ActionScript 3.0, Second Edition Online
Authors: Rex van der Spuy
Figure 5-19.
Add the two background components to you game, along with your game character.
Now all you need to do is move thedistantBackground
object at a slower rate than theforeground
object. The directives you use might look like this:
foreground.x += -vx;
foreground.y += -vy;
distantBackground.x += -vx /
2;
distantBackground.y += -vy /
2;
When your game character moves, the distant background moves at half the speed of the foreground, making it look as though it's far in the distance. Try it! It's a mesmerizing effect. And there's also nothing stopping you from adding a third element as an extremely distant background object moving at an even slower rate.
To get you started on your own parallax scrolling experiments, take a look at the ParallaxScrolling program in the chapter's source files. Here's the code that makes it work:
package
{
import flash.display.Sprite;
import flash.display.DisplayObject;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
import flash.events.Event;
[SWF(width="550", height="400",
backgroundColor="#FFFFFF", frameRate="60")]
public class ParallaxScrolling extends Sprite
{
//Embed the distant background image
[Embed(source="../images/distantBackground.png")]
public var DistantBackgroundImage:Class;
public var distantBackgroundImage:DisplayObject
= new DistantBackgroundImage();
public var distantBackground:Sprite = new Sprite();
//Embed the distant foreground image
[Embed(source="../images/foreground.png")]
public var ForegroundImage:Class;
public var foregroundImage:DisplayObject
= new ForegroundImage();
public var foreground:Sprite = new Sprite();
//Embed the character image
[Embed(source="../images/character.png")]
public var CharacterImage:Class;
public var characterImage:DisplayObject = new CharacterImage();
public var character:Sprite = new Sprite();
//Create and initialize the vx variable
public var vx:int = 0;
//Variables for the inner boundary
public var rightInnerBoundary:uint;
public var leftInnerBoundary:uint;
public function ParallaxScrolling()
{
//Add the distant background
distantBackground.addChild(distantBackgroundImage);
stage.addChild(distantBackground);
distantBackground.x
= -(distantBackground.width - stage.stageWidth) / 2;
distantBackground.y = 0;
//Add the foreground
foreground.addChild(foregroundImage);
stage.addChild(foreground);
foreground.x = -(foreground.width - stage.stageWidth) / 2;
foreground.y = 0;
//Add the character
character.addChild(characterImage);
stage.addChild(character);
character.x = 225;
character.y = 290;
//Define the inner boundary variables
rightInnerBoundary
= (stage.stageWidth / 2) + (stage.stageWidth / 4);
leftInnerBoundary
= (stage.stageWidth / 2) - (stage.stageWidth / 4);
//Add the event listeners
stage.addEventListener
(KeyboardEvent.KEY_DOWN, keyDownHandler);
stage.addEventListener
(KeyboardEvent.KEY_UP, keyUpHandler);
stage.addEventListener
(Event.ENTER_FRAME, enterFrameHandler);
}
public function keyDownHandler(event:KeyboardEvent):void
{
if (event.keyCode == Keyboard.LEFT)
{
vx = -5;
}
else if (event.keyCode == Keyboard.RIGHT)
{
vx = 5;
}
}
public function keyUpHandler(event:KeyboardEvent):void
{
if (event.keyCode == Keyboard.LEFT
|| event.keyCode == Keyboard.RIGHT)
{
vx = 0;
}
}
public function enterFrameHandler(event:Event):void
{
//Move the player
character.x += vx
//Check the inner boundaries
if (character.x < leftInnerBoundary)
{
character.x = leftInnerBoundary;
rightInnerBoundary
= (stage.stageWidth / 2) + (stage.stageWidth / 4);
distantBackground.x -= vx / 2;
foreground.x -= vx;
}
if (character.x + character.width > rightInnerBoundary)
{
character.x = rightInnerBoundary - character.width
leftInnerBoundary
= (stage.stageWidth / 2) - (stage.stageWidth / 4);
distantBackground.x -= vx / 2;
foreground.x -= vx;
}
//Check the stage boundaries
if (foreground.x > 0)
{
foreground.x = 0;
distantBackground.x
= -(distantBackground.width - stage.stageWidth) / 4;
leftInnerBoundary = 0;
}
if (foreground.x < stage.stageWidth - foreground.width)
{
foreground.x = stage.stageWidth - foreground.width;
distantBackground.x
= ((distantBackground.width - stage.stageWidth) / 4) * -3;
rightInnerBoundary = stage.stageWidth;
}
}
}
}
This code employs all the techniques you've looked at in the chapter. However, it uses a little less code that the previous example because the scrolling just happens on the x axis.
There's a bit of this code that I need to clarify. When the scrolling reaches the limit of the foreground's left border, the foreground stops moving. This is the same effect you saw in the previous example. However, now you have to stop the distant background from moving as well. And because the distant background is moving at half the speed as the foreground, you can't stop it an x position of zero. You have to stop it at an x position that's one quarter of its width below zero, minus the stage width.
if (foreground.x > 0)
{
foreground.x = 0;
distantBackground.x
= -(distantBackground.width - stage.stageWidth) / 4;
leftInnerBoundary = 0;
}
Another brain twister for you! But perhaps
Figure 5-20
will help clarify this for you.
Figure 5-20.
Because the distant background is moving at half the rate, you have to stop it at an x position that's one quarter of its width below zero, minus the stage width.
The left boundary needs to do the same bit of gymnastics.
else if (foreground.x < stage.stageWidth - foreground.width)
{
foreground.x = stage.stageWidth - foreground.width;
distantBackground.x
= ((distantBackground.width - stage.stageWidth) / 4) * -3;
rightInnerBoundary = stage.stageWidth;
}
Don't let the math worry you. Just use it; it works!
So, is that it? No way! This chapter gave you a taste of setting up a player control scheme, but there's so much more refinement that can be done. Later in the book, you'll learn how to modify these models to incorporate acceleration, friction, and gravity into the character's movements. You'll also be looking at a player control scheme to allow the game to be played with a mouse instead of the keyboard.
In this chapter, you solved some extremely important problems central to game design that you'll see popping up again and again in different contexts in the chapters that follow. Experiment a bit with some of these techniques on your own, and I'll meet you in
Chapter 6
when you're ready. I'll show you how to create an environment that your objects can interact with using collision detection.
Welcome to a fun chapter! In these pages you'll build an interactive playground of clever little game design techniques that you can expand upon to build completely interactive two-dimensional environments, better known as action and adventure games! A whole grab bag of things from collision detection, building walls, and picking up and dropping objects—they're all here. With a little imagination, you'll be able to use these very simple techniques to produce different kinds of games. Hey, congratulate yourself: you've come a long way since page 1! All your hard work is about to pay off.
At the end of the chapter, I'll introduce theCollision
class, which is a custom class designed just for this book. It contains a specialized method for handling complex collisions between objects that you can use with any of your game projects.
What makes most computer games fun to play is that they are, in their essence, a simplified simulation of the real world. Like the real world, they contain objects that you can interact with in some way. These objects might be walls that block your movement, friends who help you, or enemies who harm you.
To create these sorts of interactive objects, you first need a way of finding out whether one object is touching another object. In computer game programming, this is called
collision detection
. Collision detection is just game programming jargon for what happens when things bump into one another. AS3.0 has a very simple way of detecting collisions between objects: thehitTestObject
method.
It's amazing what kind of power thehitTestObject
method can give you. In the examples in the following pages, you'll be looking at how you can use it to do the following:
With a little imagination, you'll be able to use these techniques to produce a richly varied number of games.
In this chapter's source files you'll find a project folder called BasicCollisionDetection that demonstrates howhitTestObject
works. Compile the project, and you'll see a scene that looks like
Figure 6-1
. Move the cat character around the stage with the arrow keys and watch what happens when the cat bumps into the monster. The word “Hey!!!” is displayed in the output text field. If you move the player object away from the enemy, the text field displays “No collision...” again.
Figure 6-1.
Use hitTestObject to change the words in the text field.
The code that makes all this work will be good for you to look at as a review of all the concepts and techniques you've learned so far. It embeds and displays images, uses a dynamic text field, uses keyboard control, and modularizes the constructor method's tasks into three separate methods. You should understand all this code by now, but if there's something you're a bit fuzzy on, now might be a good time to revisit the section in the book that covers it'before you get into some trickier stuff.
Out of all this code there's only one new technique you haven't seen before: the use of thehitTestObject
method. It's used to change the words in the output text field when the game characters collide. I've highlighted it in the listing of the BasicCollisionDetection program next. Can you figure out how it works?
package
{
import flash.display.Sprite;
import flash.display.DisplayObject;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
import flash.events.Event;
import flash.text.*;
[SWF(width="550", height="400",
backgroundColor="#FFFFFF", frameRate="60")]
public class BasicCollisionDetection extends Sprite
{
//Create the text objects
public var format:TextFormat = new TextFormat();
public var output:TextField = new TextField();
public var input:TextField = new TextField();
//Embed the character image
[Embed(source="../images/character.png")]
public var CharacterImage:Class;
public var characterImage:DisplayObject = new CharacterImage();
public var character:Sprite = new Sprite();
//Embed the monster image
[Embed(source="../images/monsterNormal.png")]
public var MonsterNormalImage:Class;
public var monsterNormalImage:DisplayObject
= new MonsterNormalImage();
public var monster:Sprite = new Sprite();
//Create and initialize the vx and vy variable
public var vx:int = 0;
public var vy:int = 0;
public function BasicCollisionDetection()
{
setupTextfields();
createGameObjects();
setupEventListeners();
}
public function setupTextfields():void
{
//Set the text format object
format.font = "Helvetica";
format.size = 32;
format.color = 0xFF0000;
format.align = TextFormatAlign.LEFT;
//Configure the output text field
output.defaultTextFormat = format;
output.width = 300;
output.height = 36;
output.border = true;
output.text = "";
//Display and position the output text field
stage.addChild(output);
output.x = 125;
output.y = 65;
}
public function createGameObjects():void
{
//Add the monster to the stage
monster.addChild(monsterNormalImage);
stage.addChild(monster);
monster.x = 125;
monster.y = 150;
//Add the character to the stage
character.addChild(characterImage);
stage.addChild(character);
character.x = 300;
character.y = 150;
}
public function setupEventListeners():void
{
stage.addEventListener
(KeyboardEvent.KEY_DOWN, keyDownHandler);
stage.addEventListener
(KeyboardEvent.KEY_UP, keyUpHandler);
stage.addEventListener
(Event.ENTER_FRAME, enterFrameHandler);
}
public function keyDownHandler(event:KeyboardEvent):void
{
if (event.keyCode == Keyboard.LEFT)
{
vx = -5;
}
else if (event.keyCode == Keyboard.RIGHT)
{
vx = 5;
}
else if (event.keyCode == Keyboard.UP)
{
vy = -5;
}
else if (event.keyCode == Keyboard.DOWN)
{
vy = 5;
}
}
public function keyUpHandler(event:KeyboardEvent):void
{
if (event.keyCode == Keyboard.LEFT
|| event.keyCode == Keyboard.RIGHT)
{
vx = 0;
}
else if (event.keyCode == Keyboard.DOWN
|| event.keyCode == Keyboard.UP)
{
vy = 0;
}
}
public function enterFrameHandler(event:Event):void
{
//Move the player
character.x += vx;
character.y += vy;
//Collision detection
if (character.hitTestObject(monster))
{
output.text = "Hey!!!";
}
else
{
output.text = "No collision...";
}
}
}
}
Let's find out howhitTestObject
works.
ThehitTestObject
method can be used to check whether any two objects have bumped into one another. Let's say that you have a Sprite object calledcar
that the player can control. You also have a Sprite object calledwall
. In your game, if the player's car hits the wall, it should crash.
In plain English, you would want to write some computer code that looks something like this:
if (the car hits the wall)
{
the car must crash;
}
Here's what this might look like in AS3.0 code:
if(car.hitTestObject(wall))
{
car.crash();
}
ThehitTestObject
method is attached to thecar
object with dot notation. It has an argument,(wall)
, which contains the name of the object that you want to check for a collision.
Figure 6-2
shows how this all fits together.
Figure 6-2.
Use the hitTestObject method inside a conditional statement to check for a collision between two objects.
Usually you use thehitTestObject
method inside the conditional statement of an if statement. If the objects are touching, the method returns a Boolean value oftrue
, and the directives inside the if statement run. If it returns a value offalse
(if the objects are not touching), the directives inside the if statement don't run.
Here's the section of code in the BasicCollisionDetection program that changes the text display in the output field:
public function enterFrameHandler(event:Event):void
{
//Move the player
character.x += vx;
character.y += vy;
//Collision detection
if (character.hitTestObject(monster))
{
output.text = "Hey!!!";
}
else
{
output.text = "No collision...";
}
}
It very simply checks to see if the two objects are touching. If they are, “Hey!!!” is displayed in theoutput
text field. If they aren't, the words “No collision…” are displayed.
Very importantly, notice that thishitTestObject
if statement occurs inside theenterFrameHandler
. That means that the program is checking for a collision 60 times per second. That's why the display changes instantly when the cat and the monster touch. And it instantly changes back again when they aren't touching.hitTestObject
is almost always used inside theenterFrameHandler
.
Isn't amazing what a powerful effect just a few lines of code produces? And it gets even better!
You can put any directives you like inside the if statement that checks for a collision. In this next example, the appearance of the monster changes when the collision occurs. Let's take a look at the effect and then I'll explain how it works.
Open the StateChange project that you'll find in the chapter's source files. Compile the project and watch what happens when the objects collide: the monster opens its mouth.
Figure 6-3
shows what you'll see.