Add More Collectibles to Your AS3 Avoider Game

by Michael James Williams on April 15, 2009 · 25 comments

in Avoider Game Extras

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

Foreign currency and coins
Photo by bradipo.

Well, I think that’s the best blog post title I ever came up with. As it implies, this is the second part of a mini-series, the first being Add Collectibles to Your AS3 Avoider Game. If you haven’t read that yet, do so before reading on.

In this part of my Collectibles mini-series, we’ll favour object composition over class inheritance to add new types of coins in a much more flexible manner. Click the image below to try out the result:

Snapshot_O01.png

Zip file. Download. Open FLA. Let’s get started.

What is Composition?

Suppose we want to add a new type of collectible to our game. It’ll be another simple “coin”, and will act just the same as the golden skull we already added, except it’ll be worth twice as many points. How can we do that?

So far, I’ve talked a lot about class inheritance — that is, using the extends, override and super keywords to create new classes that inherit the variables and functions of existing classes. In the same vein, we could create a new class — CrystalSkull PlatinumSkull, let’s say — and have it extend GoldenSkull, but make it award a different number of points.

This would certainly work, but it will lead to problems later on. If we change GoldenSkull, PlatinumSkull will change too, and this might mean it stops working. If we add another new collectible that acts completely differently to GoldenSkull, like a power-up, then it’ll inherit all this stuff from GoldenSkull that it just doesn’t need. Messy.

We can improve the situation by making a new class, Collectible, that GoldenSkull itself extends, and making this the base for all new collectibles. We’ll still end up with a mess of classes as we add new items, though — for more on why, check out this post over at the AS3 Design Patterns blog.

A better choice here is to use composition. Let me explain this by example.

First, we’ll create a Collectible class. Yes, I know I implied this was a bad thing, but we’re not going to extend it, so it’ll be OK.

?View Code ACTIONSCRIPT3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package
{
	import flash.display.DisplayObject;
 
	public class Collectible
	{
		public var graphic:DisplayObject;
		public var collectibleType:String;
		public var baseValue:Number;
 
		public function Collectible( type:String )
		{
			collectibleType = type;
			if ( collectibleType == "GoldenSkull" )
			{
				graphic = new GoldenSkull();
				baseValue = 25;
			}
		}
	}
}

It’s a very simple class, but there are a couple of things worth noting:

  1. It does not extend any other class — it doesn’t need to, though this might change later.
  2. It contains a DisplayObject but does not addChild() it — again, it doesn’t need to.

Let’s get this working with our current AvoiderGame.as code before taking it any further. The first thing to alter is the code that creates a new collectible. Change this:

?View Code ACTIONSCRIPT3
140
141
142
143
144
145
146
147
148
149
if ( Math.random() < currentLevelData.collectibleSpawnRate )
{
	var randomXCollectible:Number = 20 + Math.random() * 360;
	var randomYCollectible:Number = 20 + Math.random() * 160;
	var newCollectible:GoldenSkull = new GoldenSkull();
	newCollectible.x = randomXCollectible;
	newCollectible.y = randomYCollectible;
	collectibles.push( newCollectible );
	addChild( newCollectible );
}

to this:

?View Code ACTIONSCRIPT3
140
141
142
143
144
145
146
147
148
149
if ( Math.random() < currentLevelData.collectibleSpawnRate )
{
	var randomXCollectible:Number = 20 + Math.random() * 360;
	var randomYCollectible:Number = 20 + Math.random() * 160;
	var newCollectible:Collectible = new Collectible( "GoldenSkull" );
	newCollectible.graphic.x = randomXCollectible;
	newCollectible.graphic.y = randomYCollectible;
	collectibles.push( newCollectible );
	addChild( newCollectible.graphic );
}

It’s almost exactly the same. The most important difference is that now, newCollectible is not a DisplayObject and so can’t be added to the screen. Instead, all the graphics-related functions have to act on newCollectible.graphic, which holds the actual GoldenSkull movie clip.

With this in mind, let’s change the other big chunk of code referring to the collectible:

?View Code ACTIONSCRIPT3
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
var j:int = collectibles.length - 1;
var collectible:GoldenSkull;
while ( j > -1 )
{
	collectible = collectibles[j];
	if ( PixelPerfectCollisionDetection.isColliding( avatar, collectible, this, true ) ) 
	{
		removeChild( collectible );
		collectibles.splice( j, 1 );
		gameScore.addToValue( 25 );
	}
 
	i = army.length - 1;
	while ( i > -1 )
	{
		enemy = army[i];
		if ( PixelPerfectCollisionDetection.isColliding( collectible, enemy, this, true ) ) 
		{
			removeChild( collectible );
			collectibles.splice( j, 1 );
			gameScore.addToValue( -25 );
		}
		i = i - 1;
	}
 
	j = j - 1;
}

becomes:

?View Code ACTIONSCRIPT3
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
var j:int = collectibles.length - 1;
var collectible:Collectible;
while ( j > -1 )
{
	collectible = collectibles[j];
	if ( PixelPerfectCollisionDetection.isColliding( avatar, collectible.graphic, this, true ) ) 
	{
		removeChild( collectible.graphic );
		collectibles.splice( j, 1 );
		gameScore.addToValue( collectible.baseValue );
	}
 
	i = army.length - 1;
	while ( i > -1 )
	{
		enemy = army[i];
		if ( PixelPerfectCollisionDetection.isColliding( collectible.graphic, enemy, this, true ) ) 
		{
			removeChild( collectible.graphic );
			collectibles.splice( j, 1 );
			gameScore.addToValue( -collectible.baseValue );
		}
		i = i - 1;
	}
 
	j = j - 1;
}

Again, anything dealing with the actual image of the collectible — that is, the calls to removeChild() and PixelPerfectCollisionDetection.isColliding() — now refer to the graphic property of collectible. Also, I’ve let the points value of the skull be defined by the baseValue property.

These changes are enough to make the game run as it did before:

Snapshot_R01.png

Composition, then, is about breaking a class down into separate pieces that can be altered individually. In this case, the image of the collectible is completely separate to its behaviour.

Adding a New Collectible

Now we can use this to easily add our new collectible. Create a new symbol of type movie clip, call it PlatinumSkull (or whatever’s suitable for your game) and export it for ActionScript. Draw an image to match the name:

screenshot

It’s supposed to be encrusted with… whatever the green gems are called, but it just looks kinda wrinkly. Eh, it’s late.

Adding this to our Collectible class is simple:

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

That’s all there is to it. As you can see, it’d be easy to add as many more types as you like. However, they won’t be created yet because we’re always passing “GoldenSkull” through as a parameter to this class in AvoiderGame.as. So let’s fix that. Change this line:

?View Code ACTIONSCRIPT3
144
var newCollectible:Collectible = new Collectible( "GoldenSkull" );

to this:

?View Code ACTIONSCRIPT3
144
145
146
147
148
149
150
151
152
var newCollectible:Collectible;
if ( Math.random() < 0.8 )
{
	newCollectible = new Collectible( "GoldenSkull" );
}
else
{
	newCollectible = new Collectible( "PlatinumSkull" );
}

We could improve this, perhaps by adjusting the probabilities based on the ratio of platinum/golden skulls currently on-screen. Alternatively, we could handle the random selection process inside the Collectible class itself. Let’s leave it simple for now, though.

Snapshot_O01.png

Of course, if you want to play a different sound depending on which item was collected, you can create and store the sound inside the instance of Collectible, just as you do with the image.

Wrapping Up

This is a very simple example of composition, but I hope you can see how much neater it can make the code. You might also have noticed that it’s very similar to the way the LevelData class is structured.

The zip file with all relevant classes and the FLA is available here.

In the next part of this series, we’ll add power-ups, both to give your avatar a fair advantage against the hordes of enemies, and to give the enemies an unfair advantage against the avatar ;)

{ 24 comments… read them below or add one }

Mushyrulez April 15, 2009 at 5:07 am

You certainly gave me a new idea there :P

Thanks for the tut, can’t wait for powerups :D

MichaelJWilliams April 15, 2009 at 10:25 am

Hehe, I thought you might find it especially relevant ;)

kay bouble yoo April 16, 2009 at 4:29 am

awesome tutorial.
just wondering if there is a was to connect the enemies with the collectibles, essentially making enemies collectible. for example if your avatar was able to shoot at the enemies, how would you remove the enemy with the pixel detection?
thanks
kw

MichaelJWilliams April 16, 2009 at 1:30 pm

Thanks. And cool idea. I think the simplest way would be to create a new class, called Bullet, with a picture of, er, a bullet.

Give it a function called moveABit(), just like the Enemy class has, that does something like y=y-1;.

When the player presses space, let’s say, create a new instance of the class and add it to a new class-level array that you create called bullets.

Every tick, run the moveABit() function of every bullet, just as you do for the enemies. This will involve making a new loop to go through the bullets array.

Then add a new nested loop that checks everything in the bullets array against everything in the army array to see if there’s a collision, just as we check every enemy against every collectible. If there is a collision, remove both the bullet and the enemy. Also add some points.

Is that clear enough? Let me know if not. I might write up a full tutorial on this later (or perhaps someone else will).

TCrazy June 22, 2009 at 2:35 am

i think my problem is simple. how can i put 5 different objects back to back on a screen. (for example: show a orange collectible, then a yellow collectible, then a green collectible, then a blue collectible, then a purple collectible.) i only want 5 different collectibles to appear on each different background. i got two colors (blue, and purple) to appear back to back. but the other colors dont show up. here is my code:

if ( Math.random() < 0.8 )
{
    newCollectible = new Collectible( "OrangeOutfit" );
}
else
{
    newCollectible = new Collectible( "YellowOutfit" );
}
if ( Math.random() < 0.8 )
{
    newCollectible = new Collectible( "YellowOutfit" );
}
else
{
    newCollectible = new Collectible( "GreenOutfit" );
}
if ( Math.random() < 0.8 )
{
    newCollectible = new Collectible( "GreenOutfit" );
}
else
{
    newCollectible = new Collectible( "BlueOutfit" );
}
if ( Math.random() < 0.8 )
{
    newCollectible = new Collectible( "BlueOutfit" );
}
else
{
    newCollectible = new Collectible( "PurpleOutfit" );
}

i should have six different outfits pop up one at a time right after each other.

Michael Williams June 22, 2009 at 12:45 pm

Hey TCrazy.

You’re going to need to create a new variable to keep track of which colour collectibles have already been dropped.

So, add something like public var collectiblesCreated:int at the class level, and set it to zero in the constructor (or at the start of a new level).

Then, in the onTick() function, alter your above code to something like this:

newCollectible = null;
if ( Math.random() < 0.8 )
{
    if ( collectiblesCreated == 0 )
    {
        newCollectible = new Collectible( "OrangeOutfit" );
    }
    else if ( collectiblesCreated == 1 )
    {
        newCollectible = new Collectible( "YellowOutfit" );
    }
    // other colours of collectibles here
}
if ( newCollectible != null )
{
    // add the new collectible to the array and the screen
    collectiblesCreated = collectiblesCreated + 1;    //don't forget this!
}

Something like that?

TCrazy June 23, 2009 at 4:20 am

what is this part for??? im lost.

if ( newCollectible != null )
{
// add the new collectible to the array and the screen
}

Michael Williams June 23, 2009 at 3:26 pm

Ah, OK. Quick note: I changed my code very slightly since you posted, it’s a little neater now.

Well, see how the first line of my code sets newCollectible to null? It only gets set to something other than null if Math.random() comes out low enough to spawn a new item. On top of that, the collectiblesCreated variable must have a particular collectible associated with it — like 0 has OrangeOutfit and 1 has YellowOutfit.

So when Flash reaches the if ( newCollectible != null ) statement, you know this can only be run if the above conditions have been met; that is, if Math.random() came out low enough and collectiblesCreated had an associated outfit.

Furthermore, you know that at this point, newCollectible will actually be an OrangeOutfit or a YellowOutfit or whatever. So then you can do all your code like:

collectibles.push( newCollectible );
newCollectible.x = [whatever];
newCollectible.y = [whatever];
addChild( newCollectible );

Then, and only then, collectiblesCreated is increased, so that next time this code is run it can work out which outfit to create.

TCrazy June 23, 2009 at 11:10 pm

okay this is the code i wrote in Avoidergame.as :

if ( collectiblesCreated.length < currentLevelData.maxCollectiblesOnScreen )
{
if ( Math.random() < currentLevelData.collectibleSpawnRate )
{
var randomXCollectible:Number = 1 + Math.random() * 360;
var randomYCollectible:Number = 1 + Math.random() * 160;
var newCollectible:Collectible;

                    newCollectible = null;
                    if ( collectiblesCreated == 0 )
                    {
                            newCollectible = new Collectible( "OrangeOutfit" );
                    }
                    else if ( collectiblesCreated == 1 )
                    {
                            newCollectible = new Collectible( "YellowOutfit" );
                    }
                    if ( collectiblesCreated == 1 )
                    {
                            newCollectible = new Collectible( "YellowOutfit" );
                    }
                    else if ( collectiblesCreated == 2 )
                    {
                            newCollectible = new Collectible( "GreenOutfit" );
                    }
                    if ( collectiblesCreated == 2 )
                    {
                            newCollectible = new Collectible( "GreenOutfit" );
                    }
                    else if ( collectiblesCreated == 3 )
                    {
                            newCollectible = new Collectible( "BlueOutfit" );
                    }
                    if ( collectiblesCreated == 3 )
                    {
                            newCollectible = new Collectible( "BlueOutfit" );
                    }
                    else if ( collectiblesCreated == 4 )
                    {
                            newCollectible = new Collectible( "PurpleOutfit" );
                    }
                    // other colours of collectibles here
                    collectiblesCreated = collectiblesCreated + 1;    //don't forget this!
                    if ( newCollectible != null )
                    {
                    // add the new collectible to the array and the screen <- what is this?
                    }
                    newCollectible.x = randomXCollectible;
                    newCollectible.y = randomYCollectible;
                    collectibles.push( newCollectible );
                    addChild( newCollectible );
                }
        }

And this is the code i have in my Collectible.as :

public class Collectible
{
public var graphic:DisplayObject;
public var collectibleType:String;
public var baseValue:Number;

    public function Collectible( type:String )
    {
        collectibleType = type;
        if ( collectibleType == "OrangeOutfit" )
        {
            graphic = new OrangeOutfit();
            baseValue = 10;
        }

    else if ( collectibleType == "YellowOutfit" )
    {
        graphic = new YellowOutfit();
        baseValue = 10;
    }

    else if ( collectibleType == "GreenOutfit" )
    {
        graphic = new GreenOutfit();
        baseValue = 10;
    }

    else if ( collectibleType == "BlueOutfit" )
    {
        graphic = new BlueOutfit();
        baseValue = 10;
    }

    else if ( collectibleType == "PurpleOutfit" )
    {
        graphic = new PurpleOutfit();
        baseValue = 10;
    }

When i try to test the game it says:

1176: Comparison between a value with static type Array and a possibly unrelated type int. if ( collectiblesCreated == 0 )
1176: Comparison between a value with static type Array and a possibly unrelated type int. else if ( collectiblesCreated == 1 )
1176: Comparison between a value with static type Array and a possibly unrelated type int. if ( collectiblesCreated == 1 )
1176: Comparison between a value with static type Array and a possibly unrelated type int. else if ( collectiblesCreated == 2 )
1176: Comparison between a value with static type Array and a possibly unrelated type int. if ( collectiblesCreated == 2 )
1176: Comparison between a value with static type Array and a possibly unrelated type int. else if ( collectiblesCreated == 3 )
1176: Comparison between a value with static type Array and a possibly unrelated type int. if ( collectiblesCreated == 3 )
1176: Comparison between a value with static type Array and a possibly unrelated type int. else if ( collectiblesCreated == 4 )

1119: Access of possibly undefined property x through a reference with static type Collectible. newCollectible.x = randomXCollectible;
1119: Access of possibly undefined property x through a reference with static type Collectible. newCollectible.y = randomYCollectible;

1120: Access of undefined property collectibles. collectibles.push( newCollectible );
1120: Access of undefined property collectibles. collectiblesCreated = collectibles[j];

1067: Implicit coercion of a value of type Collectible to an unrelated type flash.display:DisplayObject. addChild( newCollectible );

What did I do wrong???

TCrazy June 23, 2009 at 11:25 pm

oka i fixed alot of my errors.. i forgot to add this detail below:

public var collectiblesCreated:int

but im still getting these errors:

1119: Access of possibly undefined property x through a reference with static type Collectible. newCollectible.x = randomXCollectible;
1119: Access of possibly undefined property x through a reference with static type Collectible. newCollectible.y = randomYCollectible;
1119: Access of possibly undefined property x through a reference with static type Collectible. if ( collectiblesCreated.length < currentLevelData.maxCollectiblesOnScreen )
1119: Access of possibly undefined property x through a reference with static type Collectible. var j:int = collectiblesCreated.length – 1;

1120: Access of undefined property collectibles. collectibles.push( newCollectible );
1120: Access of undefined property collectibles. collectiblesCreated = collectibles[j];

1061: Call to a possibly undefined method splice through a reference with static type int. collectiblesCreated.splice( j, 1 );

1067: Implicit coercion of a value of type Collectible to an unrelated type flash.display:DisplayObject. addChild( newCollectible );

Michael Williams June 24, 2009 at 12:57 am

Sounds like you’ve got a couple of variables mixed up; you seem to be using collectiblesCreated as an int sometimes and an array at other times.

Also, Collectible should extend MovieClip — that’s why it can’t find values like x and y.

richard January 23, 2010 at 4:17 pm

I’m having som problems here.

Sometimes when there is more than one collectible on the screen I have to collect them in a specific order. you have any idea why?

Michael Williams January 26, 2010 at 7:33 am

Hey Richard, that’s peculiar. Any idea what the order is? Is it the order in which they appeared on the screen — or perhaps reverse order of that?

richard January 26, 2010 at 4:18 pm

I think i was wrong about that ordert thing. It happens when i played some time and there are 2 or more collectibles on the screen. Then i have to go through it a few times and then it works. Maybie flash doesn’t have the time to “see” that i collect it?

Michael Williams January 28, 2010 at 5:17 am

Hmm… that could be happening if your collectibles are small and your avatar moves fast. It’s like the avatar “jumps” over the collectible between frames. Is that the case?

richard January 28, 2010 at 5:46 pm

but my avatar is smaller than the collectibels so could it be the other way around?

Michael Williams January 31, 2010 at 4:47 am

Possibly. Is your SWF online somewhere? (If not, you could put it on Newgrounds Dumping Ground — my username is MichaelJamesWilliams)

Joe April 13, 2010 at 5:21 pm

Is there a way you could show me how to make this class, Collectible, into an enemy class that allows different kinds of enemies to appear?

Michael Williams April 23, 2010 at 12:54 pm

Hey Joe,

Sure, I’ll give you a hand. What’ve you tried so far?

Raveren May 30, 2010 at 1:23 pm

Hi Michael, I have problem ;/ I want to make that second collectible, I did all the tutorial and now I have only error ’1009′ when flash wants to add coin. Can You look at this?
AvoiderGame.as:

if ( collectibles.length < currentLevelData.maxCollectiblesOnScreen )
            {
                if ( Math.random() < currentLevelData.collectibleSpawnRate )
                {
                    var randomXCollectible:Number = 20 + Math.random() * 760;
                    var randomYCollectible:Number = 20 + Math.random() * 360;
                    var newCollectible:Collectible;
                    if ( Math.random() < 0.8 )
                    {
                        newCollectible = new Collectible( "Coin" );
                    }
                    else
                    {
                        newCollectible = new Collectible( "PlatinumCoin" );
                    }
                    newCollectible.graphic.x = randomXCollectible;
                    newCollectible.graphic.y = randomYCollectible;
                    collectibles.push( newCollectible );
                    addChild( newCollectible.graphic );
                }
            }

second part:

var j:int = collectibles.length - 1;
            var collectible:Collectible;
            while ( j > -1 )
            {
                collectible = collectibles[j];
                if ( PixelPerfectCollisionDetection.isColliding( avatar, collectible.graphic, this, true ) ) 
                {
                    removeChild( collectible.graphic );
                    collectibles.splice( j, 1 );
                    gameScore.addToValue(collectible.baseValue);
                }
                j = j - 1;

            i = army.length - 1;
            while ( i > -1 )
            {
                enemy = army[i];
                if ( PixelPerfectCollisionDetection.isColliding( collectible.graphic, enemy, this, true ) ) 
                {
                    removeChild( collectible.graphic );
                    collectibles.splice( j, 1 );
                    gameScore.addToValue(collectible.baseValue * -1);
                }
                i = i - 1;
            }
        }

And the Collectible.as:

package
{
    import flash.display.DisplayObject;

public class Collectible
{
    public var graphic:DisplayObject;
    public var collectibleType:String;
    public var baseValue:Number;

public function Collectible( type:String )
{

    collectibleType = type;
    if ( collectibleType == "Coin" )
    {
        graphic = new Coin();
        baseValue = 5;

    }
    else if ( collectibleType == "PlatinumCoin" )
    {
        graphic = new PlatinumCoin();
        baseValue = 10;
    }
}

}

}

Btw, great work with tutorials! I’ve nearly made my game :)

Raveren May 30, 2010 at 1:47 pm

Nevermind, I got it. I even dont know how but its working now ;>

Michael Williams June 12, 2010 at 1:32 pm

Congrats, Raveren :) What’s your game about?

Jonathan October 22, 2010 at 9:22 pm

Hello,

I was wondering if this the right approach for having multiple “skins” for the enemies or whether it’s better to have other enemy “skins” classes extend a basic enemies class.

I’m really enjoying the articles.

Mark January 14, 2011 at 2:38 pm

Hi Michael, I have a similar question to Jonathan above – lets say you only want to change the actual appearance of a collectible or enemy and keep all the code exactly the same – is there a way to do this in AS3 without using object composition or class extending?

The main issue I see with the composition method is the way in which sprites are created – in the above example a platinum skull is created when the random number is >=0.8 – but what if you have 20 or more sprites (just for variation) – an if else statement could get long and messy. Is there a way of saying to flash “When i create an enemy i want you to pick a movieclip from this list of movieclips in the library i’ve identified as enemies”.

Thanks,

Mark

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: