Photo by Gaetan Lee.
When I originally converted FrozenHaddock’s AS2 tutorial to my AS3 version, I never got round to adding the golden skulls that he included. I’d like to make up for this omission now with this mini-series on adding collectible items to the game.
If you followed my tutorial, you should be able to add this feature to your own game with little trouble. Otherwise, feel free to download the zip file from the last part of the tutorial to use as a guide; if you’re familiar with AS3 it should be easy enough to understand how it all fits together.
We’ll start with simple coins that add points to your score. Click the image below to see how this will play.
[flash http://gamedev.michaeljameswilliams.com/flash/tutorials/AvoiderGame/Collectibles1/AvoiderGame-MJW.swf w=400 h=300 preview={/images/tutorials/AvoiderGame/Collectibles1/AvoiderGame_Collectibles1_05.png} mode=3]
###Collectibles are like Enemies
It wouldn’t be very difficult to change our enemies into collectibles; all we’d have to do is alter the code that gives us a game over when the avatar collides with an enemy to make it give us some extra points (and remove the enemy) instead. So we can start by copying the way our enemies work, as a base.
First, though, I’m just going to reduce the number of enemies that spawn so that I can concentrate on the collectibles while testing the game. To do this, I’ll open up the *LevelData.as* file and change the number of points required to advance beyond the first level, like so:
if ( levelNumber == 1 )
{
backgroundImage = "blue";
pointsToReachNextLevel = 999999;
enemySpawnRate = 0.05;
}
Right, now they won’t get in the way.
On to coins, then. The first thing to do is draw some sort of coin. As in Frozen Haddock’s tutorial, I’m going to use golden skulls. Create a new symbol and draw whatever you like:
As you can see, I’ve replaced the eyes and nose with *precious rubies*. Call this movie clip *GoldenSkull* (or whatever’s appropriate), and export it for ActionScript with the same name.
Now for some code. Open *AvoiderGame.as*. Just as we have an array, *army*, to store all the enemies, we’ll make a new array, *collectibles* to store all these treasures we’re going to create. I’ll rush through the next bit, as we’ve all done it before.
First, define it at the class level:
public class AvoiderGame extends MovieClip
{
public var gameClock:Clock;
public var gameScore:Score;
public var backgroundContainer:BackgroundContainer;
public var army:Array;
public var collectibles:Array;
Then initialise it in the constructor:
army = new Array();
collectibles = new Array();
Now we need to create the treasures. Here’s the code that generates the enemies:
if ( Math.random() < currentLevelData.enemySpawnRate )
{
var randomX:Number = Math.random() * 400;
var newEnemy:Enemy = new Enemy( randomX, -15 );
army.push( newEnemy );
addChild( newEnemy );
gameScore.addToValue( 10 );
sfxSoundChannel = enemyAppearSound.play();
}
I'll use a much simpler version (for now) for the golden skulls:
if ( Math.random() < 1/200 )
{
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 );
}
Note that we have to randomly generate a y-value this time, because there's no code to make the skull move; if we placed it above the top of the screen it'd just stay there. Also, we set the *x* and *y* values of *newCollectible* manually, rather then passing them through to its constructor, because, again, we don't have any code to manage it. Other than that, the code's much the same.
If you test it, you should get something that looks like this:
Perhaps the one-in-200 chance I set up for a skull appearing was too high... never mind, we'll deal with that later. Now we have to add the "collect" part.
Here's the code for avatar-enemy collision:
var i:int = army.length - 1;
var enemy:Enemy;
while ( i > -1 )
{
enemy = army[i];
enemy.moveABit();
if ( PixelPerfectCollisionDetection.isColliding( avatar, enemy, this, true ) )
{
gameTimer.stop();
avatarHasBeenHit = true;
}
if ( enemy.y > 350 )
{
removeChild( enemy );
army.splice( i, 1 );
}
i = i - 1;
}
Again, I'll use this as a base for avatar-collectible collision:
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 );
}
j = j - 1;
}
(Make sure you replace all your *i*s with *j*s or you'll see some odd behaviour.) Here I'm just adding 25 points whenever the player grabs a golden skull. You could play a sound too, but I'm not going to go into that here.
The above screenshot clearly proves that my code works -- otherwise, how would I have a score ending in 5? 😉
###Loops Within Loops
Let's up the stakes a bit. Suppose an enemy touches a collectible. At the minute, they just pass through it -- but what if they took points away from you instead? That'd make things a bit more interesting.
To do this, we have to check every single enemy against every single collectible to see if they are touching, unfortunately. Flash is more than powerful enough to handle this though.
What we need is a *nested loop* -- a *while* within another *while*. In this case, we can add a loop through the enemies within our existing loop through the collectibles:
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;
}
Only lines 206-217 are new here. I recommend adding a trace( i, j )
before line 209 to really see what's going on. Basically, we're re-using the *enemy* and *i* variables from above (in the avatar-enemy collision loop) in much the same way as before. If we have three collectibles and five enemies, we'll check to see whether collectible #3 is colliding with enemies #5, #4, #3, #2 or #1, then whether collectible #2 is colliding with enemies #5, #4, #3, #2 or #1, and finally whether collectible #1 is colliding with enemies #5, #4, #3, #2 or #1. In either case, if it's true, we destroy the collectible and reduce the player's score.
It'd be nice to move the avatar-enemy collision loop in here as well, but I think it's too tricky to be worth the effort.
Try the game out now. If you adjust the probability of a golden skull appearing, it's actually possible to get a negative score.
###Tweaking the Probability
We've got the enemy spawn rate tied in to the LevelData; it makes sense to do the same with the collectibles. Open up *LevelData.as*.
I think there are two things worth adding here: the maximum number of collectibles allowed on-screen at once, and the spawn rate for new collectibles. You could also add the points value for the skull; perhaps these should be more valuable as the game progresses. I'll leave that up to you, though.
Start by defining the new variables needed, as usual:
public class LevelData
{
public var backgroundImage:String;
public var pointsToReachNextLevel:Number;
public var enemySpawnRate:Number;
public var levelNum:Number;
public var collectibleSpawnRate:Number;
public var maxCollectiblesOnScreen:Number;
Now, put values in for these new variables. I'll show you the first pair that I selected; choose the rest yourself:
if ( levelNumber == 1 )
{
backgroundImage = "blue";
pointsToReachNextLevel = 999999;
enemySpawnRate = 0.05;
collectibleSpawnRate = 0.02;
maxCollectiblesOnScreen = 3;
}
(Remember to change that value from 999999 if you want to test this at higher levels!)
Of course, this won't have any effect until we alter the code in *AvoiderGame.as*, so open that class file. Earlier on we wrote this *if* statement:
if ( Math.random() < 1/200 )
{
var randomXCollectible:Number = 20 + Math.random() * 360;
var randomYCollectible:Number = 20 + Math.random() * 160;
To change this to our collectible spawning rate from LevelData, we just need to refer to that new variable:
if ( Math.random() < currentLevelData.collectibleSpawnRate )
{
var randomXCollectible:Number = 20 + Math.random() * 360;
var randomYCollectible:Number = 20 + Math.random() * 160;
What about for altering the maximum number of skulls on screen? Well, remember that we can use *collectibles.length* to see how many items are in that array -- this will be the same as the number of skulls on screen, since skulls are on screen if and only if they are in that array. So, we can just avoid adding any new skulls unless we're below the limit.
if ( collectibles.length < currentLevelData.maxCollectiblesOnScreen )
{
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 );
}
}
Earlier on we used a nested loop, and this is a nested *if*. Think of nested tables, rather than a bird's nest.
Try it out!
[flash http://gamedev.michaeljameswilliams.com/flash/tutorials/AvoiderGame/Collectibles1/AvoiderGame-MJW.swf w=400 h=300 preview={/images/tutorials/AvoiderGame/Collectibles1/AvoiderGame_Collectibles1_05.png} mode=3]
###Wrapping Up
This simple little addition has already made the gameplay much more interesting. With the right balance of point values and spawn rates, and a decent difficulty curve, the game could be pretty fun even in this simple state. Sadly, that's the tough part 😉
You can grab the zip of all the files from this tutorial here. Like I said, this is a mini-series, so we'll extend upon this soon. In the meantime, have a play around with it, and see what simple changes are the most effective. If you trigger a sound upon collection, is this fun, or just irritating? What about if the collectibles move about? Move away from you? Let me know what you find out in the comments!
{ 13 comments… read them below or add one }
Hi.
I wonder how you make so the collectible spawnrate continue to increase?
Like we do with thw enemies and level :
Hey Richard. Should be possible to use almost exactly the same code, like this:
I haven’t tested that, so please let me know if there are any bugs 🙂
That code worked great!
So stupid i am for not thinking that one out myself. I had to decrease the spawning rate a little bit but it’s wotking fine.
Good good 🙂
Is there any free program like cs4? i tried flash develop that you mentioned but i cannot seem edit and create .flas in there. Is it only i that have missed it or doesn’t it work?
Sorry for my bad english.
I don’t know of any other programs that let you create FLA files (though there might be some) but there are other ways of making Flash games without actually having Flash.
I don’t know much about these methods, but check out these links:
http://www.brighthub.com/internet/web-development/articles/11010.aspx
http://www.scriptedfun.com/make-a-flash-game-for-free/
http://www.dreamincode.net/forums/showtopic92205.htm
I hope they’re helpful.
If you can wait a little longer, the PushButton Engine will be released soon: http://pushbuttonengine.com/ — take a look 🙂
i’m getting an Error:
The game is working and stuff but, i don’t want my game to be filled with errors 😛
Is it just me or Is the comment thing here kind of… Anoying?? since it keep on deleting half of my comment:?!
It’s deleting half your comment? Dang. I’ve never seen that before. Uh, which half?
Warnings often don’t matter very much, but I understand wanting to have everything clean 🙂
The warning you’re getting just means that somewhere in your code, you have written
var someVariable
twice for the same variable inside the same function. Any idea where?the deleting is inside codes. It just changes after i sumbit the comment, like i wrote this on first try
But instead i ended up with this
its kind of wird..
And to the error;
I found the Var. it was the var j:int
it was both on
and:
so i just changed the J in the bullet var to k instead and the warning was gone 😛
Ah, I think my spam detection is a little overzealous; it assumes that if you’re writing loads of code you must be trying to h4ck t3h internetz or something.
Hey, great work finding and fixing that bug!
I need help there is a problem which is coming from the “PixelPerfectCollisionDetection”,
whenever a coin spawns or the avatar collides with an enemy it sends a wall of these errors:
When a coin spawns the game lags horribly and the avatar can’t pick it up and when he dies there’s a short stop before the GameOverScreen appears where it sends these errors.
I know it’s coming from the collision detection because this never happened until I added it.
Help would be appreciated, thanks.
Ah, the classic Error #1009.
The problem is that it can occur in almost any situation. Have you removed the playScreen’s event listeners before the gameOverScreen appears?
ExplosionsHurt, no I have tested the game without PixelPerfectCollisionDetection, which somehow removes the problem.
Which AS file?
{ 1 trackback }