Creating Shoot Em Up


Creating Shoot 'Em Up

So far, we haven't created any shooting games . The Web is full of them, so I figure we should do one, too. A shooting game can be simple with only a dozen lines of script, or it can be extremely complex. Our game is going to be somewhere in the middle.

The Idea

I told Rachel that I wanted a shooting game in which some kind of creature ran around some kind of scene, ducking behind objects and trying to avoid getting shot by the player. Rachel's response was to send me some art of a chubby purple dinosaur that could easily have gotten a job hosting a children's television show.

After consulting with the powers that be, I decided to change his color to yellow, name him Lester, and alter him slightly to make him more of a hippo than a dinosaur. The results can be seen in Figure 7.4.


Figure 7.4: Rachel has created a chubby yellow hippo that our user will be able to hunt.

For a scene, I suggested things like a city alley, a forest, a convenience store, the post office, and so on. Rachel replied with some art of a forest setting, complete with large trees for Lester to duck behind while evading the player's shotgun blasts. Our background can be seen in Figure 7.5.

click to expand
Figure 7.5: The forest where the player will hunt Lester.

I would like it if Lester would come bounding through the forest to the front of the stage where he could give the player instructions using bubble text, like in comic books. After explaining the rules of the game, Lester should start running around, hiding behind trees until the player kills him.

When Lester gets shot, the blast should leave a bullet hole and he should make a pained face. Rachel constructed a version of Lester where he was in the midst of being blasted by the player's shotgun. This art can be seen in Figure 7.6.


Figure 7.6: Lester looks like he's really hurting when the player puts a slug into his thick yellow hide.

When Lester is shot a certain number of times, he should fall over dead. At this point, a new Lester will be created that will run around the stage to be shot. The new Lester will move more quickly and be harder to shoot.

Tip  

Whenever my initial vision does not end up matching the finished product, I think of the movie Grease . (Don't laugh .) In that movie, some guys dream of a souped-up car called Greased Lightning that they can drag race.They end up building the car and winning the all-important race with it, but the car they build is nothing like the car they dreamed of. It's important to realize a vision for what it is. If you become too attached to the vision, you can miss the opportunity to improve and you become inflexible and closed minded. By allowing your vision to be a guide and not a religious doctrine, you open yourself to more possibilities.

I would like some kind of scoreboard. A time limit on the game would also be nice. In addition, I would like the shotgun to hold several shells , with a reload button to replenish the supply. The player's gun should shoot once and then require a moment before it is cocked and ready to fire again.

That's enough of the main idea for me to get started. After I'm into the implementation, I can worry about the details of how the game will play. I often find it more useful to get some of my game created, particularly putting the art assets in place, so that I get a better feel for how things are going to work. If I try to decide too much what the game will look like at this point, I'll end up changing it as I implement it and the time spent will be wasted .

Creating the Art Assets

It's time to start organizing our library for Shoot 'Em Up . We'll need to do quite a few things, and our library for this game will have more elements than any of our previous games. The following is a list of elements we're going to need:

  • Lester

  • Forest

  • Message movie and text bubble

  • Scoreboard

  • Frame counter

  • Crosshair

  • Bullet

We need to look at each of these elements individually. We'll begin with the most complex piece of art: Lester.

Creating Lester

For the Lester symbol, we're going to need several things. First we'll need the normal Lester art. Then we'll need to add the graphics that show Lester being shot and appearing to be in pain. Finally, we'll need the art for a dead Lester, for when the player has pumped enough lead into the cute hippo to drop him.

I've decided that the best way to do this is to place all the art in one symbol, named Lester, and then use ActionScript to change from various versions (regular, shot, and dead).

Regular Lester

To do the tweening to make Lester appear to be running, I've broken the Lester graphic into several pieces including the head, body, left hand, right hand, left foot, and right foot . I've assembled these pieces into a movie clip called Lester. This clip, along with every other clip in the library, is exported for ActionScript.

The first 39 frames include Lester happily running. The first frame contains a frame label called begin . On frame 39, the last frame of the regular Lester animation, I've included one line of script that says gotoAndPlay(begin); . This, of course, causes the Lester graphic to loop through only the first 39 frames of its timeline. When the time comes that Lester gets shot, we'll add some ActionScript to skip past this to frame 40 of the timeline. Figure 7.7 shows regular Lester along with his timeline.

click to expand
Figure 7.7: Lester is tweened and appears to run from frame 1 to frame 39.
Lester Shot

Beginning at frame 40, the graphic of Lester changes. He has a different head ”one that shows his eyes and mouth changing to indicate that he's been shot. The fortieth frame contains a label of shot that will be used to change Lester's appearance when he takes a bullet.

The shot Lester continues until frame 78 where another line of script says gotoAndPlay(begin); . This causes the entire Lester graphic to loop back to the beginning of its animation, effectively making Lester appear happy again. Figure 7.8 shows the Lester shot graphic and its timeline.

click to expand
Figure 7.8: Lester appears hurt beginning at frame 40.This continues through frame 78 where he is reset to frame 1 again.
Lester Dead

The final frame in the Lester symbol, frame 79, contains Lester with his eyes Xed out and his legs and arms hanging limply off to the side. This frame is labeled dead . Lester looks a little funny now, but when he actually dies, he'll be rotated 90 degrees clockwise and appear to be lying dead on his side. Because Lester is dead, he has no motion; therefore, I didn't need to add tweening to this part of Lester's timeline. No script is associated with this part of the timeline, either; we will stop Lester's timeline from playing with ActionScript when he dies. Figure 7.9 shows Lester dead, with his timeline.

click to expand
Figure 7.9: The dead Lester needs no tweening, so it's only a single frame long.The only thing we need is a frame label of dead .
The Bullet Hole

When Lester gets shot, I want to add a bleeding bullet hole to him. The original Lester shot art from Rachel had a bleeding bullet hole in it already. I simply cut that piece out and placed it in its own symbol. I named this new symbol hole and then tweened a blood drop. Figure 7.10 shows the hole symbol with its tweened timeline.

click to expand
Figure 7.10: A bullet hole with a tweened drop of blood running out of it.

When Lester gets shot, we'll attach an instance of this hole symbol to him at the place he was shot. That will allow the player to see where he shot Lester as well as count the number of times Lester has been shot.

Tip  

You might have noticed that I placed some script on the Lester timeline. That's uncommon for me; as you know, I like to keep my scripts in one piece in frame 1 of the main timeline.There are occasionally reasons to add basic script to symbols, and this is just such a case. However, notice there are only two single-line scripts, and neither uses logic. In other words, if I have a conditional or loop I need to run, it wouldn't be placed on Lester's timeline. Instead, it would be placed in frame 1 of the main timeline.The occasional gotoAndPlay or stop function call is okay to put in a MovieClip timeline, though.

That's about all we need from the Lester symbol. Everything else will be taken care of during the ActionScript implementation.

Creating the Forest

Now that Lester is finished, we need somewhere for him to frolic while the user takes pot shots at him. You've already seen the forest art Rachel sent me in Figure 7.4, but now it's time to break the art apart.

The problem is that I want Lester to duck behind trees as he's running from the player. That means the forest is not a simple background image. Instead, I need to remove the trees and place them in their own symbols. Then when I set the stage, I can space out the depths of the trees and move Lester through them by changing his depth.

I've broken each tree into its own symbol and gathered them together. What was left of the forest after the removal of the trees is kept in the symbol named background.

There was also a pond in the original forest. I've broken this up into two pieces: pond and pondbottom. This will allow me to move Lester up through the pond, as if he is poking his head out from under the water. Figure 7.11 shows a picture of my library containing the forest clips.

click to expand
Figure 7.11: The library contains a folder that, in turn , contains each of the trees, the pond pieces, and the background.

Message Movie and Text Bubble

At the beginning of the game, Lester pops up and explains the rules of the game to the player using a text bubble. The message movie has a tween of Lester coming up and stopping after the text bubble animates in. Although we could have done this with code, I find it far simpler to use a bit of tweening. We do have to put one quick frame action in here to trigger a function the first time when the text field is present. That line is

 _parent.setBubble() 

Otherwise, there are two stop actions: the first when the text bubble is fully expanded, and the second should be added to the blank keyframe at the end of the timeline. When the instructions are complete, the animation will play to that blank keyframe and stop where it will sit unobtrusively throughout the actual gameplay.

On the bubble, we need a text field to display messages to the user and a button to allow the user to advance through the messages. Because the bubble is animating but also needs to hold a button and a text field, I've decided to make the bubble its own movie clip containing the text field and the button. I've given the bubble an instance name of bubble .

When Lester is fully up and the text bubble is fully expanded, it should look like Figure 7.12.

click to expand
Figure 7.12: Lester's text bubble is used to explain the rules of the game to the player.

Notice that I've placed a button inside the bubble. I'm planning to use that button to move through the instructions and finally begin the game. I brought this button in from one of the previous games that used it. I changed the colors a bit, but other than that, it's the same button we've been using for the past 300 pages.

Creating the Scoreboard

I want the scoreboard to tell the player how many times he's killed Lester. But I also want a time limit on this game. The object will be to kill Lester as many times as possible in the given amount of time. From these requirements, I've created the symbol in Figure 7.13.


Figure 7.13: The scoreboard has one line for the score (the number of times Lester has kicked the bucket) and a second line for the remaining time.

Because the score and time will be changing a great deal during play, I don't want to update the scoreboard data myself using mytext.text like I have sometimes done in the past. Instead, I want to tie the Var field of each text field to some variable that I will update in ActionScript.

I have tied the score field to _root.score and the time field to _root.time . When I implement the game, I'll use those two variables to store the user's score and time, and the scoreboard will automatically update itself.

Creating the Frame Counter

During testing, I like to put a frame counter on the stage. This is a device that counts the number of frames being displayed per second and displays it. It's important to get an idea of how fast the game is playing so that you can avoid things that bog it down too much. For that reason, during testing, I generally open up the frame rate of my movie to its maximum value of 120 frames per second. Then I place a frame counter on the stage. While the game is in development, anything I draw and move on the stage slows down the frame rate. The more the frame rate slows down, the more my game is chewing up the CPU. I'll often leave the frame counter up even in my finished games. It doesn't hurt anything, and some players like to see it.

Keep in mind though that many games contain animation. Animation is done assuming a specific frame rate. Although it is good to test the movie at high frame rates to see where the game is slowing , keep in mind that the game itself must be coded normally for a more standard frame rate between 15 and 30.

To make the frame counter, I create a new symbol. Inside it, I have a text field whose Var field I set to fps . Notice that I didn't use _root. in front of that variable name. That's because I don't want the fps variable to be a member of the main timeline. Instead, I want it to be a member of the frame counter symbol's timeline. By using no reference at all in the Var field, a locally scoped variable is assumed. Figure 7.14 shows the frame counter symbol, ready for ActionScript to be attached.


Figure 7.14: The frame counter symbol tells us how many frames are actually being displayed each frame.

The actual implementation of the frame counter is simple. In fact, it's so simple that I want to give it here rather than wait until the implementation section.

We need to do two things to implement the frame counter. First, we need to count each frame as it is executed. That's easily done using an onEnterFrame handler attached to the frame counter movie clip.

I attach the following script to frame 1 of the frame counter timeline:

 this.onEnterFrame = function(){++framecount;} 

As you can see, we're attaching the onEnterFrame handler using the this reference. Because this script is placed in the frame counter symbol, this will refer to the frame counter instance. Because the onEnterFrame handler is called once each frame, the preceding script will count the exact number of frames that have been executed.

The second part of the implementation requires us to display the number of frames that have been rendered in the last second. I could have added script to do this to the onEnterFrame handler that we've already implemented, but that would cause the frame counter to update every frame. That's much more often than we need to update it, so I'll save Flash the extra work by creating an interval that will update the frame counter only four times per second (every 250 milliseconds ). That can be done with the following script:

 setInterval(function(){fps = framecount*4;framecount = 0;}, 250); 

As you can see, we're using a call to setInterval . The first argument is an anonymous function that does two things. First, it sets the fps variable (this is the one we added to the Var field of the frame counter) to the current frame count times 4. The 4 multiplied in is required because our frame counter displays frames per second, and this interval is going to execute four times per second. The second part of our anonymous function resets the frame count to 0. The second argument to setInterval is the 250 milliseconds we want the interval to iterate at. If you are using this at a lower frame rate, just be aware that it will be increasingly inaccurate as you lower the frame rate.

That completes the frame counter implementation. All we would have to do to make it work is to either drag an instance onto the stage or dynamically add one with attachMovie . When we get to the implementation section, we'll use the attachMovie approach.

Tip  

I've broken my rule of thumb again. I've added script to a symbol in the library instead of the main timeline. However, like last time, there is no logic in this script. The frame counter symbol is completely self-contained. By placing the script inside the symbol, I can easily reuse it in future games. If I had attached the script externally (which I could have done from the main timeline), I would need to add that script to the main timeline of any movie that used my frame counter. By placing the script inside the symbol, there is nothing required to make the frame counter work other than attaching it to the stage.

You can test the frame counter by dragging it onto the stage and then testing your movie. Change the fps value in the Properties panel for your overall movie and then test to watch the frame counter display the correct (or nearly correct) value.

Creating the Crosshair

The player, when he moves his mouse around the stage, will be moving a crosshair instead of the mouse pointer. I've simply created a crosshair symbol in the library. When we get to implementation, we'll exchange the mouse pointer for an instance of this symbol and then set up an onMouseMovie handler for it, complete with our updateAfterEvent call (see the section "The Mouse Object" in Chapter 6, "Objects: Critter Attack ," for an explanation of updateAfterEvent ). Figure 7.15 shows my crosshair symbol.


Figure 7.15: The crosshair symbol will be used instead of the mouse pointer when the game is played .

Creating the Bullet

The shotgun the player uses to shoot Lester will have a finite amount of bullets. After firing the entire allotment, the player must click a Reload button to get more bullets. The number of bullets left in the gun will be represented by some little bullet pictures in the corner of the screen, next to the Reload button (which I haven't created yet). To do this, I'll need a small bullet symbol. I've created just such a symbol, named bullet, which looks like Figure 7.16.


Figure 7.16: The bullet symbol will be used to show the player how many more rounds he has before he must reload the shotgun.

The Design

Now that our library is fully stocked with our assets for the game, it's time to get busy implementing it. However, before we start typing ActionScript, we need to think about things a bit. There is a lot going on in this game, so we should have an idea of how our code will be organized before we start writing it. That saves us great pain later in the implementation.

The key to handling the design of a game that has many details is to break things down by parts . After some consideration and pondering, I've decided to take the following view of the game's overall execution flow.

There are two main parts to the game: the introduction and the action. During the introduction, Lester pops up and explains the rules to the player. The player does not have a crosshair or any ability to fire the shotgun at this time. All he can do is wait for Lester to explain things and then click the Start button in Lester's speech bubble to begin a new game.

After the user starts the game, Lester hides and begins to move from tree to tree, trying to avoid the player's shotgun slugs. During this time, the game behaves differently from the introduction because the player has his crosshair, is able to fire bullets at Lester, and so on.

To deal with this first level of abstraction, I've decided on the following scheme. I'm going to have a button that advances the intro through several states until the game is ready to play. After the game has started, I'll pass the controls over to the onEnterFrame handler because the action will be in need of faster changes than a menu does. I will create two functions ” gameOn and gameOff ”that will control the changes from introduction to action and vice versa.

From there, I have several functions to handle everything else. These assorted functions can be grouped into four groups:

  • High-level game control functions

  • Lester control functions

  • Gun control functions

  • Lester gets shot functions

We can break each of those into a set of functions. After implementing each function, we're finished. It sure seems easy when we're looking at things from the abstract level, doesn't it?

High-Level Game Control

I've already alluded to how this section will be broken up. But now it's time to spell things out explicitly. Like the game from Chapter 6, I want to encapsulate all the game initialization code into one function that will be called once ”when the game first loads. The following is the first script in Shoot 'Em Up , and it occurs at the top of frame 1 of the main timeline:

 initGame(); function initGame(){ } 

As usual, we'll be adding code to the initGame function body as we need it.

Now we need our two functions to handle running the introduction and the game. The following script gets us started with that:

 function setBubble(){ } function playGame(){ } 

Both of these functions will be filled out later. However, as I said before, the function that is controlling the intro will be handled by a series of onRelease events, whereas the action function will be attached to the main movie's onEnterFrame handler so that it is called once per frame. During the time that the intro is playing, setBubble will be used. When the game begins, playGame will take over.

I want to use several other functions to divide up the implementation and organize the functionality. First, we're going to need the functions I mentioned earlier to handle the actual changing from introduction to game and vice versa. The following function stubs get us ready for that:

 function gameOn(){ } function gameOff(){ } 

I would also like to encapsulate the turning on and off of the crosshair with the following stubs:

 function crosshairOn(){ } function crosshairOff(){ } 

Now I need to be able to reset the scoreboard when a new game is started:

 function resetScoreboard(){ } 
Note  

A stub is a function definition that has zero or minimal code in it. It serves as a reminder of functionality that needs to be developed in the future.

Finally, I need a function that can be used to handle the scoreboard's timer. Remember that there is a time limit in this game and the scoreboard displays the number of seconds left before the end of the game. To update the scoreboard correctly, I'll want to create an interval that reduces the time by 1 second. This interval should execute once per second, of course. I don't want to implement the interval yet, so I'm just going to give the function stub as follows :

 function clockTick(){ } 

And that completes the stubs for the high-level game control. By laying out all my functions before I implement any of them, I'm getting a better idea of how everything is going to interconnect.

Notice that of all the stubs I have given, I only issued a call to the first one: initGame . That's because the rest of the functionality is built on that one call. That single call to initGame is going to connect the onEnterFrame handler on the main timeline that actually runs the introduction and game, as well as set anything else into motion that needs it. Function calls to the other stubs will come after we're deeper into the implementation.

Lester Control

In a similar way to the high-level game control, control of Lester is organized into a series of functions. I'd like to give you the stubs for those functions now, before implementation is given. That way, the entire framework of the game will be presented before any of the detailed implementation.

Before I show you the Lester control stubs, I want to make one note. The playIntro function handles the control of Lester during the introduction. The functions you're about to see don't handle the introduction control. Instead, these functions only control Lester during the actual gameplay.

The first thing we need is a function to move Lester. This is accomplished with the following stub:

 function moveLester(){ } 

We also need a support function to create a new route for Lester to move. Remember that Lester will be ducking behind trees as the game plays; therefore, each route between trees that he takes needs to be spelled out for him. I'll be taking care of that part of the implementation in the following function:

 function getNewRoute(){ } 

That concludes the functions used to operate Lester during the game.

Gun Control

The user has control of his trusty shotgun, but there are some constraints on it. The first constraint is that after the player fires, he's forced to wait a moment before the gun can be fired again. That is handled by a pair of functions used to turn the functionality for the gun on and off, much in the same way that the game and crosshair were enabled and disabled in the previous section. The following stubs get us started:

 function gunOn(){ } function gunOff(){ } 

We'll also want to encapsulate the actual firing of the gun with the following stub:

 function fireGun(){ } 

Finally, we need to be able to reload the gun when the user clicks the Reload button. The following stub sets that up:

 function reloadGun(paused){ } 

Notice, though, that reloadGun takes one argument. That argument is used to control whether the gun is automatically re-enabled after the reloading. During gameplay, this argument will be false so that the gun auto-enables after a short delay; however, during the intro, the gun is loaded but not yet enabled. The call is made with true as the argument so that the gun stays disabled through the entire intro.

And that completes the stubs for the gun control. Like the rest of this early implementation, I'm only giving function names here. The actual implementation of these functions will come after everything is flushed out.

Lester Gets Shot

The final piece of control is required to handle Lester getting shot by the player. I'm defining two main functions for this part. The first handles the player putting a single slug into Lester's yellow butt:

 function shootLester(){ } 

Inside that function, we'll test to see if Lester has been shot enough times to die. If he has, we'll call the second function in this section, listed next:

 function killLester(){ } 

When Lester actually dies, we'll want to remove the bullet holes from his body. There is only going to be one live Lester. When the player "kills" him, we are just going to remove the holes from his body and spawn a corpse. For organization's sake, I'm encapsulating this into the following function:

 function removeHoles(){ } 

And that's all there is to it. By looking over these function stubs, you should be able to get an idea of the general organization of the game. From here, all that is left is to implement each of the functions I've given stubs for. For clarity, the following list presents each of the stubs we've created so far:

  • initGame

  • playIntro

  • playGame

  • gameOn

  • gameOff

  • crosshairOn

  • crosshairOff

  • resetScoreboard

  • clockTick

  • moveLester

  • getNewRoute

  • gunOn

  • gunOff

  • fireGun

  • reloadGun

  • shootLester

  • killLester

  • removeHoles

Now that we know where we're going, let's start the lower-level implementation.

Implementation

With all the stubs in place, let's go ahead and start filling in the function. We are going to start with the game initialization and continue setting up each of the controls.

The Game Control Implementation

We've seen the list of functions that compose this portion of the game's code. It's time to fill each of them in. Remember that the game will begin using playIntro as the onEnterFrame handler for the main timeline. Before we define that function, though, we need to set up the stage.

The initGame Function

Just like Chapter 6's game, Critter Attack , this new game is going to use an initGame function to set things up. The call to this function was already given in the preceding section, so all we need is its implementation. As usual, the first thing I put in my game initialization code is my depth settings:

 function initGame(){     background_depth=1;     pond_depth = 2;     lester_depth=10;     dupe_depth = 11;     tree3_depth=20;     middleSwapClip_depth = 25;     tree2_depth=30;     tree1_depth=40;     topSwapClip_depth = 50;     soundManager_depth = 100;     deadLester_depth = 200;     pondbottom_depth = 10000;     message_depth = 10001     framecounter_depth = 10002;     scoreboard_depth = 10003;     reload_depth = 10004;     bullet_depth=55555;     crosshair_depth = 99999; } 

Most of these depths are self explanatory, so I'm only going to mention a few. The pond depth is 2, but the pond bottom depth is 10000. That's because I want Lester to come up through the water sometimes. By setting the two halves of the pond at extremes, Lester can be at any depth between them and still appear to be coming out of the water.

There are also two depths called middleSwapClipDepth and topSwapClipDepth that will be used to help us move Lester between the tree levels so that he can appear in front of or behind any tree we want him to.

In actuality, I didn't just sit down and come up with every required depth off the top of my head. Instead, as I developed the initGame function, I added depths as I needed them. But rather than have to say, "Go back to the top of initGame and add this depth" repeatedly, I've decided to dump all the depth settings at once. I believe this is a better way to explain the code, even though it does not follow as closely to the way I developed the game in the first place.

Before we start placing objects on the stage, I want to define a few variables that we'll be using later. First, the number of times Lester has been killed is stored on the scoreboard as _root.score , so we'll need to initialize this to keep the scoreboard from being blank. ( Undefined is displayed as an empty string when it's in a text field.)

 score=0; 

We also need to initialize the time left in the current game so that it displays 0 until the player begins a new game:

 time=0; 

The total amount of time we're going to give the player during the game will be defined with its own variable so that we can reset it each time the player starts a new game. The following line declares this time variable:

 timeAllotment = 100; 

This total game time variable, timeAllotment , is set to 100. This indicates the number of seconds each game will go before ending. Because Lester has no way of defending himself or killing the player, the game will only end when time reaches 0.

The number of bullets that the player has before he must click the Reload button is given in the following line:

 bulletCapacity = 5; 

Finally, we need to define the number of times Lester can be shot before he dies. We do so on the following line:

 lesterShotTolerance = 5; 

Now it's time to create the stage objects one by one. Most of them are pretty straight-forward. First, we create the background:

 attachMovie("background"," background",background_depth); 

Then we create the frame counter:

 attachMovie("frame counter", "frameCounter", framecounter_depth); frameCounter._x= 20;frameCounter._y= 30; frameCounter._xscale = 20;frameCounter._yscale = 20; 

From there, we create the scoreboard:

 attachMovie("scoreboard", "scoreboard", scoreboard_depth); scoreboard._x= 65;scoreboard._y= 475; scoreboard._xscale = 30;scoreboard._yscale = 30; 
Note  

I've found various opinions about the value of creating everything using ActionScript. It would certainly be reasonable to set up the stage with several of our movie clip instances at author time and leave the attachMovie calls for only the necessary situations. This has the advantage of allowing us to easily position the stage elements by hand instead of changing our literal values in ActionScript several times to get the perfect arrangement of clips on the stage.

On the other hand, by doing everything with ActionScript, we don't have to worry about all the depths of our author time clips being lower than the dynamically attached clips. This can be fixed by using swapDeapths on the author time clips to move them into the range of dynamically created clips, but it does require script to handle. Further, by creating everything with ActionScript, a programmer can see exactly what the game will do without looking at the stage. If you mix author time clips with ActionScript, you have to click all over the stage to find out the names of instances and things like that. In my opinion, doing everything with script is cleaner and easier to modify.

We need to attach the trees as well:

 attachMovie("tree1"," tree1",tree1_depth); tree1._x=115;tree1._y=100; attachMovie("tree2"," tree2",tree2_depth); tree2._x=492;tree2._y=270; attachMovie("tree3"," tree3",tree3_depth); tree3._x=345;tree3._y=296; 

We also need to attach both parts of the pond:

 attachMovie("pond"," pond",pond_depth); pond._x=342;pond._y=445; attachMovie("pondbottom"," pondbottom",pondbottom_depth); pondbottom._x=342;pondbottom._y=445; 

When Lester comes up to speak, his words appear in a text bubble. Because this will always happen at the same place, I can attach just a movie of him appearing, speaking, and disappearing . When I want Lester to speak, I just tell this movie clip to play, and Lester will appear with a text bubble.

 attachMovie("message"," message", message_depth); messageState = 0 

Notice that I'm setting a variable called messageState . This will be used to keep track of what needs to be said next.

Now it's time to create Lester:

 attachMovie("lester"," lester",lester_depth, {holecount:0}); lester._x = 340;lester._y = 285; lester._xscale = 18;lester._yscale=18; lester._visible = false; 

That completes the creation of everything we need right now. There are some other objects, such the Reload button, that I'll be adding later, but I don't want them up yet. I'll create the Reload button during the intro when Lester explains its use.

You can test the movie to see the stage all set up with Lester popping up to say something. Currently though, it will just be the default text, which should look like Figure 7.17.

click to expand
Figure 7.17: The stage has been set; now we're ready to create Lester's introduction.
The setBubble Function

setBubble is going to be called the fist time as a frame action in the message movie clip. The reason for this is that the bubble doesn't exist in frame 1. We need to call the action after the text bubble appears.

The bubble itself is generic. It has a text field and a button with a text field in it. As the user clicks on the button, we want different things to happen. For that reason, I'm going to implement using a simple state machine. Because each state follows the previous one with no backtracking or skipping ahead, I have a general machine like in Figure 7.18.

click to expand
Figure 7.18: A generalization of the state machine used in Lester's introduction.

To implement a state machine like this, a switch statement is generally the cleanest solution. In fact, the entire setBubble function is going to be one big switch statement. The first thing we need to do is open that switch and choose a variable to use to keep track of the current state:

 switch(messageState){ } 

I have chosen to use the variable name messageState to keep track of the state of the introduction. Now, each message that Lester tells the user will be in the form of a case statement. We've initialized the messageState to 0 so that Flash MX 2004 will allow us to use incrementation later. Let's start by creating the first state with the following case label:

 case 0: 

Inside this state, I need to do a few things. First, I want to set the functionality of the button in the bubble movie clip inside our message movie clip. This button needs to add 1 to the messageState and then recall setBubble . This means that the button will always advance us to the next state in the switch.

 message.bubble.button.onRelease = function(){      messageState++;      setBubble(); } 

Next, we need to set the text for the button and for the content of the bubble. After that, we need break so that Flash doesn't run the next case as well.

 message.bubble.button.mytext.text=" Next"; message.bubble.mytext.text = "Welcome! Shoot me "+ lesterShotTolerance +    " times to kill me."; break; 

When the user clicks the button, messageState will now equal 1 and the function will be called again. Our button functionality and text can remain the same, so we just need to change the message in the bubble and break .

 case 1:      message.bubble.mytext.text=" You have one minute so shoot fast!"      break; 

This next state is the state where we're explaining to the player about the Reload button. It makes sense to create the Reload button now, and we do so by attaching it, moving it, scaling it, and calling stop on it. Then we'll call the reloadGun method, which we'll implement later. We'll also set the text on the button and add our next text to the text bubble. Finally, we need to reset the scoreboard.

 case 2:     message.bubble.mytext.text=" Here is your gun. Reload when you run    out of bullets." reload.mytext.text=" Reload";     reload._x=630;     reload._y=480;     reload._xscale = 40;     reload._yscale = 40;     reload.stop();     reloadGun(true);     resetScoreboard();     break; 

Case 3 is the last message we will give to the user before the game starts. In addition to changing the text in the bubble, let's also change the button text so that the player knows the game is ready to start.

 case 3:     message.bubble.mytext.text=" Ready... Set..."      ;     message.bubble.button.mytext.text=" Go!";     break; 

When the player clicks on the Go! button, the button is still going to increment messageState and call setBubble again. This time though, we aren't going to display a message to the user; instead, we'll clear the screen and start the game. To clear the screen, we just need to tell the message movie clip to play so that it can go through its "outro" animation of Lester ducking back down and the text bubble disappearing. To start the game, we just need to call gameOn .

Because the user is not going to click the button again, we need to manually increment the messageState variable for our last case.

 case 4:     message.play();     gameOn();     messageState++;     break; 

Our last case is a bit special. In case 4, we just cleared the message, so it might seem like there is nothing left to do. For the intro, that is true, but when the game is over, Lester needs to come back to taunt you into playing the game again. To get Lester back, we will be telling the message movie clip to play again when the game is over. Because the messageState variable is still set at 5, it will continue where it left off, bypassing the earlier states.

The user has already played the game, so there is no need to go through the full intro. This time we just need to have the gun reload, the scoreboard reset, the game turn on, and the message movie clip play the outro again. Although we could certainly rewrite the code, we might as well reuse what we have. When the user clicks the button, let's manually reset the messageState to 2 where the gun and scoreboard are set up, and then set messageState again, but this time to 4 where the game is started and message plays through the rest of its animation. At this point, we have a new game in progress.

The introduction is now finished. You can test it and watch Lester give you instructions, as in Figure 7.19.

click to expand
Figure 7.19: Lester's introduction is now complete and you can watch him give you instructions.
The gameOn Function

We concluded the implementation of the introduction by calling the gameOn function, so it makes sense to implement this next.

There are several things we need to do in this function. The first is to turn on the gun and crosshairs:

 gunOn(); crosshairOn(); 

We also need to set the clock into motion. We do this by creating an interval that makes a call to the clockTick function once every 1000 milliseconds (once per second):

 timeInterval = setInterval(clockTick,1000); 

Our _root.onEnterFrame handler is currently the playIntro function, and that needs to change, giving us the following:

 onEnterFrame = playGame; 

The game will have a state variable, much like introstate . To maintain consistency, I've named it gameState and initialized it to 0. The actual implementation of this will come in the playGame function, explained in the next section:

 gameState=0; 

Because the game is starting, we need to make Lester visible:

 lester._visible = true 

Now I'm going to initialize two variables that will be used in moving Lester: route and routeFlip . Their purpose is to handle maintaining Lester's route through the trees. Lester will be given a route from behind one tree to another. He'll move between these two trees, and when he finishes, he'll be given another route randomly from his list of routes. This will all be explained and implemented in the Lester control functions. For now, we can simply initialize these two variables and leave their exact explanation until later:

 lester.route=0; lester.routeFlip=0; 

I've been saving the next piece of script since the end of the introduction. We created the Reload button, but we didn't attach an onRelease handler. I do it here because I don't want the button enabled until this point in the game, when the action begins. The functionality we want for the Reload button being clicked is a call to the reloadGun function:

 reload.onRelease = reloadGun; 

That's everything we need to begin the game. There is one additional thing I would like to do here to save us from having to do it later. When the time runs out, Lester will appear at the front of the stage and laugh at the player. His speech bubble will reappear and the button inside it will be labeled Restart. When the player clicks the Restart button, the game should start again. I would like to set all this up now so that I don't have to do it when the game ends. The only thing I'm not doing here is making the text bubble visible. I'll do that when the time actually runs out:

 textbubble.button.mytext.text = "restart"; textbubble.button.onRelease = function(){     removeHoles();     introstate = 13;     onEnterFrame = playIntro; } 

Notice the function that I'm attaching to the button. It calls the removeHoles function, sets the introstate to 13, and changes the _root.onEnterFrame to the playIntro function. That will cause Lester to go back into his introduction sequence, toward the end of it. State 13 is right after the instructions and also when the scoreboard is reset. This essentially completes the game loop from introduction to game and back again.

The playGame Function

Inside the gameOn function, we changed the main timeline's onEnterFrame handler from playIntro to playGame . Now it's time to implement the playGame function. There isn't much to it, so this section will be brief.

The one thing we do need to talk about is the gameState variable that we defined in initGame and initialized to 0. This variable's job is to keep track of whether Lester is currently moving between trees or needs a new route to be chosen. When gameState is 0, Lester needs a new route, so we'll call getNewRoute for him, and after doing so, we'll change the gameState to 1. A gameState of 1 indicates that Lester is currently executing some route and does not need a new one yet. For each frame, as we move Lester through his current route, we'll check to see if the route is finished. When it is, we'll set the gameState back to 0 so that Lester will choose a new route.

The playGame function's only purpose is to check the game state and either send Lester on his current route or choose a new route for him.

Therefore, even though gameState only has two valid values (0 and 1), it can be seen as a kind of simple state machine. For that reason, I'm going to implement it with a switch statement:

 switch(gameState){     case 0://lester chooses his route         getNewRoute();         gameState = 1;         break;     case 1://lester is moving         gameState = moveLester();         break; } 

As you can see, we have a case for each state. The case 0 has a call to getNewRoute and then sets the gameState to 1. The case 1 calls moveLester and assigns its return value to gameState .

What's going on with that? Well, I created moveLester to return a value. Because the moveLester function is responsible for moving Lester along a given route, it's reasonable that it has a return value to indicate whether the current route is complete.

If this call to moveLester causes Lester to complete his current route, moveLester returns 0. The 0 is then assigned to the gameState variable which, when plugged back into the switch, causes the 0 case to occur. The 0 case has a call to getNewRoute .

If, on the other hand, moveLester does not complete Lester's route in this frame, it returns 1, which is again assigned to gameState . In that case though, moveLester will be called again in the next turn, furthering him on his route.

Note  

I considered doing away with the playGame function altogether and simply attaching moveLester to the onEnterFrame handler. Then, inside moveLester , I could have checked for a completed route and called getNewRoute from there. That would have probably been a better choice in some ways. I chose to break it up into two functions ” playGame and moveLester ”to make a logical break between the functions that must be executed every frame and the function to move Lester. It's just a coincidence that the only thing we have to do each frame is call the moveLester function or get a new route if we need one.

The gameOff Function

When time runs out, a call will be made to gameOff . With that in mind, we have several things to do. We want Lester to move up front and talk to the player again. He'll laugh at them, and the button inside his speech bubble will be used to start a new game. Therefore, the first thing we should do is turn off the things we turned on in gameOn . Let's start with the gun and the crosshair:

 gunOff(); crosshairOff(); 

Now we want to remove the holes from Lester and make him invisible so that we can make the message movie clip play and not have two versions of him on the stage:

 removeHoles(); lester._visible=false; message.play() 

Next, we need to clean up all the corpses littering the ground. We haven't gotten to the part when Lester is killed yet, but when we do, we're going to duplicate his movie clip so that as you kill him, he leaves corpses around the stage. When the game is over after the player runs out of time, the dead bodies need to be removed from the stage. The for loop accomplishes that:

 for(i=1;i<=score;++i)     _root["dupe"+deadLester_depth+i].removeMovieClip(); 

That's all there is to gameOff . After gameOff is called, case 5 of the setBubble function takes over to restart the game.

The crosshairOn Function

The implementation of this function amounts to swapping the default mouse pointer for our crosshair movie clip. We begin by hiding the default mouse pointer:

 Mouse.hide(); 

Now we attach our own pointer:

 attachMovie("crosshair"," crosshair",crosshair_depth); 

Finally, we attach an onMouseMove handler for the new crosshair instance:

 crosshair.onMouseMove=function(){      crosshair._x=_xmouse;      crosshair._y=_ymouse;      updateAfterEvent(); } 

Inside this handler, we update the position of the crosshair clip to match the mouse and then call updateAfterEvent , which forces a redraw to avoid a lagging mouse pointer.

The crosshairOff Function

Now it's time to remove the crosshair that we just attached in the previous section. The process is simple enough; we just reveal the default mouse with Mouse.show and then remove our crosshair clip. Removing the crosshair clip also removes any event handlers attached to it:

 Mouse.show(); crosshair.removeMovieClip(); 
The resetScoreboard Function

This is another simple function that requires only two lines of script. First we'll need to reduce the score back to 0, and then we'll need to reset the time to its initial value:

 score=0; time = timeAllotment; 
The clockTick Function

This function is called once every 1000 milliseconds after being set up as an interval in the gameOn function. The function's implementation is brief. First we decrement time and test to see if it has run out. If the time has indeed expired , we call gameOff :

 if(time == 0){     gameOff(); } 

Lester Control Implementation

We're now ready to deal with the frame-to-frame control of Lester. As you know, we're using the gameState variable to keep track of what Lester is doing. The first time the playGame function is run, Lester's gameState is 0, indicating he needs a new route. Let's take a look at getNewRoute first and then follow that with moveLester , which actually does the moving of each frame.

The getNewRoute Function

The first thing I need to do is pick a random route number. My implementation has 11 routes that Lester can choose from, and they will be explained next. For now, we simply need to pick one number randomly from the list of available routes:

 lester.route = getRandom(0,11); 

Now we can go into a switch statement using that number we just generated. Each case will be a separate route that Lester will be given:

 switch(lester.route){     //           x0,y0,scale0,x10,y20,scale1,stepcount     case 0: routeDesc = [340,275,20,504,321,28,40];break;     case 1: routeDesc = [504,321,28,340,275,20,40];break;     case 2: routeDesc = [504,321,28,715,278,22,35];break;     case 3: routeDesc = [715,278,22,504,321,28,35];break;     case 4: routeDesc = [340,295,18,705,295,24,75];break;     case 5: routeDesc = [705,295,24,340,295,18,75];break;     case 6: routeDesc = [340,285,20,200,300,21.5,40];break;     case 7: routeDesc = [200,300,21.5,340,285,20,40];break;     case 8: routeDesc = [185,300,21.5,-20,380,26,40];break;     case 9: routeDesc = [-20,380,26,185,300,21.5,40];break;     case 10: routeDesc = [495,350,28,495,310,28,10];break;     case 11: routeDesc = [345,540,48,345,480,48,10];break; } 

This looks confusing, so let me explain. Depending on which case is executed, an array is created holding seven values. The first two values are the x and y coordinates that Lester will begin the route at. The third number is Lester's scale when he begins his route. The next three numbers in the array ”4, 5, and 6 ”are the x, y, and scale values at the end of Lester's route. The final number indicates the number of steps the entire route should be broken up into.

We've created a random number and then used a switch to associate an array containing the route data with that random route number. Now we need to move Lester to the starting point of his new route and scale him properly:

 lester._x = routeDesc[0]; lester._y = routeDesc[1]; lester._xscale = lester._yscale = routeDesc[2]; 

When we move Lester, we'll use routeDesc[3] , routeDesc [4] , and routeDesc [5] to make changes to him over the course of his route. But rather than leave these values inside the routeDesc array, it would be better to attach them to Lester for consistency. I create a set of variables on Lester called rx , ry , and rs , which indicate the x, y and scale properties of the end of his route:

 lester.rx = routeDesc[3]; lester.ry = routeDesc[4]; lester.rs = routeDesc[5]; 

Finally, when we move Lester, we're going to reduce the step count by 1. The step count is the number of frames it's going to take Lester to move across his entire route. The initial value of the step count for a given route is indicated by the last number in the routeDesc array. I attach this value to Lester like I did with the last three variables. However, this time I want to make some modifications on the way in based on the player's current score:

 lester.stepcount = Math.ceil(routeDesc[6]/3 + routeDesc[6]/(score+1)); 

This reduces the number of steps Lester takes on his route as the player's score gets higher. In other words, the more times the player kills Lester, the faster Lester will move from tree to tree. The first part of the line creates an offset of one-third of the default steps, which guarantees a minimum number of steps for each route no matter how high the score. The rest of the steps depend on the default number of steps divided by the score. Notice that we are adding 1 to the score; this is to ensure that we never divide by 0, giving us NaN . Last, the number has to be made into an integer, so we round up.

That gives us most of what we need in our route, but there is one problem. Some of the routes, numbers 10 and 11 in particular, need to be executed in reverse after they are finished. In other words, when Lester pokes his head out of the water, I want him to move it back down into the water before picking a new route. This requires some modifications to our current getNewRoute function.

I've decided to handle this issue in the following way. Any route that Lester picks up that requires being iterated backward afterward will have a special flag marked called routeFlip . When a new route is asked for and the routeFlip flag is on, the old route's number will be used with 100 added to it. Then each route that needs to be done backward will have a corresponding route numbered 100 more than the original. This sounds complex, but when you see it in code, it should be clear:

  if(lester.routeFlip){   lester.routeFlip = false;   lester.route+=100;   }   else   lester.route = getRandom(0,11);  switch(lester.route){     //           x0,y0,scale0,x10,y20,scale1,stepcount     case 0: routeDesc = [340,275,20,504,321,28,40];break;     case 1: routeDesc = [504,321,28,340,275,20,40];break;     case 2: routeDesc = [504,321,28,715,278,22,35];break;     case 3: routeDesc = [715,278,22,504,321,28,35];break;     case 4: routeDesc = [340,295,18,705,295,24,75];break;     case 5: routeDesc = [705,295,24,340,295,18,75];break;     case 6: routeDesc = [340,285,20,200,300,21.5,40];break;     case 7: routeDesc = [200,300,21.5,340,285,20,40];break;     case 8: routeDesc = [185,300,21.5,-20,380,26,40];break;     case 9: routeDesc = [-20,380,26,185,300,21.5,40];break;     case 10: routeDesc = [495,350,28,495,310,28,10];  lester.routeFlip = true;  break;     case 11: routeDesc = [345,540,48,345,480,48,10];  lester.routeFlip = true;  break;  case 110: routeDesc = [495,310,28,495,350,28,10];break;   case 111: routeDesc = [345,480,48,345,540,48,10];break;  } 

As you can see, we've added an if else block at the beginning to handle the need for a route flip. We've also added lester.routeFlip = true; to each route that needed it (10 and 11). Finally, we've added cases for each flipped route that is 100 more than the original (110 and 111).

Tip  

When creating the routes, I would change the random number call to force one particular route (the one I was working on).That way, I could play the game and tweak each route one at a time.

That completes the new route creation. All that's left is to give Lester a movement function.

The moveLester Function

Because the route has already been created for us, the implementation of the moveLester function should be pretty easy.

First we move Lester close to the destination point. We do this by taking the difference of the destination position and Lester's current position and dividing the result by the number of steps left to be made. The same thing is done with Lester's scale. Then we reduce the step count by 1:

 lester._x+= (lester.rx - lester._x) / lester.stepcount; lester._y+= (lester.ry - lester._y) / lester.stepcount; lester._yscale = lester._xscale+=(lester.rs-lester._xscale)/lester.stepcount; lester._yscale=lester._xscale+((lester.rs-lester._xscale)/lester.stepcount; lester.stepcount; 
Note  

The second return statement doesn't need to be put in an else statement because the if statement is already returning. That means that the only way you can get to the second return is if the condition was not satisfied.

Finally, we need to return 0 if Lester has finished his route (the step count will be 0), or return 1 if the route is unfinished :

 if(lester.stepcount==0) return 0; return 1; 

That completes Lester's movement. If you test your movie now, you should see Lester running about between the trees, although you won't be able to shoot at him yet.

Gun Control Implementation

Now that we have Lester running about properly, it's time to implement the gun. We need to handle the gunOn and gunOff functions that we've already called.

Before we rush into these functions' implementation, I want to discuss the general way these functions are going to operate. When the gun is enabled, an onMouseUp handler will be added to the main timeline that will make a call to fireGun to fire the gun. When the gun is fired, this onMouseUp handler will be removed, effectively disabling the gun. At that time, a timer will be created that re-enables the gun after a given delay. That way, the player can't blow off 10 rounds in 1 second and instantly drop Lester where he stands.

The gunOn Function

This function turns the gun on when the game starts. We need to attach the onMouseUp handler to the main timeline:

 onMouseUp = fireGun; 

Now we need to clear out the interval that is being used to re-enable the gun after firing:

 clearInterval(gunInterval); 

This gun interval will become clear after the implementation of the fireGun function.

The gunOff Function

All we need to do to disable the gun is remove the onMouseUp handler:

 onMouseUp = null; 
The fireGun Function

When the gun is enabled and the player releases the mouse button, the fireGun function is called. The first thing fireGun needs to do is check to see if the gun has bullets left:

 if(bullets){ } 

If the gun has bullets ( bullets > 0 ), we need to remove one of the bullets from the lower-right corner where the number of bullets left in the gun is displayed. (This will be set up in the section on reloadGun , given next.)

 _root["bullet"+(bulletCapacity+1-bullets)].removeMovieClip(); 

To remove the correct bullet, we need to take the total potential of bullets and subtract the number of bullets that remain. We then need to add 1 to match the naming system of the bullets.

Now we need to turn off the player's ability to access the gun so that the player can't shoot again:

 gunOff(); 

We also need to set the interval that will turn the gun controls back on again after a given time delay:

 gunInterval = setInterval(gunOn,750); 

Now we need to record the loss of one bullet from our total number of bullets:

 bullets; 

Finally, we need to hit test Lester. Several things on the stage might stop the bullet, though. First, we need to make sure that we aren't shooting a tree or the pond. If we've avoided all those targets but have also hit Lester, then we trigger the shootLester function. To perform all these checks, we are going to use several logical ANDs ( && ). The following code implements this:

 if(!tree1.hitTest(_xmouse,_ymouse, true)     && !tree2.hitTest(_xmouse,_ymouse, true)     && !tree3.hitTest(_xmouse,_ymouse, true)     && !pondbottom.hitTest(_xmouse,_ymouse, true)     && lester.hitTest(_xmouse,_ymouse, true)){         shootLester();     } } 
Note  

Had we been using startDrag to move the crosshair, we also could have used _dropTarget to determine if we had correctly hit Lester. When you access the _dropTarget of the crosshair on click, you get the highest movie clip that the crosshair's registration mark intersects. Because the trees have a higher depth, they will be the target when Lester is behind a tree. All you have to do is check the string that is returned against Lester's name.

As you can see, I'm using a big set of logical ANDs to string together all the hit tests. At the bottom of it, when Lester has been hit but nothing else, I'm making the call to shotLester .

The reloadGun Function

We've already set up the call to reloadGun . It was given as the onRelease handler for the Reload button. Now it's time to implement this function. First we turn off the gun because reloading should take a moment:

 gunOff(); 

Remember that the reloadGun function takes an argument, a boolean, called paused . This argument tells the reload function whether it should attach an interval to re-enable the gun after a delay:

 if(!paused)     gunInterval = setInterval(gunOn,750); 

Now we can set the number of bullets up to its maximum:

 bullets= bulletCapacity; 

Finally, we need to create some bullet movie clips in the lower-right corner to let the player know how many bullets are left before he must reload:

 for(i=1;i<=bulletCapacity;++i){     var tempbullet = attachMovie("bullet"," bullet"+i, bullet_depth+i);     tempbullet._x = 520 + i*10;     tempbullet._y = 490;     tempbullet._xscale = tempbullet._yscale = 20; } 
Note  

If you reload the gun while bullets are in it, the loop will destroy any old bullets when it attaches the new bullets at the same depth as the old ones.

Lester Shot Implementation

Everything is up and running with the exception of two small functions. First we need to handle the situation when Lester is shot, and then we need to handle the situation when Lester dies.

The shootLester Function

We've already made the call to shootLester , inside the fireGun function, and now it's time to implement it. First we want to make Lester change his timeline so that he appears to be in pain:

 lester.gotoAndPlay("shot"); 

Next we need to attach a new bullet hole to Lester to show the player where he's been hit:

 lester.attachMovie("hole"," hole"+(++lester.holecount),lester.holecount); lester["hole"+lester.holecount]._x = lester._xmouse; lester["hole"+lester.holecount]._y = lester._ymouse; 

Finally, we need to test to see if Lester's been shot enough times to die, and if so, we call killLester :

 if(lester.holecount == lesterShotTolerance)     killLester(); 
The killLester Function

There isn't much to do when Lester dies. The first thing we need to do is increment the score:

 score++; 

Now we can duplicate the Lester movie clip so that we can leave a Lester corpse lying on the ground. We'll rotate this clip and move its playhead to the dead frame:

 var deadLester = lester.duplicateMovieClip("dupe"+deadLester_depth+score,    deadLester_depth+score); deadLester._rotation=90; deadLester._y+= 50; deadLester.gotoAndStop("dead"); 

Because we have only one live Lester, we need to remove the holes. Before we do, though, let's move them over to the corpse by including a for loop to go through the bullet holes. We know there must be the same number of bullet holes on Lester as the number of hits it takes to bring him down. Therefore, we can use the lesterShotTolerance variable. After that, it is a matter of setting up the hole and the target hole and then transferring the _x and _y properties. We do need to counteract the 90-degree turn by rotating the new hole by negative 90 degrees. Here is the code for this transfer:

 for(i=1;i<lesterShotTolerance+1;++i){           var oldHole= lester["hole" + i]           var newHole = deadLester.attachMovie("hole", "hole" + i, i);           newHole._x = oldHole._x           newHole._y = oldHole._y           newHole._rotation = -90      } 

Now we can remove the bullet holes from Lester:

 removeHoles(); 

Now we need to make the new Lester look happy again:

 lester.gotoAndPlay("begin"); 

Finally, we need to give Lester a new route so that he'll appear somewhere else on the stage:

 getNewRoute(); 

That completes the implementation. Try testing your game now, and you should see Lester running around the stage being pierced by your shotgun slugs, as in Figure 7.20.

click to expand
Figure 7.20: The entire game is implemented, with the exception of the sound effects.

Sound

We've implemented the entire game, but it's eerily quiet. That's because we haven't added sound effects yet. I generally don't do that until late in the development, and this game is no exception. The first thing to do is create a list of events that need sound effects.

For this game, I've created a set of events that will need a unique sound effect. This conflicts with the way I organize my sounds because sound effect files are generally named for the sound that they contain. In other words, if I wanted people to laugh when Lester was killed, my event would be Lester dies and the sound effect might be PeopleLaughing.wav. To deal with this double-naming issue, I've created Table 7.1 to list each event along with its associated sound effect.

 
Table 7.1: Shoot 'Em Up Sound Events

Sound Event

Sound Name

Ambient forest sounds

forest.wav

Fire gun

fireGun.wav

Fire empty gun

fireEmptyGun.wav

Reload gun

reloadGun.wav

Lester shot

shootLester.wav

Lester dead

peopleLaugh.wav

Game over

lesterLaugh.wav

Now that we have an idea of which events will require sounds and which sounds will be used to handle each event, it's time to implement the sound manager.

Creating the Sound Manager

You might recall the way I created the sound manager in Chapter 6's game, Critter Attack . I'm going to do almost the same thing this time. Inside the initGame function, we have a call to createSoundManager , which will set up everything we need. Let's go ahead and begin that implementation:

 function createSoundManager(){     var sound_depth = 1;     createEmptyMovieClip("soundManager",soundManager_depth);     soundManager.createEmptyMovieClip("reloadGun", sound_depth++);     soundManager.reloadGun.mySound = new Sound(soundManager.reloadGun);     soundManager.reloadGun.mySound.attachSound("reloadGun.wav");     soundManager.reloadGun.mySound.setVolume(50);     soundManager.createEmptyMovieClip("fireEmptyGun", sound_depth++);     soundManager.fireEmptyGun.mySound = new Sound(soundManager.fireEmptyGun);     soundManager.fireEmptyGun.mySound.attachSound("fireEmptyGun.wav");     soundManager.createEmptyMovieClip("fireGun", sound_depth++);     soundManager.fireGun.mySound = new Sound(soundManager.fireGun);     soundManager.fireGun.mySound.attachSound("fireGun.wav");     soundManager.fireGun.mySound.setVolume(30);     soundManager.createEmptyMovieClip("forest", sound_depth++);     soundManager.forest.mySound = new Sound(soundManager.forest);     soundManager.forest.mySound.attachSound("forest.wav");     soundManager.forest.mySound.setVolume(30);     soundManager.createEmptyMovieClip("lesterLaugh", sound_depth++);     soundManager.lesterLaugh.mySound = new Sound(soundManager.lesterLaugh);     soundManager.lesterLaugh.mySound.attachSound("lesterLaugh.wav");     soundManager.createEmptyMovieClip("peopleLaugh", sound_depth++);     soundManager.peopleLaugh.mySound = new Sound(soundManager.peopleLaugh);     soundManager.peopleLaugh.mySound.attachSound("peopleLaugh.wav");     soundManager.createEmptyMovieClip("shootLester", sound_depth++);     soundManager.shootLester.mySound = new Sound(soundManager.shootLester);     soundManager.shootLester.mySound.attachSound("shootLester.wav"); } 

As you can see, we're just creating one main sound manager clip and then creating several clips attached to it. Each clip is named for the sound it will contain. Then we create new sound objects on each of the clips. Each sound is named mySound . We conclude each effect by attaching the .wav file from our library. Now we can play sounds whenever we like with the following general form:

 soundManager.  soundToPlay  .mySound.start(); 

By filling in the soundToPlay reference, we can begin the playing of any sound we like from our sound manager.

Notice, however, that the names of my sound clips are based on the sound name, not the event name. That means I'll need to refer to Table 7.1 as I'm adding sounds to the game. I could have named the sound clips after their events, but that would have been less flexible. This way, if I want to change which sounds go with which events, all I have to do is change the table. The names of the sounds (the actual script) do not need to change.

Playing the Sounds

Now that everything is set up, it's time to sprinkle the sound-playing script about our game. We'll be placing these in one at a time, by sound.

The Ambient Forest Sounds

This is the sound of the forest. I want this sound effect to play constantly while the game is playing. I can do this by giving arguments to the Sound.start method when I call it. The best place to begin this constant sound is in the initGame function. At the end of that function, add the following script:

 createSoundManager(); soundManager.forest.mySound.start(0,9999999); 

That will keep the forest sound effect looping nicely . You might look at the number of loops ”9999999 ”and think that's not enough. And I supposed technically it's not because the loops will run out if given enough time. Note that the forest.wav sound is 9 seconds long, though. If you do the math, you'll find that the forest sound will repeat for a little under three years before it runs through the 9999999 repetitions. That should be long enough, I hope.

The Fire Gun and Fire Empty Gun Sounds

Both the gun-firing sounds will go inside the fireGun function, but we need to choose between the two. If the player has bullets left, we'll start the fire gun sound, and if he's out of bullets, we'll start the fire empty gun sound.

We already have an if statement inside fireGun that tests to see if bullets remain. Inside that if statement, along with all the other gun-firing code, we add the following line of script:

 soundManager.fireGun.mySound.start(); 

Now to get the empty gun sound, we add an else after the if(bullets) block:

 else soundManager.fireEmptyGun.mySound.start(); 
The Reload Gun Sound

This one is simple enough. Inside the reloadGun function, we add the following line:

 soundManager.reloadGun.mySound.start(); 
The Lester Shot Sound

Another simple sound effect runs when Lester is shot. Inside shootLester , we add the following line:

 soundManager.lesterShot.mySound.start(); 
The Lester Dead Sound

When Lester dies, inside the killLester function, we add the following line:

 soundManager.peopleLaugh.mySound.start(); 
The Game Over Sound

The game over sound plays when the game is over. We can put this sound in a few different places, but the best place is inside gameOff . Inside the gameOff function, we are moving Lester to the front of the stage and setting up his "Game Over" speech bubble. At the end of all that, we can add the following line:

 soundManager.lesterLaugh.mySound.start(); 

And that completes all the sound effects.

Testing

The first thing I noticed during testing was that the Lester corpses that were left when the player killed Lester were at a higher depth than Lester. That's not the effect I wanted. The problem is that I'm using duplicateMovieClip to create the corpses, and that keeps me from being able to attach the dead Lesters to an empty movie clip to organize them. Instead, the corpses must reside as members of the main timeline like Lester does.

To make room for the dead Lesters, I've changed some of the existing depth settings. The following is changed script from the initGame function:

 deadLester_depth = 3; lester_depth=65; tree3_depth=70; middleSwapClip_depth = 75; tree2_depth=80; tree1_depth=85; topSwapClip_depth = 90; 

That fixed the problems with Lester's corpses.

The final problem is the fact that this game isn't much fun to play. As you might expect, there is little I can do about that now, but it does illustrate some important points. Creating fun games is much harder than creating lame ones. You'll find this to be dreadfully true as you implement your own games. Keep in mind that not all the games you create will be fun to play. Don't be discouraged if you have what seems like a good idea that turns into a real bummer of a game. Everyone has that problem; just come up with another idea and try again.

Complete Code Listing

The following is a complete listing of the script used in Shoot 'Em Up . Use this to get an over-all view of the code:

 initGame(); function initGame(){     background_depth=1;     pond_depth = 2;     deadLester_depth = 3;     lester_depth=65;     tree3_depth=70;     middleSwapClip_depth = 75;     tree2_depth=80;     tree1_depth=85;     topSwapClip_depth = 90;     soundManager_depth = 100;     pondbottom_depth = 10000;     message_depth = 10001;     framecounter_depth = 10002;     scoreboard_depth = 10003;     reload_depth = 10004;     bullet_depth=55555;     crosshair_depth = 99999;     score=0;     time=0;     timeAllotment = 60;     bulletCapacity = 5;     lesterShotTolerance = 5;     attachMovie("background"," background",background_depth);     attachMovie("frame counter", "frameCounter", framecounter_depth);     frameCounter._x= 20;frameCounter._y= 30;     frameCounter._xscale = 20;frameCounter._yscale = 20;     attachMovie("scoreboard", "scoreboard", scoreboard_depth);     scoreboard._x= 65;scoreboard._y= 475;     scoreboard._xscale = 30;scoreboard._yscale = 30;     attachMovie("tree1"," tree1",tree1_depth);     tree1._x=115;tree1._y=100;     attachMovie("tree2"," tree2",tree2_depth);     tree2._x=492;tree2._y=270;     attachMovie("tree3"," tree3",tree3_depth);     tree3._x=345;tree3._y=296;     attachMovie("pond"," pond",pond_depth);     pond._x=342;pond._y=445;     attachMovie("pondbottom"," pondbottom",pondbottom_depth);     pondbottom._x=342;pondbottom._y=445;     attachMovie("message"," message", message_depth);     messageState = 0     attachMovie("lester"," lester",lester_depth, {holecount:0});     lester._x = 340;lester._y = 285;     lester._xscale = 18;lester._yscale=18;     lester._visible = false     createSoundManager();     soundManager.forest.mySound.start(0,9999999); } function setBubble(){     switch(messageState){         case 0:             message.bubble.button.onRelease = function(){                 messageState++;                 setBubble();             }             message.bubble.button.mytext.text=" Next";             message.bubble.mytext.text=" Welcome! Shoot me "+ lesterShotTolerance +    "times to kill me.";             break;         case 1:             message.bubble.mytext.text=" You have one minute so shoot fast!"             break;         case 2:             message.bubble.mytext.text=" Here is your gun. Reload when you run    out of bullets." attachMovie("button"," reload",reload_depth);             reload.mytext.text=" Reload";             reload._x=630;             reload._y=480;             reload._xscale = 40;             reload._yscale = 40;             reload.stop();             reloadGun(true);             resetScoreboard();             break;         case 3:             message.bubble.mytext.text=" Ready... Set..." ;             message.bubble.button.mytext.text=" Go!";             break;         case 4:             message.play();             gameOn();             messageState++;             break;         case 5:             message.bubble.button.onRelease = function(){                 messageState=2;                 setBubble();                 messageState=4;                 setBubble();             }             message.bubble.button.mytext.text=" Restart";             message.bubble.mytext.text=" Hahahaha, you ran out of time!";         } } function playGame(){     switch(gameState){         case 0://lester chooses his route             getNewRoute();             gameState=1;             break;         case 1://lester is moving             gameState = moveLester();             break;     } } function gameOn(){     gunOn();     crosshairOn();     timeInterval = setInterval(clockTick,1000);     onEnterFrame = playGame;     gameState=0;     lester._visible=true     lester.route=0;     lester.routeFlip=0;     reload.onRelease = reloadGun; } function gameOff(){     gunOff();     crosshairOff();     onEnterFrame = null;     reload.onRelease = null;     clearInterval(timeInterval);     removeHoles();     lester._visible=false     message.play()     for(i=1;i<=score;++i)         _root["dupe"+deadLester_depth+i].removeMovieClip();     soundManager.lesterLaugh.mySound.start(); } function crosshairOn(){     Mouse.hide();     attachMovie("crosshair"," crosshair",crosshair_depth);     crosshair.onMouseMove=function(){         crosshair._x=_xmouse;         crosshair._y=_ymouse;         updateAfterEvent();     } } function crosshairOff(){     Mouse.show();     crosshair.removeMovieClip(); } function resetScoreboard(){     score=0;     time = timeAllotment; } function clockTick(){     if(time == 0){         gameOff();     } } function getNewRoute(){     if(lester.routeFlip){         lester.routeFlip = false;         lester.route+=100;     }     else         lester.route = getRandom(0,11);     switch(lester.route){         //           x0,y0,scale0,x10,y20,scale1,stepcount         case 0: routeDesc = [340,275,20,504,321,28,40];break;         case 1: routeDesc = [504,321,28,340,275,20,40];break;         case 2: routeDesc = [504,321,28,715,278,22,35];break;         case 3: routeDesc = [715,278,22,504,321,28,35];break;         case 4: routeDesc = [340,295,18,705,295,24,75];break;         case 5: routeDesc = [705,295,24,340,295,18,75];break;         case 6: routeDesc = [340,285,20,200,300,21.5,40];break;         case 7: routeDesc = [200,300,21.5,340,285,20,40];break;         case 8: routeDesc = [185,300,21.5,-20,380,26,40];break;         case 9: routeDesc = [-20,380,26,185,300,21.5,40];break;         case 10: routeDesc = [495,350,28,495,310,28,10];              lester.routeFlip = true; break;         case 11: routeDesc = [345,540,48,345,480,48,10];              lester.routeFlip = true; break;         case 110: routeDesc = [495,310,28,495,350,28,10];break;         case 111: routeDesc = [345,480,48,345,540,48,10];break;     }     lester._x = routeDesc[0];     lester._y = routeDesc[1];     lester._xscale = lester._yscale = routeDesc[2];     lester.rx = routeDesc[3];     lester.ry = routeDesc[4];     lester.rs = routeDesc[5];     lester.stepcount = Math.ceil(routeDesc[6]/3 + routeDesc[6]/(score+1)); } function moveLester(){     lester._x+= (lester.rx - lester._x) / lester.stepcount;     lester._y+= (lester.ry - lester._y) / lester.stepcount;     lester._yscale = lester._xscale += (lester.rs - lester._xscale) /    lester.stepcount;     lester.stepcount;     if(lester.stepcount==0) return 0;     return 1; //return 0 for find new route, 1 to continue on this one } function fireGun(){     if(bullets){         _root["bullet"+(bulletCapacity+1-bullets)].removeMovieClip();         gunOff();         gunInterval = setInterval(gunOn,750);         bullets;         if(!tree1.hitTest(_xmouse,_ymouse, true) &&    !tree2.hitTest(_xmouse,_ymouse, true) && !tree3.hitTest(_xmouse,_ymouse, true) &&    !pondbottom.hitTest(_xmouse,_ymouse, true) && lester.hitTest(_xmouse,_ymouse,    true)){              shootLester();         }         soundManager.fireGun.mySound.start();     }     else soundManager.fireEmptyGun.mySound.start(); } function reloadGun(paused){     gunOff();     if(!paused)         gunInterval = setInterval(gunOn,750);     bullets=bulletCapacity;     for(i=1;i<=bulletCapacity;++i){         var tempbullet = attachMovie("bullet"," bullet"+i, bullet_depth+i);         tempbullet._x = 520 + i*10;         tempbullet._y = 490;         tempbullet._xscale = tempbullet._yscale = 20;     }     soundManager.reloadGun.mySound.start(); } function gunOn(){     onMouseUp = fireGun;     clearInterval(gunInterval); } function gunOff(){     onMouseUp = null; } function shootLester(){     lester.gotoAndPlay("shot");     lester.attachMovie("hole"," hole"+(++lester.holecount),lester.holecount);     lester["hole"+lester.holecount]._x = lester._xmouse;     lester["hole"+lester.holecount]._y = lester._ymouse;     if(lester.holecount == lesterShotTolerance)         killLester();     soundManager.lesterShot.mySound.start(); } function killLester(){     score++;     var deadLester = lester.duplicateMovieClip("dupe"+deadLester_depth+score,    deadLester_depth+score);     deadLester._rotation=90;     deadLester._y+= 50;     deadLester.gotoAndStop("dead");     for(i=1;i<lesterShotTolerance+1;++i){         var oldHole= lester["hole" + i]         var newHole = deadLester.attachMovie("hole", "hole" + i, i);         newHole._x = oldHole._x         newHole._y = oldHole._y         newHole._rotation = -90     }     removeHoles();     lester.gotoAndPlay("begin");      getNewRoute();      soundManager.peopleLaugh.mySound.start(); } function removeHoles(){     for(i=1;i<lesterShotTolerance+1;++i)         lester["hole"+i].removeMovieClip();     lester.holecount=0; } function createSoundManager(){     var sound_depth = 1;     createEmptyMovieClip("soundManager",soundManager_depth);     soundManager.createEmptyMovieClip("reloadGun", sound_depth++);     soundManager.reloadGun.mySound = new Sound(soundManager.reloadGun);     soundManager.reloadGun.mySound.attachSound("reloadGun.wav");     soundManager.reloadGun.mySound.setVolume(50);     soundManager.createEmptyMovieClip("fireEmptyGun", sound_depth++);     soundManager.fireEmptyGun.mySound = new Sound(soundManager.fireEmptyGun);     soundManager.fireEmptyGun.mySound.attachSound("fireEmptyGun.wav");     soundManager.createEmptyMovieClip("fireGun", sound_depth++);     soundManager.fireGun.mySound = new Sound(soundManager.fireGun);     soundManager.fireGun.mySound.attachSound("fireGun.wav");     soundManager.fireGun.mySound.setVolume(30);     soundManager.createEmptyMovieClip("forest", sound_depth++);     soundManager.forest.mySound = new Sound(soundManager.forest);     soundManager.forest.mySound.attachSound("forest.wav");     soundManager.forest.mySound.setVolume(30);     soundManager.createEmptyMovieClip("lesterLaugh", sound_depth++);     soundManager.lesterLaugh.mySound = new Sound(soundManager.lesterLaugh);     soundManager.lesterLaugh.mySound.attachSound("lesterLaugh.wav");     soundManager.createEmptyMovieClip("peopleLaugh", sound_depth++);     soundManager.peopleLaugh.mySound = new Sound(soundManager.peopleLaugh);     soundManager.peopleLaugh.mySound.attachSound("peopleLaugh.wav");     soundManager.createEmptyMovieClip("shootLester", sound_depth++);     soundManager.shootLester.mySound = new Sound(soundManager.shootLester);     soundManager.shootLester.mySound.attachSound("shootLester.wav"); } //utility function to generate a random integer in the range minimum    to maximum inclusive function getRandom(minimum, maximum){      return Math.floor(Math.random() * (maximum - minimum +1) + minimum); } 



Macromedia Flash MX 2004 Game Programming
Macromedia Flash MX 2004 Game Programming (Premier Press Game Development)
ISBN: 1592000363
EAN: 2147483647
Year: 2004
Pages: 161

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net