SHMUP Tutorial 4: Bullets

I thought I would have already covered bullets by now when I had planned out this tutorial, but I guess there was more to go over than I initially thought! Anyways hopefully you are following this from Part 3 of my tutorial series, if you aren’t you can download this project file to follow along.

Right so, let’s get this started with a new image!

Save this one in your folder with your ship and stars and.. yup you guessed it! Open up your “Assets.as” file and let’s get this tutorial started!

[Embed(source = "Images/bluebullet.png")]
public static const GRAPHIC_BLUE_BULLET:Class;

Make sure you embed your bullet image with the correct folder path!

Great, now let’s go ahead and make a new class for our bullet. Hmm.. I guess we can just name that “Bullet.as”

package  
{
	import net.flashpunk.Entity;
	import net.flashpunk.FP;
	import net.flashpunk.graphics.Spritemap;
 
	public class Bullet extends Entity 
	{
 
		private var _sprites:Spritemap;
		private var moveX:Number = 0;
		private var moveY:Number = 0;
 
		public function Bullet() 
		{
			_sprites = new Spritemap(Assets.GRAPHIC_BLUE_BULLET, 10, 10);
			_sprites.add("idle", [0, 1, 2], 12, true);
			_sprites.play("idle");
			_sprites.centerOO();
			graphic = _sprites;
			type = "Bullet";
			setHitbox(6, 6, 1, 3);
		}
 
		public function setMovement(_x:Number = 0, _y:Number = 0):void
		{
			moveX = _x;
			moveY = _y;
		}
 
		override public function update():void
		{
			x += moveX * FP.elapsed;
			y += moveY * FP.elapsed;
			if (x > FP.camera.x + FP.width + 50 || x < FP.camera.x - 50 || y < - 50 || y > FP.height + 50) {
				destroy();
			}
		}
 
		public function destroy():void
		{
			FP.world.recycle(this);
		}
	}
}

Oh boy. We have a lot of new stuff to explain here! How exciting!

private var _sprites:Spritemap;
private var moveX:Number = 0;
private var moveY:Number = 0;

Here we are just declaring a few variables that we will be using.
You might have noticed that the bullet image I had you saved is actually three images stacked on top of each other. The reason for this is we are going to use a Spritemap to make it animate those three images!

_sprites = new Spritemap(Assets.GRAPHIC_BLUE_BULLET, 10, 10);
_sprites.add("idle", [0, 1, 2], 12, true);
_sprites.play("idle");
_sprites.centerOO();
graphic = _sprites;

Alright so here is what makes our bullet animate! First we are setting our _sprites variable to be a new Spritemap, using our image that we embedded in our Assets file. Next we tell it that it that each frame will be 10 pixels wide by 10 pixels tall.
This brings us to the add function, this is used to add a new animation for our spritemap. We name it “idle”, then we tell it which frames it will use to play in a new array, how many frames per second our animation should play, and if it should loop.
Then we simply just tell our spritemap to play our animation that we just setup! Easy right?
Finally we are going to center our spritemap, just like we have been doing with our Image classes, and then we set the graphic of our entity to the spritemap.

type = "Bullet";
setHitbox(6, 6, 1, 3);

Here we are setting up the hitbox for the bullet. Because the mass of the bullet graphic is in the front of it I decided it would feel better if the hitbox was a square that was centered vertically, but not horizontally.

public function setMovement(_x:Number = 0, _y:Number = 0):void
{
	moveX = _x;
	moveY = _y;
}

You might be thinking “Hey! What gives! This function isn’t even used!” when you look through this file. And you would be right! But this will be used later when we create a new bullet. Because we will be recycling bullets this will give us an easy way to create new bullets that move differently later on. Think spread fire power ups where some bullets move diagonally. Since this function accepts two numbers and then sets the movement for our bullet, that is very easy to do while still letting us recycle the same bullet class easily.

override public function update():void
{
	x += moveX * FP.elapsed;
	y += moveY * FP.elapsed;
	if (x > FP.camera.x + FP.width + 50 || x < FP.camera.x - 50 || y < - 50 || y > FP.height + 50) {
		destroy();
	}
}

Here is our bullet’s update function, all it does is increase our location based on the moveX and moveY variables and then it checks to see if our bullet is off screen, if it is off screen then it runs the destroy function.

public function destroy():void
{
	FP.world.recycle(this);
}

Now you might be wondering why we didn’t just recycle our bullet directly? Well this function will also be used by our enemy class to remove the bullet when it collides with it, by calling this function we can easily add in more code later if we need to for removing bullets and it will run in both scenarios of being removed.

Awesome! We have some some bullets! But wait. Our player can’t fire them yet! Let’s fix that now, open up your “Player.as” file and update it to this :

package  
{
	import net.flashpunk.Entity;
	import net.flashpunk.FP;
	import net.flashpunk.graphics.Image;
	import net.flashpunk.utils.Input; 
	import net.flashpunk.utils.Key;
	public class Player extends Entity 
	{
 
		private var moveSpeed:Number = 200;
		private var _image:Image;
		private var bulletDelay:Number = 0;
 
		public function Player() 
		{
			_image = new Image(Assets.GRAPHIC_SHIP_PLAYER);
			_image.centerOO();
			graphic = _image;
			Input.define("right", Key.RIGHT, Key.D); 
			Input.define("left", Key.LEFT, Key.A);
			Input.define("down", Key.DOWN, Key.S);
			Input.define("up", Key.UP, Key.W);
			Input.define("fire", Key.SPACE);
			type = "Player";
			setHitbox(32, 32, 16, 16);
			FP.console.enable();
		}
 
		override public function update():void
		{
			if (Input.check("right")) x += moveSpeed * FP.elapsed; 
			if (Input.check("left")) x -= moveSpeed * FP.elapsed; 
			if (Input.check("down")) y += moveSpeed * FP.elapsed; 
			if (Input.check("up")) y -= moveSpeed * FP.elapsed; 
			if (x < 32 + FP.camera.x) x = 32 + FP.camera.x;
			if (x > FP.width - 32 + FP.camera.x) x = FP.width - 32 + FP.camera.x;
			if (y < 32) y = 32 ;
			if (y > FP.height - 32) y = FP.height - 32;
			if(_image.alpha >= 1) {
				if (collide("Enemy", x, y)) {
					x = FP.camera.x + 32;
					y = FP.halfHeight;
					_image.alpha = 0;
				}
			} else {
				_image.alpha += FP.elapsed;
			}
 
			if(bulletDelay > 0)  bulletDelay -= FP.elapsed;
			else if (Input.check("fire")) {
				bulletDelay = 0.5;
				var _bullet:Bullet = FP.world.create(Bullet) as Bullet;
				_bullet.x = x;
				_bullet.y = y;
				_bullet.setMovement(400);
				FP.world.add(_bullet);
			}
		}
	}
}

Now let’s go over all of the new code shall we?

private var bulletDelay:Number = 0;

Just declaring a variable so we can put a delay between shots.

Input.define("fire", Key.SPACE);

A new input definition! Feel free to add in more keys other than space if you want to, all you have to do is separate them with commas!

if(bulletDelay > 0)  bulletDelay -= FP.elapsed;
else if (Input.check("fire")) {
	bulletDelay = 0.5;
	var _bullet:Bullet = FP.world.create(Bullet) as Bullet;
	_bullet.x = x;
	_bullet.y = y;
	_bullet.setMovement(400);
	FP.world.add(_bullet);
}

Alright, so first we are going to check if our delay is greater than 0. If it is then let’s subtract how long has passed since our last update to our bulletDelay. If our bullet delay is less than our equal to zero than we will check if the “fire” button is being held down. If it is then we will set our delay to 0.5, making it so that it will be another half a second before we will fire another bullet.
Now we are going to create our bullet, this is a lot like when we created enemies in our previous tutorial, but this time we are using FP to access our current world to call the create function.
Next we set our new bullet’s x and y to our player’s x and y, then we run that setMovement function for our bullet to make it move 400 pixels horizontally per second and it defaults to 0 pixels vertically.
Finally we add our new bullet to our current world.

Tada! If you start up your game you will now be able to fire bullets! But they don’t actually do anything yet do they? Let’s open up our “Enemy.as” file next!

package  
{
	import net.flashpunk.Entity;
	import net.flashpunk.FP;
	import net.flashpunk.graphics.Image;
 
	public class Enemy extends Entity 
	{
		private var _image:Image;
		private var speedY:Number = 50;
 
		public function Enemy() 
		{
			_image = new Image(Assets.GRAPHIC_SHIP_PLAYER);
			_image.centerOO();
			_image.color = 0xFF0000;
			_image.tinting = 0.7;
			_image.flipped = true;
			graphic = _image;
			type = "Enemy";
			setHitbox(32, 10, 16, 5);
		}
 
		override public function update():void 
		{
			if (y < 32 && speedY < 0) speedY *= -1;
			if (y > FP.height - 32 && speedY > 0) speedY *= -1; 
			y += speedY * FP.elapsed;
 
			if (x < FP.camera.x - 100 - width) {
				destroy();
			}
 
			var _bullet:Bullet = (collide("Bullet", x, y)) as Bullet;
			if (_bullet) {
				_bullet.destroy();
				destroy();
			}
		}
 
		public function destroy():void
		{
				FP.world.recycle(this);
		}
 
	}
}

All right! Now our bullets will recycle our enemies! How eco-friendly of them! Let’s go over the new code now.

if (x < FP.camera.x - 100 - width) {
	destroy();
}

We changed this to a function call now instead of directly recycling our enemies.

var _bullet:Bullet = (collide("Bullet", x, y)) as Bullet;
if (_bullet) {
	_bullet.destroy();
	destroy();
}

Now this is a little different from how we handled collision with our player. This time we first create a variable of the Bullet type, and since the collide function returns an entity, we cast it as a Bullet. If our enemy doesn’t collide into a bullet, then this variable will just be null.
Next all we have to do is check if our _bullet variable isn’t null.
Finally, if our enemy did collide into a bullet then it will first destroy the bullet it collided into, and then it will destroy itself.

public function destroy():void
{
	FP.world.recycle(this);
}

And this brings us to the last bit of new code. This is just like the destroy function in our bullet class, and will become very useful later when we need to run code when our enemy is destroyed.

You can download a zip containing the entire project for this tutorial here.

Bookmark the permalink.

Comments are closed.