Game Design in Flash 5, Part II: Heroes & Villains

In Part One of this game-building tutorial, we created a hero ship and a bullet and programmed the two to work in tandem. The ship can be steered via the arrow keys, and the space bar launches the bullet from the ship’s location.

Article Continues Below

Now it’s time to start turning this experiment from a primitive environment simulator to a real game.

This game, like all others, involves conflict, rules, and the potential for loss as well as gain.  Heroes alone make for boring games.  We need a villain.

Rock show#section2

Now we’ll add a rock to the mix – yes, just one for now – and assign it the following script:

onClipEvent (enterFrame) {
_x += deltax;
_y += deltay;
if (_x<0) {
_x = centerx*2;
} else if (_x>(centerx*2)) {
_x = 0;
}
if (_y<0) {
_y = centery*2;
} else if (_y>(centery*2)) {
_y = 0;
}
if (this.hitTest(_root.bullet._x, _root.bullet._y, true)) {
destroyRock();
}
function findRandomPosition () {
// We’re going to use some simple trig to put
// the rock away from the center of the screen
// We defined the centerx and centery when this clip first loaded
// (Down below in the onClipEvent(load) event handler)
radius = 190+Math.random()*75;
// This creates a random number between 190 and 265
// Which controls how far from the center of the
// screen the rock will appear
// We don’t want it smack in the middle, where the ship is.
angle = Math.random()*(2*Math.PI);
// this generates an angle between 0 and 2 * PI
// --in other words, somewhere in a full circle
_x = centerx+Math.cos(angle)*radius;
// The x position of the rock is the x component
// of its angle, the cosine, multiplied by the radius
_y = centery+Math.sin(angle)*radius;
// The y position of the rock is the y component
// of its angle, the sine, multiplied by the radius
deltax = (Math.random()*10)-5;
// Little formula to calculate a random number from -5 to 5
deltay = (Math.random()*10)-5;
}
function destroyRock () {
findRandomPosition();
_root.bullet._x = -100;
_root.bullet._y = -100;
_root.bullet.deltax = 0;
_root.bullet.deltay = 0;
}
}
onClipEvent (load) {
// This gets called when this rock first appears
centerx = 275;
centery = 200;
radius = 190+Math.random()*75;
angle = Math.random()*(2*Math.PI);
_x = centerx+Math.cos(angle)*radius;
_y = centery+Math.sin(angle)*radius;
deltax = (Math.random()*10)-5;
deltay = (Math.random()*10)-5;
}

Layer of stone#section3

How do you create a rock?  Well, if you’ve got my work ethic, you draw a grey circle and select it and hit F8, then you hit Control-i (Command-i for Mac) and type “rock” (without the quotes, yes).  Create a new layer, cut and paste this rock onto it, and rename it “rock.” Ah.  Good enough for government work, as they say.

In fact, while we’re at it, let’s make sure all our layers are named appropriately.  There should be a layer named “ship” that contains the ship – and only the ship.  There should be a layer named “bullet” that contains the bullet – and only the bullet.  There should be a layer named “evil urges” that contains – okay, rename it “actions” and leave it empty for a moment.

Playtime is over#section4

Remember how I said that using onClipEvent() scripts attached to our movie clips freed us from the tyranny of timeline-based scripts?  I meant every word of it.  But that doesn’t mean the movie ain’t gonna play.  No, it’s going to play, all right – and it’ll loop right back to the beginning when it reaches the end of the frames in the main timeline.

Is this a disaster? Well, no. Not yet, it isn’t.  But it will be once we start doing fancy stuff, so let’s put an empty keyframe on frame 14 of the “actions?`; layer and bring up the script window.  Type in a frame script that says

stop();

…and I’ll give you a shiny new nickel if you can’t tell me exactly what that does.

So add the script to the rock by selecting it and then opening the script window.  Note the other onClipEvent() script I’ve added to complement the familiar onClipEvent(enterFrame) script.

Lock and (load)#section5

OnClipEvent(load) is an event that gets called when a clip appears in the score – in other words, when the main timeline reaches a part of the movie that contains this clip. If you want this function to fire only once in the movie, make sure the movie clip doesn’t appear and disappear from the main timeline.

In this case, the (load) script is called once, and it randomizes the location of the rock as well as its x and y speeds. Think of it as a special function that gets called automatically when the movie clip appears in your main timeline.

Also please note that this code to find a random location for the rock exists up in the onClipEvent(enterFrame) script as well –  inside a function named findRandomPosition().  (It’s a good thing to use very expressive names for variables and functions, by the way. You will be happy later.  Anyone looking at your code will be happy too. As Flaubert said: “Be regular and orderly in your life, like a bourgeois, so you may be violent and original in your work.”)

Well, the question comes to mind:  If we’ve got a function that’ll find a nice, random location for us, why don’t we just call it from the onClipEvent(load) handler?

Uh… that’s a very good question.  Because it doesn’t work.  Moral: Calling functions from a (load) handler isn’t a good idea.

So I’ll explain really quickly what the (load) handler is doing, and then I’ll explain the code in findRandomLocation(), because that’s the same algorithm that the (load) handler uses to position the rock.

The harder they code, the harder they fall#section6

What is the (load) handler doing? It’s declaring the variables centerx and centery, which are the horizontal and vertical center points of the screen, respectively. The movie clip will remember these variables for its entire life, which is nice and saves us work.

It also means that if we ever enlarge the size of the stage, all we have to do is change those variables in the (load) handler and the rock will be able to handle the change perfectly.

In fact, take a sneak peek at the wraparound code for the rock. You’ll see that it no longer checks to see if the rock’s x value is larger than 570 (the right edge of the screen) or if its y value is larger than 400 (the bottom edge of the screen).

Instead, it checks the rock’s x and y positions based on centerx and centery.  If centerx is 275, then the right edge must be centerx*2.  Well, in fact, this is circular reasoning, based on the fact that we assigned centerx the value of 275 simply because we knew that was halfway across the screen. But this will also allow us to change the size of the stage and then simply change centerx and centery, which is a whole lot better than scouring the code for references to 275 or 200 or 570 or 400.

That’s what programmers mean by avoiding the hard coding of values.  Simply put: if there’s a number that you’re going to refer to repeatedly, and it’s potentially going to change at some point in the future, then assign it to a variable and always use that variable for the rest of your movie.  Makes life easier.

In fact, if you were religious about it, you could move your centerx and centery variables to the main timeline and refer to them every time you need them.  But for now, we’ll let it lie.

Shot with hs own gun#section7

Our code to wrap the rock around the edges of the screen –  well, really to pop it across to the other side when it gets too near the edge –  is now pretty clean and flexible, so why not use it on the bullet and the ship as well?  Of course, this means we also have to use an onClipEvent(load) script to set centerx and centery for each of those movie clips, but that’s a moment’s work.

The only catch is this: that code will have to be altered for the bullet. As we’ll see further along, there are times when we definitely want the bullet to stay off screen –  namely, when it’s not moving.  So I’ve put the bullet’s wraparound code inside a quick conditional test:

if ((deltax != 0) and (deltay != 0)) {
//wraparound code goes here
}

This basically makes sure that the code only executes if the bullet is sitting still.  The != operator means “does not equal” and is a familiar part of C-based syntax, so JavaScript coders will know it well.

This kind of conditional will appear many places in this tutorial.  In fact, state-based decisions are key to game programming in any language.  Is the bullet supposed to be moving? Then move it.  Is the rock hittable? Then check to see if it’s being hit.

Sometimes the best way to keep track of an object’s current state is to create a special variable to hold it –  say, “state”  –  and change it when the object changes state (e.g., when a bullet is launched, change its state to “flying”).

But in this case, since the bullet only has two states (moving and still), checking if deltax and deltay are zero will do the trick.

Random acts of positioning#section8

Let’s go back to the rock’s script.  I’ve commented the code pretty heavily, but since it involves some math, there’s always room for dilation.

Here are the guts of it:

centerx = 275;
centery = 200;radius = 130+Math.random()*70;angle = Math.random()*(2*Math.PI);_x = centerx+Math.cos(angle)*radius;_y = centery+Math.sin(angle)*radius;deltax = (Math.random()*10)-5;deltay = (Math.random()*10)-5;

We want the rock to appear randomly, sure.  But we don’t want it to appear smack on top of the ship.  So let’s do it the classic arcade way:  by positioning the rock randomly around the edge of the screen.

Let’s pretend the rock is going to be somewhere on the perimeter of an imaginary circle.  The circle is going to be centered around the middle of the screen, and its size is going to be somewhat random. We want it to be bigger than 130 (or else its edge would be pretty close to the center) and less than, say, 200 (which would put its edge dangerously close to the edge of the screen, and we want the rock onscreen, after all).

So we set our radius to a random number between 130 and 200.  And then we just pick a random angle to find a point somewhere on the circle’s perimeter.  By setting a variable named “radius“ to a random number between 0 and 2*pi, we do just that.  (Remember, trig functions use radians, not degrees – if life were perfect, we’d use a number between 0 and 360 to indicate degrees. Then again, if life were perfect, we’d also fly to work on hydrogen-powered hover scooters.)

A can of worms#section9

We’ve got a radius and a nice, random angle.  Now let’s use trig to convert those values into x and y coordinates our rock can use.

To find the x position, you multiply the radius by the cosine of the angle.  To find the y position, you multiply the radius by the sine of the angle.  Simple!

Ah, we’re not done yet.  We can’t move a ship to that position yet, because we forgot that our imaginary circle is supposed to start at the center of the screen. But our simple trig calculations will assume it’s at 0,0, which means that the odds are pretty good it’ll position our rock off to the left somewhere or off the top.

We fix this by adding centerx to the x position that our circle trick gave us, and adding centery to the y position it gave us.

Astutely, you might well be asking:  “But your radius is set to a random number between 130 and 200.  Why 130 as the minimum, and why 200 as the maximum?  If the screen changes size, won’t that have to change as well?  Isn’t this hard-coding?”

Well, yes.  The best way to calculate a random radius would be a formula based upon centerx and centery (whichever is smallest).  For the sake of clarity I’ve avoided opening that can of worms here, but please feel free to try your hand at it.

When bad things happen to good rocks#section10

In addition to the (load) script, there’s also a function called destroyRock().  It gets called elsewhere in this (enterFrame) script, but in point of fact it could be called by any script anywhere in this entire movie file.  If we wanted our ship to be able to destroy this rock, we could add…

_root.rock.destroyRock()

…to our ship’s script, and the rock would start executing the code inside its destroyRock() function.

In fact, later we’ll be calling the rock’s findRandomPosition() function from the ship.  But let’s not get ahead of ourselves.

Instead, let’s focus on destroyRock().  What does it do?

Two things: it tells the bullet to go take a powder, and it tells the rock to randomly position itself somewhere onscreen using the same code that was used in the (load) handler.

Here’s the code for getting rid of the bullet:

_root.bullet._x = -100;
_root.bullet._y = -100;

Or, in other words, get off this screen and go to your room.

_root.bullet.deltax = 0;
_root.bullet.deltay = 0;

And don’t go anywhere.

Hit parade#section11

Is the rock being hit?  That’s the question we must ask before calling destroyRock(), after all.

So let’s look at the code that checks to see if the rock is being hit by the bullet.  This is the code that will call destroyRock() if, in fact, it determines that such brutality is taking place.

if (this.hitTest(_root.bullet._x, _root.bullet._y, true)) {
destroyRock();
}

Seems pretty simple, eh?  The hitTest() function is a beautiful addition to Flash ActionScript, and in fact it can work one of two ways.  The way I’ve used, we send three arguments to the function (a fancy way of saying I’ve put three pieces of data between the parentheses): I send an x, a y, and a special kind of constant called a Boolean true to the hitTest function.  (You don’t have to know much about Boolean constants and variables to use them; they’re essentially a prettied-up way of saying 0 and 1, and they’re nice to use for any situation that needs a true or false answer.  In this case, it tells the Flash interpreter that I do indeed want to have the shape flag set.)

Flagging interest#section12

The shape flag allows us access to some powerful collision detection that Flash 4 simply couldn’t give us.  Setting the shape flag to true tells the Flash engine that we want it to look carefully to check if that x and y coordinate are hitting actual pixel data inside the rock movie clip and are not in fact on top of empty space.  This is more rigorous than simply testing to see if the pixel falls inside the bounding box of the rock, which might register as a hit even if the pixel were passing closely by.  When you’ve got sprites that don’t happen to be box-shaped, and you really want a higher level of precision, hitTest() is a good thing.

Cheap reward#section13

When you don’t need that kind of precision, and you don’t want to bog down the processor in pixel-by-pixel testing, you can use hitTest() another way, and it’s even simpler: just send one argument, and make that argument the name of the movie clip you’re checking.  So if this script were attached to the rock:

if(this.hitTest(_root.bullet){}

We’d get a positive result every time the bounding box of the bullet overlapped with the bounding box of the rock.  Faster, sloppier.

Boxing day#section14

If the term “bounding box” seems confusing, simply stop the movie and click on a movie clip.  That blue box around it is the bounding box.  No, it doesn’t precisely fit the sprite, unless that sprite happens to be perfectly box-shaped.  Yes, this is a very rough and inexact way of detecting collisions.  Sometimes it’s enough.  But not this time.  That’s why the rock is checking more carefully for collisions with the bullet.  And that’s why there are three pieces of data between the parentheses after hitTest, not just one.

The sweet science of collision detection, including use of the Pythagorean theorem to measure distance – which works very well for round sprites like, well, a rock in a space game – is elucidated a bit on Colin Moock’s excellent site.

There are also fine tutorials on Flashkit and other Flash coding sites.

But I felt it important to walk through the new hitTest() function, since it’s likely to be used extensively by Flash game developers, and the ability to do pixel-accurate testing in real time is especially nifty.

Director coders who have monkeyed with Flash sprites will recognize hitTest, which was represented in Lingo years ago with a Flash Asset Xtra handler called… well, hittest. And it had a shape flag too.  But I’m sure that’s just a coincidence…

Inch by inch#section15

So we’re on our way to having a game.  If you like, you can download the sample file and check it out for yourself.  We’ve got a hero ship, a bullet it can fire, and a rock that can be shot and destroyed (though for now it simply reappears elsewhere).

Many important pieces are still missing, though: we need to be able to fire more than one bullet, we need to have more than one rock, the rocks need to stay destroyed, and there needs to be an element of danger.  But these things will have to wait for Part III.

No Comments

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA

I am a creative.

A List Apart founder and web design OG Zeldman ponders the moments of inspiration, the hours of plodding, and the ultimate mystery at the heart of a creative career.
Career