Read Foundation Game Design with ActionScript 3.0, Second Edition Online
Authors: Rex van der Spuy
Figure 7-83
. Make a big background image.
I made a class for this new background image calledBigBackground
.
I also needed to separate the black rounded rectangle that frames the time display from the border. That's because the black rectangle should stay fixed in one position on the stage, not scroll with the background. I called this black rectangle image timeDisplay.png and created a class for it calledTimeDisplay
. TheTimeDisplay
andBigBackground
classes are identical to the other game object classes that you looked at in this chapter.
I created theBigBackground
andTimeDisplay
objects using theBigBackground
andTimeDisplay
classes like this:
public var background:BigBackground = new BigBackground();
public var timeDisplay:TimeDisplay = new TimeDisplay();
I then added them to the stage and positioned them like this:
addGameObjectToStage
(
background,
-(background.width - stage.stageWidth) / 2,
-(background.height - stage.stageHeight) / 2
);
addGameObjectToStage(timeDisplay, 200, 5);
The background is centered using the same formula you used in
Chapter 6
.
Make sure that you add thetimeDisplay
object
after you add all the other objects to the stage
. That will ensure that it floats above them when the game scrolls.
With the background's position in place, you can start to add the game objects. However, now that you're dealing with a huge space, you can't position them using simple stage coordinates. You need to provide coordinates relative to where you want them to be placed on the background. Most of these objects will be off-stage when the game starts.
This is luckily very easy to do. Just add the game objects' x and y coordinates to the background's x and y coordinates. Here's how the fist box is positioned:
addGameObjectToStage(box1, background.x + 100, background.y + 100);
This places it 100 pixels to the right and 100 pixels below wherever the background's top left corner happens to be. You can see this in
Figure 7-84
.
Figure 7-84
. The game objects are positioned relative to the background object's x and y position.
This is really convenient because if you ever change the initial start position of the background, the box's position will automatically adjust its own position to compensate.
All the game objects are positioned like this, relative to the background's position:
//The boxes
addGameObjectToStage(box1, background.x + 100, background.y + 100);
addGameObjectToStage(box2, background.x + 200, background.y + 300);
addGameObjectToStage(box3, background.x + 400, background.y + 450);
addGameObjectToStage(box4, background.x + 100, background.y + 600);
addGameObjectToStage(box5, background.x + 300, background.y + 700);
addGameObjectToStage(box6, background.x + 500, background.y + 200);
addGameObjectToStage(box7, background.x + 600, background.y + 600);
addGameObjectToStage(box8, background.x + 500, background.y + 500);
addGameObjectToStage(box9, background.x + 700, background.y + 200);
addGameObjectToStage(box10, background.x + 700, background.y + 400);
addGameObjectToStage(box11, background.x + 800, background.y + 500);
addGameObjectToStage(box12, background.x + 100, background.y + 700);
addGameObjectToStage(box13, background.x + 900, background.y + 300);
addGameObjectToStage(box14, background.x + 800, background.y + 200);
//The bombs
addGameObjectToStage(bomb1, background.x + 55, background.y + 710);
addGameObjectToStage(bomb2, background.x + 255, background.y + 310);
addGameObjectToStage(bomb3, background.x + 355, background.y + 160);
addGameObjectToStage(bomb4, background.x + 855, background.y + 510);
addGameObjectToStage(bomb5, background.x + 755, background.y + 610);
For this to work, you have to make sure that you set the x and y position of the background first, before you set those of the game objects.
Here's all the code from theenterFrameHandler
that scrolls the background and game objects. It uses a custom method calledscroll
to do the actual work of scrolling the game objects. I'll explain how all this works in the pages ahead.
//Calculate the scroll velocity
var temporaryX:int = background.x;
var temporaryY:int = background.y;
//Check the stage boundaries
//1. Check the inner boundaries
if (character.x < leftInnerBoundary)
{
character.x = leftInnerBoundary;
rightInnerBoundary
= (stage.stageWidth / 2) + (stage.stageWidth / 4);
background.x -= vx;
}
else if (character.x + character.width > rightInnerBoundary)
{
character.x = rightInnerBoundary - character.width
leftInnerBoundary
= (stage.stageWidth / 2) - (stage.stageWidth / 4);
background.x -= vx;
}
if (character.y < topInnerBoundary)
{
character.y = topInnerBoundary;
bottomInnerBoundary
= (stage.stageHeight / 2) + (stage.stageHeight / 4);
background.y -= vy;
}
else if (character.y + character.height > bottomInnerBoundary)
{
character.y = bottomInnerBoundary - character.height;
topInnerBoundary
= (stage.stageHeight / 2) - (stage.stageHeight / 4);
background.y -= vy;
}
//2. Background stage boundaries
if (background.x > 0)
{
background.x = 0;
leftInnerBoundary = 0;
}
if (background.y > 0)
{
background.y = 0;
topInnerBoundary = 0;
}
if (background.x < stage.stageWidth - background.width)
{
background.x = stage.stageWidth - background.width;
rightInnerBoundary = stage.stageWidth;
}
if (background.y < stage.stageHeight - background.height)
{
background.y = stage.stageHeight - background.height;
bottomInnerBoundary = stage.stageHeight;
}
//3. Character stage boundaries
if (character.x < 50)
{
character.x = 50;
}
if (character.y < 50)
{
character.y = 50;
}
if (character.x + character.width > stage.stageWidth - 50)
{
character.x = stage.stageWidth - character.width - 50;
}
if (character.y + character.height > stage.stageHeight -50)
{
character.y = stage.stageHeight - character.height - 50;
}
//Calculate the scroll velocity
var scroll_Vx:int = background.x - temporaryX;
var scroll_Vy:int = background.y - temporaryY;
//Use the scroll velocity to move the game objects
scroll(box1, scroll_Vx, scroll_Vy);
scroll(box2, scroll_Vx, scroll_Vy);
scroll(box3, scroll_Vx, scroll_Vy);
scroll(box4, scroll_Vx, scroll_Vy);
scroll(box5, scroll_Vx, scroll_Vy);
scroll(box6, scroll_Vx, scroll_Vy);
scroll(box7, scroll_Vx, scroll_Vy);
scroll(box8, scroll_Vx, scroll_Vy);
scroll(box9, scroll_Vx, scroll_Vy);
scroll(box10, scroll_Vx, scroll_Vy);
scroll(box11, scroll_Vx, scroll_Vy);
scroll(box12, scroll_Vx, scroll_Vy);
scroll(box13, scroll_Vx, scroll_Vy);
scroll(box14, scroll_Vx, scroll_Vy);
scroll(bomb1, scroll_Vx, scroll_Vy);
scroll(bomb2, scroll_Vx, scroll_Vy);
scroll(bomb3, scroll_Vx, scroll_Vy);
scroll(bomb4, scroll_Vx, scroll_Vy);
scroll(bomb5, scroll_Vx, scroll_Vy);
Here's the scroll method used by the code above:
public function scroll
(gameObject:Sprite, scroll_Vx:int, scroll_Vy:int):void
{
gameObject.x += scroll_Vx;
gameObject.y += scroll_Vy;
}
Most of this code is used to scroll the background and set the stage boundaries for both the character and the background. You've see all of it before—it's a combination of the scrolling code from
Chapter 5
, along with the extra modification you made in this chapter to prevent the character from crossing the playing field's 50 pixel wide borders. Review
Chapter 5
and the earlier section in this chapter if you're unsure about how any of the code works.
What's new here is that the code is making all the game objects move relative to the background. That makes them look as if they're fixed in position to the background. Of course, it's just an illusion, but a brilliant one. Let's see how it works.
First, you have to create two variables to temporarily capture the current x and y position of the background. You have to do this
before
you scroll the background. These variables are calledtemporaryX
andtemporaryY
.
var temporaryX:int = background.x;
var temporaryY:int = background.y;
All they do is record the background's position before it moves.
After the background is moved with the scrolling code, you use these variables to calculate something called the
scroll velocity
. This tells you the difference between the background's previous position and its current position. Thescroll_Vx
andscroll_Vy
variables calculate this.
var scroll_Vx:int = background.x - temporaryX;
var scroll_Vy:int = background.y - temporaryY;
Now that you know the difference, you can use it to correctly reposition the game objects. All you need to do is apply thescroll_Vx
andscroll_Vy
values to the objects' x and y positions, like this:
box1.x += scroll_Vx;
box1.y += scroll_Vy;
That's all you need to do. The objects will now remain fixed in place relative to the background, no matter how it scrolls.
However, the code inTimeBombScroll
goes one step further. Rather than meticulously setting the x and y positions of 14 boxes and five bombs, it delegates this work to a method calledscroll
.
scroll(box1, scroll_Vx, scroll_Vy);
Thescroll
method's function definition then takes care of the repetitive work of setting the objects' x and y properties, like this:
public function scroll
(gameObject:Sprite, scroll_Vx:int, scroll_Vy:int):void
{
gameObject.x += scroll_Vx;
gameObject.y += scroll_Vy;
}
It just saves you a bit of tedious typing. And this is really all there is to it. Here's the procedure you have to follow to make sure this works:
temporaryX
andtemporaryY
variables.scroll_Vx
andscroll_Vy
values to find out by how much the background has moved.scroll_Vx
andscroll_Vy
values to the game objects that you want to scroll along with the background.You have to add the code in
exactly
this order for this to work. And, of course, all this code is running inside theenterFrameHandler
.
This is actually an easy introduction to a very precise game animation technique called
Verlet integration.
Verlet integration isn't covered in this book, but you can read all about it in
Advanced Game Design with Flash.
The basic process of capturing an object's position in temporary variables, moving the object, and then calculating the difference is what Verlet integration is all about.
The rest of the code in TimeBombScroll.as is identical to TimeBombPanic.as, except for two small changes: the character is centered when the game starts and the time limit has been increased to 20 seconds. (But, come on, you should still be able to beat it in 10!) Take a look the full code in the source files so that you can see it all in context.
You now know how to make a complete game from beginning to end. This chapter showed you how to make game graphics, control them with code, and turn them into a real working game.
But there are still a lot of things you'll want your games to do that you haven't learned yet. How do you make enemy game objects that move by themselves? How can you switch game levels? And how can you fire bullets?
Chapter 8
, coming up next, will explain all this and more.
Time Bomb Panic is a good model of a small, one level game. It's a simple concept, takes only ten seconds to play, and you still have enough time in the day to play with your cats or go surfing. But it's probably not the kind of game you want to make. Those game ideas that keep you lying awaking at night planning no doubt involve hundreds of levels; take 50 hours or more to play; and contain dozens of different game objects, puzzles, and interesting scenarios. They're big games. In this chapter you'll learn how to make them.
The game you're going to look at this chapter is called Monster Mayhem. It's a two level game with just a few objects, but you can use the same structure to make games with any number of levels and objects and still keep your programming code under control. It's a good model for a big game—and for most of the smaller ones you'll make as well.
You'll find the game in the MonsterMayhem project folder in this chapter's source files. Play through it a few times to get a sense of what it's all about. In the first level, you need to kill the two monsters wandering around the stage. Press the space bar to launch a star projectile upwards; if the star hits the monsters three times they explode. The game ends if any of the monsters touch the character. If you kill them both, the second level loads. There are four monsters in level two, and they move much faster, but this time you can fire the star in four directions. Can you kill all four to beat the game? It's harder than it looks!
Figure 8-1
shows how the game plays.
Figure 8-1.
Monster Mayhem
The structure of the code in Monster Mayhem game is radically different from any previous games you've made in the book so far. Each level of the game is contained entirely inside a single Sprite. When a level is complete, the main application class removes the current level Sprite from the stage and adds a new one. That means that none of the game objects, like the character or the monsters, are added to the main stage. They're all first added to a containing Sprite. And it's that containing Sprite that's added to the stage by the application class.
To get a clearer idea of how this works, let's compare the structure of Monster Mayhem with Time Bomb Panic. In Time Bomb Panic, all the game objects are created as separate classes. Those classes are then added directly to the stage in the main application class.
Figure 8-2
illustrates this structure at work.
Figure 8-2.
In Time Bomb Panic all the objects are added to the stage in the main application class.
In Monster Mayhem, all the game objects are first added to a class calledLevelOne
. An object called_levelOne
is then added to the stage by theMonsterMayhem
application class.
Figure 8-3
Illustrates this.
Figure 8-3.
In Monster Mayhem all the objects are first added to the LevelOne class. The main application class then adds a _levelOne object and adds it to the stage.
LevelOne is a Sprite. That means that all the action in the game, and all the game logic, is taking place inside the LevelOne Sprite, not the application class. All the application class does is to add the level to the
stage so that you can see it. The reason for containing the entire game level in a Sprite is so that the application class can easily switch levels by adding or removing them from the stage.
And here's something really important to know:
only the application class has access to the stage
. That means it's the only class that can make objects visible by displaying them on the stage. Infuse this into your memory cells because it's really important in understanding some technical details of the code you'll need to know soon. The application class is the only class that can use a line of code like this:
stage.addChild(gameObject);
Other classes can display objects inside themselves
but not directly on the stage
. They can only add objects to themselves by referring tothis
, which means “this class.”
this.
addChild(gameObject);
You can see from
Figure 8-3
that theLevelOne
class is adding objects to itself using code like the following:
this.addChild(_character);
this.addChild(_monster);
this.addChild(_star);
This means that these objects are being added to theLevelOne
class. That's just fine, but you won't be able to actually see any of these objects until theLevelOne
class that's containing them is visible on the stage itself. That's why it's the job of the application class to create a_levelOne
object from theLevelOne
class and add it to the stage, like this:
stage.addChild(_levelOne);
Let's take a look at the application class,MonsterMayhem.as
, and see how it addslevelOne
to the stage. Here's the entire class:
package
{
import flash.display.Sprite;
import flash.events.Event;
[SWF(width="550", height="400",
backgroundColor="#FFFFFF", frameRate="60")]
public class MonsterMayhem extends Sprite
{
private var _levelOne:LevelOne;
private var _levelTwo:LevelTwo;
public function MonsterMayhem()
{
_levelOne = new LevelOne(stage);
_levelTwo = new LevelTwo(stage);
stage.addChild(_levelOne)
stage.addEventListener("levelOneComplete", switchLevelHandler)
}
private function switchLevelHandler(event:Event):void
{
trace("Hello from the application class! Switch levels!");
stage.removeChild(_levelOne);
_levelOne = null;
stage.addChild(_levelTwo);
}
}
}
The application class has these jobs:
_levelOne
and_levelTwo
. These are the game levels. Both of these objects are passed a reference to the stage in their arguments when they're created, like this:_levelOne = new LevelOne(stage);
I'll explain how this works and why this is important soon.
_levelOne
object to the stage._levelOne
object from the stage and adds_levelTwo
. Don't worry about this bit of code in theswitchLevelHander
yet'you'll look at it in detail later in the chapter.Notice also that the application class only imports two classes:Sprite
andEvent—
nothing else. That's because it doesn't need any other classes to do the simple jobs it does.
There's something new inMonsterMayhem.as
that you haven't seen yet. The two level objects,_levelOne
and_levelTwo
, are declared as private.
private
var _levelOne:LevelOne;
private
var _levelTwo:LevelTwo;
TheswitchLevelHandler
is also declared as private.
private function switchLevelHandler(event:Event):void
{...
Note that private means that those variables and methods can be used only within the class they're defined. They can only be used in theMonsterMayhem
class and nowhere else.
If you don't use the private keyword when you declare a property or method, AS3.0 assumes that they're public. Public properties can be accessed freely by any other classes. You can use the public keyword to make this explicit in your code if necessary.
Why should you declare a variable or method as private? Imagine that your house is a class and your oven is one of the class's variables. Your oven is having trouble switching on, so you call a repairman to take a look at it. But you're really busy and can't be home when the repairman comes, so you leave the door unlocked and trust that all will be well. Best-case scenario: you come home to find that your oven works, but a vase is lying broken on the floor, an empty pizza box is on the sofa, and a bill arrives at the end of the month for all kinds of pay-per-view movies you know you never watched. Worst-case scenario: you come home to find your house a smoldering ruin and all the other houses in the neighborhood up in flames. If only you could have been there to tell the repairman (who was standing ready with his 10,000- volt charge-jumper), “It's a gas stove, not electric!” Because your stove was public, any other class that doesn't know what it's doing, such as the repairman, can make any changes it wants to and cause havoc with your game. If the stove were declared as private, the clueless repairman would be locked out.
In a very small game with only a few classes, you could certainly get away with keeping all of your variables and methods public, and everything would be just fine. In a larger game, however, you'd be opening yourself up to a potential debugging nightmare scenario. So, in the interest of helping you develop good long-term programming habits, all the code in this book will keep a class's variables and methods private, unless another class needs to access them.
Using private properties to lock down a class in this way is an aspect of object-oriented programming called
encapsulation.
Encapsulation means that your class is completely sealed off from tampering by other classes and is as self-contained as possible. If other classes want to access or modify any properties in an encapsulated class, they have to follow very strict rules about doing so.
AS3.0 also has two special accessor methods calledget
andset
that let classes use another class's private properties in a controlled way. They're not used in this book but you can read more about them in the chapter “Methods” from Adobe's online document
Programming ActionScript 3.0 for Flash
athttp://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/
.