Add Power-Ups to Your AS3 Avoider Game

by Michael James Williams on April 22, 2009 · 19 comments

in Avoider Game Extras,Tutorial

Table of contents for AS3 Avoider Game: Collectibles

  1. Add Collectibles to Your AS3 Avoider Game
  2. Add More Collectibles to Your AS3 Avoider Game
  3. Add Power-Ups to Your AS3 Avoider Game

This guy is pretty powered up
Photo by ÐIÐËO.

Alright, so coins and more coins are great and all, but I think we can all agree that items that make you super-fast, really tall, and radioactive are more fun.

In this final part of the three piece Collectibles series, we’ll extend our framework so that we can add awesome power-ups that change your (and the enemies’!) size and speed. We’ll also leave this all open enough that you can create new types of power-up that go way beyond this.

Click the image below to see how this will play:

Snapshot_O03.png

If you’ve been following this mini-series, you can just pick up where we left off in the last part. If not, start at the beginning. Alternatively, just grab this zip file; it has all you need. Either way, open the FLA, and let’s get started.

Collectible + Code = Power-Up

Really, the “skull” collectibles we’ve made so far are already power-ups themselves. Thing is, instead of increasing the player’s size or speed, they increase the player’s score. It won’t be difficult to alter this.

Let’s quickly recap how it works at the minute:

  • Collectible class contains a reference to a graphic (for displaying the power-up in the play screen), a base value (for containing how many points should be added to or removed from the score), and the type of collectible (golden/platinum skull).
  • LevelData class defines the spawn rate of all collectibles, and the maximum number of collectibles allowed on-screen at once.
  • AvoiderGame class uses the info from the above two classes to decide when to create collectibles and what to do when the player or an enemy touches one of them.

As we’ve seen, to introduce a new type of point-affecting collectible all we need to do is add the definition to Collectible and the behaviour information to the AvoiderGame. Oh, and we’ll probably want to draw a new image to use as well, though we could re-use another.

Starting Small

For our first power-up, we’ll go with something truly useful: an item to make the avatar smaller so that it’s easier to dodge the enemies. I’ve mentioned the scaleX and scaleY properties before — changing these change the width and height of a display object proportionally; each is set to 1 by default, so setting it to 0.5 will halve the size while 2 will double it.

In Alice in Wonderland, the titular character shrinks after drinking a potion labelled “DRINK ME”. So, here is my new power-up graphic:

screenshot

(For those of you wondering whether my graphical skillz had improved since Part 9, I hope this answers your question.)

Naturally this should be created as a new symbol of type movie clip, exported for ActionScript with a name like DrinkMePotion. Once you’ve done all that, open Collectible.as so that we can add it in, like so:

?View Code ACTIONSCRIPT3
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public function Collectible( type:String )
{
	collectibleType = type;
	if ( collectibleType == "GoldenSkull" )
	{
		graphic = new GoldenSkull();
		baseValue = 25;
	}
	else if ( collectibleType == "PlatinumSkull" )
	{
		graphic = new PlatinumSkull();
		baseValue = 50;
	}
	else if ( collectibleType == "DrinkMePotion" )
	{
		graphic = new DrinkMePotion();
		baseValue = 50;
	}
}

Hm. We’ve run into a bit of a snag here. What do we set baseValue to? Well, I suppose we should set it to 0.5, in order to halve the avatar’s size. But then, how will AvoiderGame know to apply this number to the scaleX and scaleY properties of the avatar, rather than the score? Well, I guess we’ll just build that into the AvoiderGame.as code, like so:

?View Code ACTIONSCRIPT3
210
211
212
213
214
215
216
217
218
219
220
221
222
223
if ( PixelPerfectCollisionDetection.isColliding( avatar, collectible.graphic, this, true ) ) 
{
	removeChild( collectible.graphic );
	collectibles.splice( j, 1 );
	if ( collectible.collectibleType == "DrinkMePotion" )
	{
		avatar.scaleX = collectible.baseValue;
		avatar.scaleY = collectible.baseValue;
	}
	else
	{
		gameScore.addToValue( collectible.baseValue );
	}
}

OK… except, what do we do if we want to add a new item that causes the player’s size to change? A cake that causes them to grow, perhaps. We’ll have to add this info, not just to the Collectible class where it belongs, but also to the AvoiderGame class:

?View Code ACTIONSCRIPT3
210
211
212
213
214
215
216
217
218
219
220
221
222
223
if ( PixelPerfectCollisionDetection.isColliding( avatar, collectible.graphic, this, true ) ) 
{
	removeChild( collectible.graphic );
	collectibles.splice( j, 1 );
	if ( ( collectible.collectibleType == "DrinkMePotion" ) || ( collectible.collectibleType == "EatMeCake" ) )
	{
		avatar.scaleX = collectible.baseValue;
		avatar.scaleY = collectible.baseValue;
	}
	else
	{
		gameScore.addToValue( collectible.baseValue );
	}
}

Ugh. Imagine what this will look like once we’ve added a few more collectibles: a mess. Even though the cake’s behaviour — the code run upon touching the cake — is exactly the same as the potion’s, we still have to edit this part of AvoiderGame when we add it. This removes much of the benefit of using an external Collectible class; we might as well just keep all the info inside AvoiderGame.

Don’t worry though, this is easy to fix. All we need to do is add information about the power-up’s behaviour to the Collectible class, like so:

?View Code ACTIONSCRIPT3
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Collectible
{
	public var graphic:DisplayObject;
	public var collectibleType:String;
	public var baseValue:Number;
	public var behaviourType:String;
 
	public function Collectible( type:String )
	{
		collectibleType = type;
		if ( collectibleType == "GoldenSkull" )
		{
			graphic = new GoldenSkull();
			baseValue = 25;
			behaviourType = "score";
		}
		else if ( collectibleType == "PlatinumSkull" )
		{
			graphic = new PlatinumSkull();
			baseValue = 50;
			behaviourType = "score";
		}
		else if ( collectibleType == "DrinkMePotion" )
		{
			graphic = new DrinkMePotion();
			baseValue = 0.5;
			behaviourType = "scale";
		}
	}
}

(Feel free to remove the u from behaviour, if it bothers you ;) )

Now, back in AvoiderGame, we link the behaviour code to the behaviour type, not surprisingly:

?View Code ACTIONSCRIPT3
210
211
212
213
214
215
216
217
218
219
220
221
222
223
if ( PixelPerfectCollisionDetection.isColliding( avatar, collectible.graphic, this, true ) ) 
{
	removeChild( collectible.graphic );
	collectibles.splice( j, 1 );
	if ( collectible.behaviourType == "scale" )
	{
		avatar.scaleX = collectible.baseValue;
		avatar.scaleY = collectible.baseValue;
	}
	else if ( collectible.behaviourType == "score" )
	{
		gameScore.addToValue( collectible.baseValue );
	}
}

And in the code that controls what happens when an enemy touches a collectible:

?View Code ACTIONSCRIPT3
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
if ( PixelPerfectCollisionDetection.isColliding( collectible.graphic, enemy, this, true ) ) 
{
	removeChild( collectible.graphic );
	collectibles.splice( j, 1 );
 
	if ( collectible.behaviourType == "scale" )
	{
		enemy.scaleX = collectible.baseValue;
		enemy.scaleY = collectible.baseValue;
	}
	else if ( collectible.behaviourType == "score" )
	{
		gameScore.addToValue( -collectible.baseValue );
	}
}

As you can see, it’s now really easy to add new types of power-up, or change what existing types do, just by changing a couple of lines in the Collectible class alone.

There is still the niggling issue of the spawn rates being defined completely by AvoiderGame. I’m going to leave this out for the sake of space — why not have a go at fixing it yourself?

For now, we can just change it to the following, so that potions are generated:

?View Code ACTIONSCRIPT3
145
146
147
148
149
150
151
152
153
154
155
156
if ( Math.random() < 0.2 )
{
	newCollectible = new Collectible( "GoldenSkull" );
}
else if ( Math.random() < 0.4 )
{
	newCollectible = new Collectible( "PlatinumSkull" );
}
else
{
	newCollectible = new Collectible( "DrinkMePotion" );
}

Note that I’ve changed the probabilities so that potions have a 60% chance of appearing — I really want to see how they work!

Save and run the game:

Snapshot_O01.png

Tee hee.

Gotta Go Faster

That’s size sorted, how about speed?

I do enjoy doodling these items, so I’m going to start by making my “speed up” object:

screenshot

Rollerskates! Obviously I’ve created this as a movie clip called RollerSkates which I’ve exported for ActionScript.

Adding these to the Collectible class is simple enough:

?View Code ACTIONSCRIPT3
33
34
35
36
37
38
else if ( collectibleType == "RollerSkates" )
{
	graphic = new RollerSkates();
	baseValue = 2;
	behaviourType = "speed";
}

Now for the behaviour. We’ll deal with the enemy first, as that’s simpler; take a look at Enemy.as:

?View Code ACTIONSCRIPT3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Enemy extends MovieClip 
{
	public var xSpeed:Number;	//pixels moved to the right per tick
	public var ySpeed:Number;	//pixels moved downwards per tick
 
	public function Enemy( startX:Number, startY:Number ) 
	{
		x = startX;
		y = startY;
 
		xSpeed = 0;
		ySpeed = 3;
	}
 
	public function moveABit():void 
	{
		x = x + xSpeed;
		y = y + ySpeed;
	}
}

The speed is controlled by a couple of public variables, which is great, as that’ll be easy to change. Head to the relevant part of AvoiderGame:

?View Code ACTIONSCRIPT3
247
248
249
250
251
else if ( collectible.behaviourType == "speed" )
{
	enemy.speedX = collectible.baseValue;
	enemy.speedY = collectible.baseValue;
}

Wait, this isn’t right… it’ll make the enemy move diagonally, two pixels to the right and two pixels down every tick. We need to multiply the current speedX and speedY by the base value:

?View Code ACTIONSCRIPT3
247
248
249
250
251
else if ( collectible.behaviourType == "speed" )
{
	enemy.xSpeed = enemy.xSpeed * collectible.baseValue;
	enemy.ySpeed = enemy.ySpeed * collectible.baseValue;
}

Might as well test it now — though of course, we’ll need to make it spawn. I’ll leave you to add that code. Here is the result:

screenshot

It works, though the game gets a little crazy! Now we need to do the same thing with the avatar. This is almost as easy; take a look at Avatar.as:

?View Code ACTIONSCRIPT3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Avatar extends MovieClip 
{
	public function Avatar() 
	{
 
	}
 
	public function moveABit( xDistance:Number, yDistance:Number ):void
	{
		var baseSpeed:Number = 3;
		x += ( xDistance * baseSpeed );
		y += ( yDistance * baseSpeed );
	}
}

All we need to do is make that baseSpeed into a class-level, public variable, and we’ll be set:

?View Code ACTIONSCRIPT3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Avatar extends MovieClip 
{
	public var baseSpeed:Number;
	public function Avatar() 
	{
		baseSpeed = 3;
	}
 
	public function moveABit( xDistance:Number, yDistance:Number ):void
	{
		x += ( xDistance * baseSpeed );
		y += ( yDistance * baseSpeed );
	}
}

The code for AvoiderGame is simple too:

?View Code ACTIONSCRIPT3
231
232
233
234
else if ( collectible.behaviourType == "speed" )
{
	avatar.baseSpeed = avatar.baseSpeed * collectible.baseValue;
}

While we’re at it, I’m going to alter the behaviour code for scale to make it multiply the size by the base value, like we’re doing with the speed:

?View Code ACTIONSCRIPT3
222
223
224
225
226
if ( collectible.behaviourType == "scale" )
{
	avatar.scaleX = avatar.scaleX * collectible.baseValue;
	avatar.scaleY = avatar.scaleY * collectible.baseValue;
}
?View Code ACTIONSCRIPT3
246
247
248
249
250
if ( collectible.behaviourType == "scale" )
{
	enemy.scaleX = enemy.scaleX * collectible.baseValue;
	enemy.scaleY = enemy.scaleY * collectible.baseValue;
}

This way, if you collect two potions, you’ll become really, really small. Hilarity will ensue, I’m sure.

Try these new changes out by clicking on the image below:

Snapshot_O03.png

So awesome.

Challenges

What next? Well, naturally there’s a huge number of different power-ups you could come up with. How about one that slows all the enemies down (or speeds them up, if they grab it), or one that makes the enemies change direction (or switches around the player’s controls), or one that causes the player (or the enemy) to teleport to a random position?

Feature-wise, a great addition would be to add a time property to Collectible, which makes the effect of the power-up wear off after a certain number of ticks have passed. This way you could add power-ups that make the player invincible without it having to last the entire game.

Also, being able to combine different behaviours in a single object would be neat. Sure, you could create a new behaviour called (for example) “speedandscale” that affects both the speed and the size of whomever picks it up, but that’s a cheap way of doing things. It would be much better to store an array of behaviours within Collectible and loop through it in AvoiderGame. Once you get into this, it can be quite tricky, but the challenge will be worth it!

Wrapping Up

I had fun doing this part. I think it’s ’cause I got to draw stuff. I hope you feel the same way ;)

If you want to grab the code and the FLA that I used in this tutorial, you can download them in a zip file from here.

I’d really be interested in seeing the power-ups you come up with. Many of them will fit in just a few lines of code — if you’d paste your favourites into the comments below that would be awesome :)

{ 18 comments… read them below or add one }

Snurre April 22, 2009 at 11:05 am

Great ! :)

But there is a huge bug now, after taking 8 scale-powerups, the hittest is not functional anymore. So, that would be an other challenge for some of you.

MichaelJWilliams April 22, 2009 at 1:47 pm

Dang, I didn’t notice that. Thanks Snurre! I suppose it should have some limit on the smallest the skull can get.

Snurre April 22, 2009 at 7:37 pm

Yep, and then the scaleing should be reversed, cause it’s no fun being that small, it’s way too easy..)

MichaelJWilliams April 22, 2009 at 7:39 pm

Yeah, you’re right, it’s just it was getting late and I’d only be repeating myself anyway…

FrozenHaddock April 24, 2009 at 9:44 am

Another good addition to the series :)

(Sorry for not getting anything Avoid-y in this week, I had some freelance work and some life troubles. I’ll try and get something in for next week.)

MichaelJWilliams April 25, 2009 at 3:59 pm

Thanks :) And don’t worry about it ;)

Ardavanski May 31, 2009 at 2:12 pm

Thanks :) But, if you take to many potions you get soooooo small that you cant see the player anymore :O and same with speed, you get so fast, that you can with 1 click get off the screen. I would recomend adding boundaries to your tutorials, because the player CAN just go out of the screen = free highscores!

if (this._x>=500) {

this._x = 500;

} else if (this._x<=0) {

this._x = 0;

}

if (this._y>=400) {

this._y = 400;

} else if (this._y<=0) {

this._y = 0;

}

}

this is for my unfinished-dumped-project-game. Would be cool if you added it, would help some people :)

Michael Williams June 1, 2009 at 4:45 pm

Good points :) But really I can only go so far in a tutorial. The idea is that you can take what you’ve learned and use it to solve these sorts of problems — in this case, that might be by limiting the player’s speed, or by stopping speed power-ups spawning once the player gets too fast, or whatever.

Ditto for the boundaries (though this was an honest mistake on my part, at first). I really think it helps you more if you work out how to fix the problem yourself — as long as the tutorial teaches you enough to be able to do that, of course.

Cheers for sharing that code! Like you say, I’m sure it will help some people :)

JimE February 12, 2010 at 11:23 pm

Micheal,
Some of my collectibles are movieclips. Is there a trick to accessing the currentframe of those at the display object level once they have been add childed?

i’m trying to pause them along with pausing the game in general but cant seem to get true instance name while looping through the children of display object. If you could point me in right direction or something would be awesome as this might be last major hurdle I have not fixed yet after a month of reading and building.
Thks,
jim

Michael Williams February 13, 2010 at 2:27 am

Hi JimE,

What does your code look like at the minute?

You can use .stop() and .play() to pause and resume the animation of the movie clips, or .gotoAndStop( frameNumber ), .gotoAndPlay( frameNumber ) and .currentFrame to stop and play the animations at certain frames.

JimE February 13, 2010 at 10:34 am

Currently I am still just working on tracing the output of loop to figure out issue

        private function pauseGame(e:Event = null):void
        {
            // Update the pause button based on the isPaused flag 
            if (isPaused)
            {
                btnPause.gotoAndStop(1);
                trace("game is active")
            } else {
                    btnPause.gotoAndStop(2);
                    trace("game is paused")
                    trace("Number of children of Stage: " + numChildren);
                    var r:int = numChildren -1 ;
                        while ( r > -1 ) {
                            trace(r);
                            trace("child at 33" + this.getChildAt(33));
                            trace("name " + this.getChildAt(r));
                            trace(this.getChildAt(r).name);
                            // if object(r) == Mov* then gotoAndStop  currentFrame somehow need to accomplish this or have that same effect
                            r--;
                        } //ends testing for moving objects when game is paused
                }
            isPaused = !isPaused;  // Reverse the game.isPaused boolean value
        }

the relevant output for traces returns this
33
child at 33[object MovPeople3]
name [object MovPeople3]
instance5575

At this time I am pretty positive the instance name is not being recognized from the formation of the movieclip in the flash cs3 and I suspect that is my biggest issue to tagging the current frame .. but not at all sure .. lots of research but no joy so far. that’s why posted to you.

exported to actionscript not on first frame
Class setting = parkinggame.MovPeople3
Base Class = flash.display.MovieClip

Thks for perusing this much appreciated.
Jim

Michael Williams February 17, 2010 at 11:11 am

Hey Jim,

Your logic is good, there’s just one little thing (which I haven’t covered in this tutorial) that you need to change.

This line here:

if object(r) == Mov*

First, rather than object(r) you should use this.getChildAt(r), like you’ve done above.

Second, to check what class an object is, you can use the is keyword, rather than ==.

So here’s a new if statement:

if ( this.getChildAt(r) is MovieClip )

Or, you could check:

if ( this.getChildAt(r) is Enemy )

…and so on.

Then, to stop the animation, you can simply do:

this.getChildAt(r).stop();

Actually, that might not work — Flash doesn’t know for sure that an object you obtain using getChildAt() has a stop() function. You have to tell it, “it’s OK, this is a MovieClip, so it’ll have stop()“:

( this.getChildAt(r) as MovieClip ).stop();

So, as says, “treat this as a MovieClip”.

Hope that helps :)

JimE February 17, 2010 at 2:05 pm

Getting close for sure here. Spent some time just this morning after realizing had to cast displayobject as movieclip. But still have some ways to go

… thoughts right now are that I need to selectively do so.

ie if displayobject is a collectible type 3 then treat as movieclip .

Other wise seem to be getting some issues. And those are the only objects that are of concern.

JimE February 19, 2010 at 8:00 pm

Hope this looks ok … thought this may help some other people tackle pausing timeline movieclips. I am sure it could better but it is working for me.

            private function pauseGame(e:Event = null):void {
            var collectible:Collectible;
            if (isPaused)
            {
                btnPause.gotoAndStop(1);
                trace("game is active");
                    var r:int = numChildren -1 ;
                    while ( r > -1 ) {
                        var myMC:MovieClip = (this.getChildAt(r)) as MovieClip;
                        var m:int = collectibles.length - 1;
                            while ( m > -1 ) {
                                collectible = collectibles[m];
                                    if ((myMC == collectible.graphic)){
                                        if (collectible.behaviourType == 3){
if (myMC.currentFrame > 1){ if (myMC.totalFrames > myMC.currentFrame ){ trace("starting correct movieclips " +r); myMC.gotoAndPlay( myMC.currentFrame ); m = -1; } } } } m--; } r--; } } else { btnPause.gotoAndStop(2); trace("game is paused"); var r:int = numChildren -1 ; while ( r > -1 ) { var myMC:MovieClip = (this.getChildAt(r)) as MovieClip; var m:int = collectibles.length - 1; while ( m > -1 ) { collectible = collectibles[m]; if ((myMC == collectible.graphic)){ if (collectible.behaviourType == 3){
if (myMC.currentFrame > 1){ if (myMC.totalFrames > myMC.currentFrame ){ trace("stopping correct movieclips " +r); myMC.gotoAndStop( myMC.currentFrame ); m = -1; } } } } m--; } r--; } } isPaused = !isPaused; // Reverse the game.isPaused boolean value }

My Collectibles.as contains many of the variables I needed to access and once I had that the casting of object as movieclip was the key.

Michael
Thanks so much,
Jim

Michael Williams February 22, 2010 at 9:13 pm

Cheers, Jim :)

pepeu October 5, 2010 at 11:01 pm

Hi Jim,
Perhaps this is a simplified version of your code.

private function pauseGame(e:Event = null):void {
    var collectible:Collectible;
    if (isPaused){
        btnPause.gotoAndStop(1);
        trace("game is active");
    } else {
        btnPause.gotoAndStop(2);
        trace("game is paused");
    }
    // I put the whole while loop outside since it would run either way.
    var r:int = numChildren -1 ;
    while ( r > -1 ) {
        var myMC:MovieClip = this.getChildAt(r) as MovieClip;
        var m:int = collectibles.length - 1;
        while ( m > -1 ) {
            collectible = collectibles[m];
            // one long if checking if all of the conditions are true.
            if (myMC == collectible.graphic && collectible.behaviourType == 3 && myMC.currentFrame >  && myMC.totalFrames > myMC.currentFrame ){
                trace("starting correct movieclips " +r);

            // This below here is a kind of a conditional statement.
            // statement ? ifStatementIsTrue : ifStatementIsFalse;
            //
            (isPaused) ? myMC.gotoAndPlay( myMC.currentFrame ) : myMC.gotoAndStop( myMC.currentFrame );
            m = -1;
        }
        m--;
    }
    r--;
}
isPaused = !isPaused;  // Reverse the game.isPaused boolean value

}

King October 8, 2010 at 11:52 pm

Is there a way to make it so a powerup only shows up once you get 1000 points? Sorry I don’t know I am new to actionscripting and flash.

People havent’t post… For awhile.

Mistakenness May 8, 2011 at 11:02 am

Hi !
I think power-ups in general have to last seconds, so I added one if statement in the big PixelPerfectCollision if statement :

if (avatar.width < 24.70 || avatar.height < 28.85)
                    {
                        var TenSecond:Timer = new Timer(500, 10);
                        TenSecond.addEventListener( TimerEvent.TIMER_COMPLETE, DrinkMePotion, false, 0, true );
                        TenSecond.start();
                    }

and I simple Function, just put it wherever you want :

public function DrinkMePotion( timerEvent:TimerEvent ):void
        {
            avatar.width = 24.70;
            avatar.height = 28.85;

    }

Obviously avatar width and height have to be changed to suite you needs !

I think this is not a very neat way of working. At this point, we should create it inside the Collectibles.as.

Leave a Comment

Writing code? Write <pre> at the start and </pre> at the end to keep it looking neat.

Anti-Spam Protection by WP-SpamFree

{ 1 trackback }

Previous post:

Next post: