I’m getting a bit tired of running away from these enemies without having any means of defending myself.
Let’s blast them.
Click to play. [Space] to shoot.
This tutorial is based on my avoider game tutorial. If you followed that, you’ll be able to add these new bullets to your existing game easily. If not, don’t worry about it — just download the base zip file; it contains all the files I’m going to be using.
###Space Invaders Shooting
Let’s start with the simplest style of shooting: press space to fire a bullet upwards.
We might as well start by drawing the bullet. Open the FLA and create a new symbol of type Movie Clip (I’m sure you know the drill by now). Call it Bullet
and export it for ActionScript with the same class name.
I’ve drawn this very simple shape for mine:
This is zoomed in, of course! A rectangle would look fine.
Time for some code. Open *AvoiderGame.as*. The first thing we’re going to do is get it recognising the space bar.
###Listening for the Space Bar
Remember, there are four places we need to change to allow the space bar to be recognised.
First, we need to define a new Boolean at the class level:
public var downKeyIsBeingPressed:Boolean;
public var upKeyIsBeingPressed:Boolean;
public var leftKeyIsBeingPressed:Boolean;
public var rightKeyIsBeingPressed:Boolean;
public var spaceBarIsBeingPressed:Boolean;
Next, we have to set that Boolean to false
in the constructor:
downKeyIsBeingPressed = false;
upKeyIsBeingPressed = false;
leftKeyIsBeingPressed = false;
rightKeyIsBeingPressed = false;
spaceBarIsBeingPressed = false;
Finally, we have to set that boolean to either true
or false
in the onKeyPress()
and onKeyRelease()
functions. Here’s onKeyPress()
:
public function onKeyPress( keyboardEvent:KeyboardEvent ):void
{
if ( keyboardEvent.keyCode == Keyboard.DOWN )
{
downKeyIsBeingPressed = true;
}
else if ( keyboardEvent.keyCode == Keyboard.UP )
{
upKeyIsBeingPressed = true;
}
else if ( keyboardEvent.keyCode == Keyboard.LEFT )
{
leftKeyIsBeingPressed = true;
}
else if ( keyboardEvent.keyCode == Keyboard.RIGHT )
{
rightKeyIsBeingPressed = true;
}
else if ( keyboardEvent.keyCode == Keyboard.SPACE )
{
spaceBarIsBeingPressed = true;
}
}
I’ll leave you to figure out the code for onKeyRelease()
.
OK, now we can detect the space bar, it’s time to make it do something. First, we can just make it create a new bullet. Move to the point in the onTick()
function, after the avatar gets moved but before any collision detection takes place, and add these lines:
if ( spaceBarIsBeingPressed )
{
var newBullet = new Bullet();
newBullet.x = avatar.x;
newBullet.y = avatar.y;
addChild( newBullet );
}
Click to play
Well… it’s a start. If you hold down space, you can draw pictures. Er, let’s get those bullets moving.
###Getting the Bullets Moving
We might as well copy the way the enemies move for this. Remember how that works?
– All enemies are placed in an array, army
– Every tick, we loop through that array and call moveABit()
on each enemy
– moveABit()
changes the enemy’s position slightly (in my code, it just increases y)
So first we need a bullets
array. Add public var bullets:Array;
to the rest of the public variables, and put bullets = new Array();
in the constructor function, just as we did with army
.
Next we need to *push* the new bullet we created in the code above into our new array:
if ( spaceBarIsBeingPressed )
{
var newBullet = new Bullet();
newBullet.x = avatar.x;
newBullet.y = avatar.y;
addChild( newBullet );
bullets.push( newBullet );
}
Great, now we have an array of bullets. Time to loop through them.
###Looping Through the Bullets
I know a lot of people have difficulty with arrays. Just remember, they’re like a numbered list. We move through the list backwards because certain problems would occur if we didn’t.
Here’s the important part of the army loop:
var i:int = army.length - 1;
var enemy:Enemy;
while ( i > -1 )
{
enemy = army[i];
enemy.moveABit();
i = i - 1;
}
If there are ten enemies, they will take up army[0]
through army[9]
, so we start at army[9]
and keep going backwards through the list — that’s what i = i - 1
does. We only do this while the number of the enemy that we’re looking at is more than -1; army[-1]
doesn’t refer to anything, so we’d get an error if we tried to use it.
Here’s the code we’ll use for the bullets:
var j:int = bullets.length - 1;
var bullet:Bullet;
while ( j > -1 )
{
bullet = bullets[j];
bullet.moveABit();
j = j - 1;
}
(I’ve put this after we create the new bullet, but before we do any collision detection.)
Note that I’m using j
to move through this loop instead of i
. You don’t have to do this; I just think it’s less confusing.
OK, just one more thing we need to do: create the bullet.moveABit()
function. Because this code is so similar to the Enemy’s, I’m just going to paste it here for you to copy:
package
{
import flash.display.MovieClip;
public class Bullet extends MovieClip
{
public var speed:Number; //pixels moved per tick
public function Bullet()
{
speed = 3;
}
public function moveABit():void
{
y = y - speed;
}
}
}
Save that as *Bullet.as*. Run the game now, and here’s what you’ll get:
Click to play
That’s better, but we still need to build in collision detection, and slow down the rate of fire.
###Bullet-Enemy Collision Detection
We’ve got lots of bullets and lots of enemies, and we need to check whether *any* bullet hits *any* enemy. How?
If you read my mini-series on collectibles, you’ll already know. If not, I recommend thinking about it before reading on.
The answer is *nested loops*.
Rather than checking all of the bullets against all of the enemies, we check all of the bullets against *one* of the enemies, and then check all of the bullets against the *next* enemy, then the next, and so on.
Here’s a simplified example:
var i:int = army.length - 1;
var enemy:Enemy;
while ( i > -1 )
{
enemy = army[i];
enemy.moveABit();
j = bullets.length - 1;
while ( j > -1 )
{
bullet = bullets[j];
//if bullet collides with enemy, destroy them both
j = j - 1;
}
i = i - 1;
}
(Note: I haven’t written var j
or var bullet
because those are already defined earlier in the function.)
If that’s not clear, grab a piece of paper and a pencil and work through it, imagining that there are five enemies and three bullets, and bullet[1]
is colliding with enemy[3]
.
OK, now we’ve got the basic idea, it’s time to actually plug this code into our existing enemy movement loop. Sure, we could write another loop just to handle enemy-bullet collisions, but that would be wasteful, and will slow things down.
So, here’s my existing enemy movement loop:
var avatarHasBeenHit:Boolean = false;
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;
}
…and here’s my first attempt at enemy-bullet collision detection:
var avatarHasBeenHit:Boolean = false;
var i:int = army.length - 1;
var enemy:Enemy;
while ( i > -1 )
{
enemy = army[i];
enemy.moveABit();
//new code starts here
j = bullets.length - 1;
while ( j > -1 )
{
bullet = bullets[j];
if ( bullet.hitTestObject( enemy ) )
{
removeChild( enemy );
army.splice( i, 1 );
removeChild( bullet );
bullets.splice( j, 1 );
}
j = j - 1;
}
//new code stops here
if ( PixelPerfectCollisionDetection.isColliding( avatar, enemy, this, true ) )
{
gameTimer.stop();
avatarHasBeenHit = true;
}
if ( enemy.y > 350 )
{
removeChild( enemy );
army.splice( i, 1 );
}
i = i - 1;
}
(I’ve used hitTestObject()
, even though it’s not very accurate, because it’s much faster than PixelPerfectCollisionDetection
.)
This does actually work, the bullets destroy the enemies and themselves, but error messages appear in the Output panel. The problem is, after an enemy gets destroyed by a bullet (removing it from the screen and the array), we still try to use it in the following bit of code (checking for a collision with the avatar, and to see whether it’s moved off the bottom of the screen).
To get around this, we can use the same technique we used for the avatar — the avatarHasBeenHit
boolean:
var avatarHasBeenHit:Boolean = false;
var enemyHasBeenHit:Boolean;
var i:int = army.length - 1;
var enemy:Enemy;
while ( i > -1 )
{
enemy = army[i];
enemy.moveABit();
enemyHasBeenHit = false; //new code
j = bullets.length - 1;
while ( j > -1 )
{
bullet = bullets[j];
if ( bullet.hitTestObject( enemy ) )
{
enemyHasBeenHit = true; //new code
removeChild( bullet );
bullets.splice( j, 1 );
}
j = j - 1;
}
if ( enemyHasBeenHit ) //new code
{
removeChild( enemy ); //new code
army.splice( i, 1 ); //new code
}
else //new code
{
if ( PixelPerfectCollisionDetection.isColliding( avatar, enemy, this, true ) )
{
gameTimer.stop();
avatarHasBeenHit = true;
}
if ( enemy.y > 350 )
{
removeChild( enemy );
army.splice( i, 1 );
}
}
i = i - 1;
}
(I’ve written //new code
throughout the code to make it easier to see what’s changed.)
So now we’re only checking for enemy-avatar collisions, and whether the enemy’s left the screen, if the enemy hasn’t already been hit by a bullet. It’s still not perfect, because we’ve got the same code in two separate parts of the loop (removing the enemy after it’s been hit by a bullet or left the screen) — can you see a way to improve that?
Check out the game so far:
Click to play
Getting better!
###Slowing Down the Rate of Fire
Does the constant stream of bullets look familiar to you?
It’s actually the same problem we had way back in Part 2 of the base tutorial, when a new enemy was being created every tick.
Back then we fixed it by having a one-in-ten chance of an enemy being created each tick. But applying this same “random” approach to how often the player can fire is just going to be frustrating.
We need to make it more exact: the player can only shoot if at least ten ticks have passed since his last shot.
Hey, that’s basically an if statement! if ( ticksSinceLastShot >= 10 )
Let’s work that in. First, we’ll need a public var ticksSinceLastShot:int
(remember, an int
is just a whole number). Set ticksSinceLastShot = 0;
in the *AvoiderGame* constructor function. Also, in the onTick()
function, put:
ticksSinceLastShot = ticksSinceLastShot + 1;
Now we can use our *if* statement from above:
if ( spaceBarIsBeingPressed )
{
if ( ticksSinceLastShot >= 10 )
{
var newBullet = new Bullet();
newBullet.x = avatar.x;
newBullet.y = avatar.y;
addChild( newBullet );
bullets.push( newBullet );
}
}
Great! Try it out:
…what? Oh! We need to reset the ticksSinceLastShot
variable after we’ve, er, shot:
if ( spaceBarIsBeingPressed )
{
if ( ticksSinceLastShot >= 10 )
{
var newBullet = new Bullet();
newBullet.x = avatar.x;
newBullet.y = avatar.y;
addChild( newBullet );
bullets.push( newBullet );
ticksSinceLastShot = 0;
}
}
Try it out:
Click to play
Excellent.
###Odds and Ends
There are a few small changes I’d like to make, but I’ve covered them all in earlier tutorials so I won’t go into much detail.
First, for garbage collection purposes, we need to remove the bullets once they get above the top of the screen:
var j:int = bullets.length - 1;
var bullet:Bullet;
while ( j > -1 )
{
bullet = bullets[j];
bullet.moveABit();
if ( bullet.y < -10 )
{
removeChild( bullet );
bullets.splice( j, 1 );
}
j = j - 1;
}
Second, I want to alter the scoring system, so that the player only gets points if he shoots an enemy. This means removing the points awarded when the enemy appears:
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();
}
...and adding the score in the bullet-enemy collision detection:
if ( bullet.hitTestObject( enemy ) )
{
enemyHasBeenHit = true; //new code
removeChild( bullet );
bullets.splice( j, 1 );
gameScore.addToValue( 10 );
}
It might be a good idea to make the player lose points if an enemy gets past the bottom of the screen -- but I'll leave that up to you.
Third, I want to add more sound effects. For this, I'm going to use the awesome (and free!) tool sfxr:
I've used the *Laser/Shoot* setting to make the noise to be played when the player shoots, and the *Explosion* setting to be played when the bullet hits an enemy. (We'll look at making explosion animations in a later tutorial.)
Here's the result:
Click to play
###Wrapping Up
You can download a zip of all the files I used in this tutorial (including the sound files) from this link.
This small addition has changed the entire game. It's very simple, though. Next time we'll improve this, so that the player can aim before they fire.
Until then, why not try adding a different kind of bullet that can be fired with the shift key?
Click here to read the next part of this mini-series: Move with the Mouse, Aim with the Keyboard.
{ 11 comments… read them below or add one }
← Previous Comments
Hey Richard, glad to hear you’re enjoying them.
Cool idea. I’m not sure why that wouldn’t work; your code looks fine to me. What happens if you call
enemy.gotoAndPlay("NanaBoom")
on every new Enemy instance as soon as you create them?When I try that it comes up with this error
Sorry about the late reply, Richard. Looks like your
enemy
var isn’t pointing to anything. But.. that can’t be the problem with the original code because you’ve just done a hitTestObject() check against it.Hmm.
Are you getting any errors?
Hey… when i do that the bullet doesnt seem to show up or move?
}
can you check it out and see whats wrong please.
thanks
Hey gta0004,
Check out the debugging guide 🙂
Hey there. When I input in the var bullet j codings. I get errors shown below:
1120: Access of undefined property bullets. bullets.push( newBullet );
1119: Access of possibly undefined property length through a reference with static type int. var j:int = bullets.length – 1;
1061: Call to a possibly undefined method splice through a reference with static type int. bullets.splice (j, 1);
1119: Access of possibly undefined property length through a reference with static type int. j = bullets.length – 1;
1061: Call to a possibly undefined method splice through a reference with static type int. bullets.splice( j, 1 );
What’s wrong down here? Hope to hear from you soon
First of all i must say that it’s one of the best tutorial ever!!!! But i don’t know if i’m gonna get an answer since the last post date from like last year 🙂 But ok here I go anyway! I’ve been scracthing my head with an #2025 error for a while! I’m pretty sure it’s something ridiculous to solve. Here the problem : I’m using the same while loop in this tutorial but inside a function that i call in the loop function. But here is not the problem.
The problem is that i’ve put an empty MC on the stage and my Main class is tied to it. Then i call all my stuff. If i keep this structure no problem happen, the bullets hittest just well with the enemy. But for functionality reason (for a follow player func) I put almost all of my instances in an another Empty Mc that i declare from my main class. Everything works well but only one thing! when the bullet collide with the enemy. I get the #2025 error. What i do not understand is that when i do the fist while loop for the bullet I’ve added a code that test against a blocker Mc if you will and everything works well. It’s only when I check for bullet/enemy collision that the error popup! Here’s a little bit of my code to better understand :
(Don’t worry all my instance are nicely called and available for all my code)
Variable dec:
Placing of my stuff :
Here’s the loop function :
I’ve tried everything to contForCamera.removeChild(enemy); to root, to stage. I’m getting the same godamn #2025 error. If I remove the contForCamera MC and put everything on the same level everything works fine!!!! But i can say godbye to my followplayer function! (by the i use your other tut on activeTut for that! Nice! one!)
I’m sure it’s something stupid! But it’s been like 1 week i’ve been searching!
Thanks in advance for the help!
Well, Well. You can flush my lengthy post just dound my error after following your nice DEBUGGING TUTORIAL!!!
The error was that i was call to many reference to the contForCamera.remove(X) thing. It’s was only require on this part of the code :
But thanks anyways to hear me rants!!!!
Ha, awesome, glad that helped 🙂
Dear Michael,thank you for great tutorial.
I’m actually on the point of adding new arrays of enemies.
It works,but the new array has two strange behaviors:
1-copies are often placed in the same position at the same moment
2-it happens that hitting to some copies you hit the whole array which is present on the stage at the moment.
The only error which comes out is a 3596
cause i’ve actually duplicated the math random to addchild on stage
but using the same enemySpawnRate
i’m gonna checking out the guide for debugging,but i’ve posted this here anyway cause i think the method is not right.
ah and here the part connected with collisions..
and further on…
thank you for reading and for eventual support!!
and of course i have decleared the barmy and fenemy var in the public class..