Adding Game Effects
Gone are the early days of games on the Web, just when the idea of a game in a web page was cool enough to get your attention. Now, you have to work to add quality, like little touches such as animation and sound, to your games.
Let's spruce up this simple matching game with some special effects. Although they don't change the basic game play, they make the game seem a lot more interesting to players.
Animated Card Flips
Because we are flipping virtual cards over and back, it makes sense to want to see this flip as an animation. You can do this with a series of frames inside a movie clip, but because you're learning ActionScript here, let's do it with ActionScript.
Because this animation affects the cards, and only the cards, it makes sense to put it inside the Card class. However, we don't have a Card class. We opted at the start of this chapter to not use a Card class and just allow Flash to assign a default class to it.
Now it is time to create Card class. If we make a Card.as file, however, it is used by any Card object that is in the folder. We have MatchingGame1.fla through MatchingGame9.fla with Card objects in it. So, to make it clear that we only want MatchingGame10.fla to use this Card class, we change the name of the symbol and the class it references to Card10. Then, we create a Card10.as ActionScript class file.
This class enables an animated flip of the card, rather than just changing the card instantly. It replaces all the gotoAndStop functions in the main class. Instead, it tells the card to startFlip. It also passes in the frame which the card should show when the flip is over. The Card10 class then sets up some variables, sets up an event listener, and proceeds to animate the card over the next 10 frames:
package { import flash.display.*; import flash.events.*; public dynamic class Card10 extends MovieClip { private var flipStep:uint; private var isFlipping:Boolean = false; private var flipToFrame:uint; // begin the flip, remember which frame to jump to public function startFlip(flipToWhichFrame:uint) { isFlipping = true; flipStep = 10; flipToFrame = flipToWhichFrame; this.addEventListener(Event.ENTER_FRAME, flip); } // take 10 steps to flip public function flip(event:Event) { flipStep--; // next step if (flipStep > 5) { // first half of flip this.scaleX = .2*(flipStep-6); } else { // second half of flip this.scaleX = .2*(5-flipStep); } // when it is the middle of the flip, go to new frame if (flipStep == 5) { gotoAndStop(flipToFrame); } // at the end of the flip, stop the animation if (flipStep == 0) { this.removeEventListener(Event.ENTER_FRAME, flip); } } } }
So, the flipStep variable starts at 10 when the startFlip function is called. It then is reduced by one frame each thereafter.
If flipStep is between 6 and 10, the scaleX property of the card is set to .2*(flipStep-6), which would be .8, .6, .4, .2, and 0. So, it gets thinner with each step.
Then, when flipStep is between 5 and 0, the new formula of .2*(5-flipStep) is used. So, it would be 0, .2, .4, .6, .8, and then 1.0, and it returns to normal size.
At the fifth step, the card jumps to the new frame. It appears to shrink, goes to nothing, jumps to the new frame, and then grows again.
To accomplish this effect, I had to make one change to how the graphics on the Card movie clip were arranged. In all previous versions of the game, the cards had their upper-left corner at the center of the movie clip. For the change to scaleX to make it appear that the card was flipping around its center, however, I had to center the card graphics on each frame over the center of the movie clip. Compare the Card movie clips in MachingGame9.fla and MatchingGame10.fla to see the difference. Figure 3.16 shows how this looks when editing the movie clips.
Figure 3.16 The left side shows the registration point of the movie clip at the upper left, as it is in the first nine example movies of this chapter. The right side shows the movie clip centered as it is for the final example.
At the last step, the event listener is removed completely.
The great thing about this class is it works just as well when the card is being turned back face down, going to frame 1.
Look at MatchingGameObject10.as and see where all the gotoAndStop calls have been replaced with startFlip. By doing this, we are not only creating a flip animation, but we are also giving the Card class more control over itself. Ideally, you might want to give cards complete control over themselves by having the Card10.as class more functions, such as those that set the location of the cards at the start of the game.
Limited Card-Viewing Time
Another nice touch to this game is to automatically turn over pairs of mismatched cards after the player has had enough time to look at them. For instance, the player chooses two cards. They don't match, so they remain face up for the player to inspect. After 2 seconds, however, the cards turn over, even if the player hasn't begun to select another pair.
To accomplish this, we use a Timer. A Timer makes adding this feature relatively easy. To start, we need to import the Timer class into our main class:
import flash.utils.Timer;
Next, we create a timer variable at the start of the class:
private var flipBackTimer:Timer;
Later in the clickCard function, we add some code right after the player has chosen the second card, not made a match, and his or her score has been decreased. This Timer code sets up the new timer, which calls a function when 2 seconds have gone by:
flipBackTimer = new Timer(2000,1); flipBackTimer.addEventListener(TimerEvent.TIMER_COMPLETE,returnCards); flipBackTimer.start();
The TimerEvent.TIMER_COMPLETE event is triggered when a timer is done. Typically, a Timer runs a certain number of times, triggering a TimerEvent.TIMER each time. Then, on the last event, it also triggers the TimerEvent.TIMER_COMPLETE. Because we only want to trigger a single event at some point in the future, we set the number of Timer events to one, and then look for TimerEvent.TIMER_COMPLETE.
When 2 seconds go by, the returnCards function is called. This is a new function that works like the later part of the old clickCard function. It flips both the first and second selections back to the face-down state, and then sets the firstCard and secondCard values to null. It also removes the listener:
public function returnCards(event:TimerEvent) { firstCard.startFlip(1); secondCard.startFlip(1); firstCard = null; secondCard = null; flipBackTimer.removeEventListener(TimerEvent.TIMER_COMPLETE,returnCards); }
The returnCards function duplicates code that was in clickCard before, so in MatchingGameObject10.as I've replaced this duplicate code in clickCard with a simple call to returnCards. This way, we only have one spot in our code that returns a pair of cards to the face-down state.
Because returnCards demands a single event parameter, we need to pass that parameter into returnCards whether we have something to pass. So, the call inside clickCard passes a null:
returnCards(null);
If you run the movie, flip two cards, and then wait, the cards flip back on their own.
Because we have a removeEventListener command in the returnCards function, the listener is removed even if the returnCards function is triggered by the player turning over another card. Otherwise, the player turns over a new card, the first two cards turns back, and then the event is triggered after 2 seconds regardless of the fact that the original two cards are already face down.
Sound Effects
No game is truly complete without sound. ActionScript 3.0 makes adding sound relatively easy, although there are quite a few steps involved.
The first step is to import your sounds. I've created three sounds and want to bring them each into the library:
FirstCardSound.aiff MissSound.aiff MatchSound.aiff
After we have imported them, they need to have properties changed. Name them all after their filenames, but minus the .aiff extension. Also, check the Export for ActionScript option and give them the same class name as symbol name. Figure 3.17 shows one of the sound's Properties dialog box.
Figure 3.17 Each sound is a class and can be accessed in ActionScript by its class name.
Next, we set up the main game class to play the sounds at the right time. First, we need to import two new classes so we can use sound:
import flash.media.Sound; import flash.media.SoundChannel;
Then, we create class variables to hold references to these sounds:
var theFirstCardSound:FirstCardSound = new FirstCardSound(); var theMissSound:MissSound = new MissSound(); var theMatchSound:MatchSound = new MatchSound();
I like to pass all sound playback through a single function. Let's call it playSound and add it to the end of the class:
public function playSound(soundObject:Object) { var channel:SoundChannel = soundObject.play(); }
When we want a sound to play, we call playSound with the sound variable we want to use, as follows:
playSound(theFirstCardSound);
In MatchingGameObject10.as, I've added playSound(theFirstCardSound) when the first card is clicked and when a card is clicked while two mismatched cards are shown. I've added playSound(theMissSound) when the second card is turned over and there is no match. I've added playSound(theMatchSound) when the second card is turned over and a match is found.
This is all it takes to add sound effects to the game.