Add Power-Ups to Your AS3 Avoider Game

by Michael Williams on April 22, 2009 · 9 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 :)

{ 1 trackback }

AS3: Enemies, Part 1 | Avoider Game . com
April 29, 2009 at 12:30 am

{ 8 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 :)

Leave a Comment

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

CommentLuv Enabled

Anti-Spam Protection by WP-SpamFree

Previous post:

Next post: