Advertisement

An Introduction to FlashPunk: Creating a Spaceship Shoot-'Em-Up

by

In the last tutorial, we introduced FlashPunk and its capabilities. Now it's time to build a game with it! We'll build a top-down, mouse-controlled shoot-'em-up, with a title screen and a game over screen. Read on to learn more...


Final Result Preview

Let's take a look at the final result we will be working towards:


Step 1: The Player Ship

As always, first we need a clean project. Grab the latest FlashPunk build from the official site. Create a new AS3 project in FlashDevelop and put FlashPunk's source in the source folder of the project. The game will have the following dimensions: 400x500 px.

We will begin our game by adding a ship - the player ship - on the screen. For that, we will need a new world, called GameWorld, and an image:

Player Ship Image

The player ship will be an entity. Create one, embed the image in it and put it on the screen. If you're feeling lost, below is the code for the player ship (if you're really feeling lost, I recommend reading the first tutorial again). I think that creating the world won't be a problem for you.

package  
{
	import net.flashpunk.Entity;
	import net.flashpunk.graphics.Image;
	
	public class PlayerShip extends Entity 
	{
		[Embed(source = '../img/PlayerShipImage.png')]
		private const IMAGE:Class;
		
		public function PlayerShip() 
		{
			graphic = new Image(IMAGE);
			
			graphic.x = -27.5;
			graphic.y = -30;
			
			x = 200;
			y = 400;
		}
		
	}

}

Add the player ship into the world and make it the current world when FlashPunk's Engine starts. You'll get the following:

The player ship in game

Step 2: Movement

With the player ship on the screen, we need to make it move. Just like every shoot-'em-up game, the player ship will be able to move across all the screen. There's one thing left to decide before coding the movement: we will be using frame-based movement (the reason for this is in the next step). That means changing (if needed) the super() call to FlashPunk's Engine and NOT using the FP.elapsed property.

The code for the player ship movement is below. The movement is mouse-based:

		private var _currentDistanceX:Number;
		private var _currentDistanceY:Number;
		
		private const SPEED:int = 3;
		
		override public function update():void
		{
			calculateDistances();
			
			if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED)
			{
				x = Input.mouseX;
				y = Input.mouseY;
			}
			else
			{
				x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED;
				y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED;
			}
		}
		
		private function calculateDistances():void
		{
			_currentDistanceX = Input.mouseX - x;
			_currentDistanceY = Input.mouseY - y;
		}

You will need to import net.flashpunk.utils.Input in the player ship class. Also, don't forget to enable FlashPunk's console. After compiling the project, you will see the player ship following the mouse:


Step 3: Enemies

Time to add something fun: enemies. Enemies need to spawn randomly (based on waves) and need to have a controlled movement. They also have a different image:

Image of the enemy

The code for creating the enemy class and adding the image to it shouldn't be a problem. What we need to focus on is how to make enemies follow a path. The idea is this: we create a wave of enemies and pass them a path to follow, and when to spawn. After that, they will be responsible for showing themselves on the screen and moving along the path.

What is a good representation for a path? A Vector of points seems good enough. Also, we need to define a distance between each point. That way, we can be sure of one important thing: if we define the distance between two points of the path to be the distance that the enemy moves during 1 frame, we won't need the enemy to process too much information, and we will be able to define after how many frames the enemy will leave the screen by just counting the number of points in the vector. That way, it's very easy to define when a wave will end.

See it in action: the code below only makes enemies follow a path already given to them, and on the time passed to them (which will be counted in frames). When the enemy is created, a counter that starts on a given number begins decreasing after each elapsed frame, and when it reaches zero, the enemy will put itself on screen and begin following its path.

package  
{
	import flash.geom.Point;
	import net.flashpunk.Entity;
	import net.flashpunk.FP;
	import net.flashpunk.graphics.Image;
	import net.flashpunk.World;
	
	public class Enemy extends Entity 
	{
		[Embed(source = '../img/EnemyImage.png')]
		private const IMAGE:Class;
		
		private var _timeToAct:uint;
		private var _pathToFollow:Vector.<Point>;
		
		private var _currentPoint:uint;
		
		private var _myWorld:World;
		private var _added:Boolean;
		
		public function Enemy(timeToAct:uint, pathToFollow:Vector.<Point>, worldToBeAdded:World) 
		{
			graphic = new Image(IMAGE);
			
			graphic.x = -15;
			graphic.y = -8;
			
			_timeToAct = timeToAct;
			
			_pathToFollow = pathToFollow;
			
			_currentPoint = 0;
			
			_myWorld = worldToBeAdded;
			_added = false;
		}
		
		override public function update():void
		{
			if (_timeToAct > 0)
			{
				_timeToAct--;
			}
			else
			{
				if (!_added)
				{
					_myWorld.add(this);
					
					_added = true;
				}
				
				x = _pathToFollow[_currentPoint].x;
				y = _pathToFollow[_currentPoint].y;
				
				_currentPoint++;
				
				if (_currentPoint == _pathToFollow.length)
				{
					_myWorld.remove(this);
					
					_added = false;
					
					destroy();
				}
			}
		}
		
		public function destroy():void
		{
			graphic = null;
		}
		
	}

}

From the code above, you can see that we always move a fixed distance from point to point by just positioning the enemy on its current point. It is because of this that we are able to determine when an enemy wave will end. You can also see that the enemy takes care of everything: adding itself in the world, moving through the world and removing itself from the world. With that, it's very simple to create enemies in the game.


Step 4: Adding Enemies to the Screen

Our base Enemy class is done. Now it's time to modify GameWorld a bit to add enemies. The first task is to generate paths for the enemies. For the purposes of this tutorial, we will only create a straight line, but feel free to try creating any kind of wave path. This is the function that creates a straight line:

		private function generateEnemyPath(distanceBetweenPoints:Number):Vector.<Point>
		{
			var i:Number;
			
			var vec:Vector.<Point> = new Vector.<Point>();
			
			var xPos:Number = Math.random() * 360 + 20;
			
			for (i = -20; i < 520; i += distanceBetweenPoints)
			{
				vec.push(new Point(xPos, i));
			}
			
			return vec;
		}

With that, we can already give to an enemy a path to follow. The next step is to actually create the enemy:

		private var _enemy:Enemy;
		
		public function GameWorld() 
		{
			_playerShip = new PlayerShip();
			
			add(_playerShip);
			
			_enemy = new Enemy(0, generateEnemyPath(1), this);
		}
		
		override public function update():void
		{
			super.update();
			
			if (_enemy)
				_enemy.update();
		}

Compile and run the game. You'll probably get the following error:

[Fault] exception, information=RangeError: Error #1125: The index 540 is out of range 540.

That happens because even after the enemy deletes itself from the world, we are still calling the update() function of it, because our code didn't detect when the enemy removed itself. Let's fix that by overriding the current remove() method:

		override public function remove(e:Entity):Entity
		{
			if (e is Enemy)
			{
				_enemy = new Enemy(0, generateEnemyPath(1), this);
			}
			
			return super.remove(e);
		}

Now compile the project and you'll see this:

This is what that function does: every time the enemy removes itself from the world, we detect that through our overriden function and then just create another enemy to "replace" the old one. That's it! We now have enemies moving across the screen!


Step 5: Shooting With the Player Ship

A shoot-'em-up game wouldn't be fun without cool ways of shooting bullets out of your ship, would it? In this step we'll see a great way of organizing bullet patterns in order to shoot them. If you tried to take a guess, you are probably correct: we're going to use Vectors of Points. However, this time the points will have dynamic beginnings and ends, because your ship won't always be at the same place every time you shoot, but don't worry, it's not as hard as it sounds!

The strategy here is to generate a bullet pattern around a fixed x- and y-axis, and then sum the player ship's position to the points from the pattern, thus relocating the axis to a new position, giving the impression that the bullets are coming out of the player ship. The bullet image we are going to use is this:

Player bullet

Everything gets simpler when you look at the code. We are basically doing something very similar to the enemy path:

package  
{
	import flash.geom.Point;
	import net.flashpunk.Entity;
	import net.flashpunk.graphics.Image;

	public class PlayerBullet extends Entity 
	{
		[Embed(source = '../img/BulletImage.png')]
		private const IMAGE:Class;
		
		private var _pathToFollow:Vector.<Point>;
		
		private var _xPos:Number;
		private var _yPos:Number;
		
		public function PlayerBullet(pathToFollow:Vector.<Point>, xPos:Number, yPos:Number) 
		{
			graphic = new Image(IMAGE);
			
			graphic.x = graphic.y = -3.5;
			
			_pathToFollow = pathToFollow;
			
			_xPos = xPos;
			_yPos = yPos;
		}
		
		override public function update():void
		{
			x = _xPos + _pathToFollow[0].x;
			y = _yPos + _pathToFollow[0].y;
			
			_pathToFollow.shift();
			
			if (_pathToFollow.length == 0)
			{
				world.remove(this);
				
				destroy();
			}
		}
		
		public function destroy():void
		{
			_pathToFollow = null;
			
			graphic = null;
		}
		
	}

}

Notice that we don't really create the bullet patterns in here: they're always passed as parameters, just like the enemies. The only difference is that the bullets are always added right away in the world and we keep the initial position of the bullet.

Let's try adding a bullet when the player clicks with the mouse. In PlayerShip.as:

		override public function update():void
		{
			calculateDistances();
			
			if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED)
			{
				x = Input.mouseX;
				y = Input.mouseY;
			}
			else
			{
				x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED;
				y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED;
			}
			
			if (Input.mousePressed)
			{
				world.add(new PlayerBullet(GameWorld(world).generateBulletPath(3), x, y));
			}
		}

Now we need to create the bullet path. We are going to create a straight line, just like for the enemy, but you can do any kind of path! In GameWorld.as, let's create the generateBulletPath() function:

		public function generateBulletPath(distanceBetweenPoints:Number):Vector.<Point>
		{
			var i:Number;
			
			var vec:Vector.<Point> = new Vector.<Point>();
			
			for (i = 0; i > -500; i -= distanceBetweenPoints)
			{
				vec.push(new Point(0, i));
			}
			
			return vec;
		}

With that, hit the compile and run button and this is what you get:


Step 6: Collision Detection (Using Masks)

We have now the basics of the game running: a player ship that shoots and enemies that go down the screen. Time to add collision detection!

The first step to add the collision detection is to give each entity a type. I'll leave that to you: give the "Player" type to PlayerShip, "Enemy" to Enemy and "PlayerBullet" to PlayerBullet.

We will be using pixel-perfect collision in this game, so it might be useful to talk about masks. Masks are elements used by FlashPunk for collision detection. They are basically like hitboxes, but they can have a different form (pixel level). We need to set up masks for the player ship, enemies and bullets. The image used by the mask is the same image of the entity. Look at the code for the PlayerShip, Enemy and PlayerBullet, respectively:

		public function PlayerShip() 
		{
			graphic = new Image(IMAGE);
			
			graphic.x = -27.5;
			graphic.y = -30;
			
			mask = new Pixelmask(IMAGE, -27.5, -30);
			
			x = 200;
			y = 400;
			
			type = "Player";
		}
		public function Enemy(timeToAct:uint, pathToFollow:Vector.<Point>, worldToBeAdded:World) 
		{
			graphic = new Image(IMAGE);
			
			graphic.x = -15;
			graphic.y = -8;
			
			mask = new Pixelmask(IMAGE, -15, -8);
			
			_timeToAct = timeToAct;
			
			_pathToFollow = pathToFollow;
			
			_currentPoint = 0;
			
			_myWorld = worldToBeAdded;
			_added = false;
			
			type = "Enemy";
		}
		public function PlayerBullet(pathToFollow:Vector.<Point>, xPos:Number, yPos:Number) 
		{
			graphic = new Image(IMAGE);
			
			graphic.x = graphic.y = -3.5;
			
			mask = new Pixelmask(IMAGE, -3.5, -3.5);
			
			_pathToFollow = pathToFollow;
			
			_xPos = xPos;
			_yPos = yPos;
			
			type = "PlayerBullet";
		}

As you can see, this is very simple: we create a new Pixelmask, pass the source to use as a mask (just like with the graphic) and then pass both x and y offsets (in case you want to center the mask somewhere). Now, in GameWorld.as:

		private var _bulletList:Vector.<PlayerBullet>;
		
		override public function update():void
		{
			super.update();
			
			if (_enemy)
				_enemy.update();
			
			_bulletList = new Vector.<PlayerBullet>();
			
			getType("PlayerBullet", _bulletList);
			
			for each (var bullet:PlayerBullet in _bulletList)
			{
				if (bullet.collideWith(_enemy, bullet.x, bullet.y))
				{
					_enemy.takeDamage();
					
					remove(bullet);
					
					bullet.destroy();
				}
			}
		}

Notice that we could have simply used _enemy.collide("PlayerBullet", _enemy.x, _enemy.y) to check for collisions, but the method above is better when we have many bullets on the screen and there is a possibility that two bullets hit the same enemy at the same time. We have called the takeDamage() function of the Enemy class, but at the moment there is none. (Create an empty function for now. In the next step we will make the enemy take damage and explode when necessary.) Compile the project and you'll get this:


Step 7: Enemy Death

We have made bullets hit our enemies. In this step we will play an explosion animation every time an enemy dies and remove it from the game. The explosion animation sprite sheet is below:

Explosion after enemy death

The approach we will take to do this is by decreasing the enemy's health in the takeDamage() function, and if the health gets below zero, we will destroy it and put the animation in the screen. The code for decreasing the health is below:

		private var _health:int = 100;
		
		public function takeDamage():void
		{
			_health -= 50;
			
			if (_health <= 0)
			{
				addExplosion();
				
				_myWorld.remove(this);
				
				_added = false;
				
				destroy();
			}
		}

The code is very simple. There's only one thing unknown about it: the addExplosion() function. This function will create an instance of Explosion and add it to the world. The Explosion class will just play and remove itself from the world after that. It's a simple class:

package  
{
	import net.flashpunk.Entity;
	import net.flashpunk.graphics.Spritemap;

	public class Explosion extends Entity 
	{
		[Embed(source = '../img/ExplosionAnimation.png')]
		private const ANIMATION:Class;
		
		public function Explosion(xPos:Number, yPos:Number) 
		{
			graphic = new Spritemap(ANIMATION, 50, 46, onAnimationEnd);
			
			graphic.x = -25;
			graphic.y = -23;
			
			x = xPos;
			y = yPos;
			
			Spritemap(graphic).add("Explode", [0, 1, 2, 3, 4], 25/60, false);
			
			Spritemap(graphic).play("Explode");
		}
		
		private function onAnimationEnd():void 
		{
			world.remove(this);
			
			destroy();
		}
		
		public function destroy():void
		{
			graphic = null;
		}
		
	}

}

The trick here is using the callback parameter of the Spritemap class: when the animation ends, that function will be called, and then it removes itself from the world.

Now, back to Enemy.as to finish that function!

		private function addExplosion():void 
		{
			world.add(new Explosion(x, y));
		}

Easy, isn't it? Compile the game and destroy some enemies!


Step 8: Score

The next logical step after making enemies die is to add a score in the game! We will do that using FlashPunk's Text class. Start by creating a GameScore class, which will hold the score of the game. Since Text is a Graphic, we will make GameScore an Entity and add its text as a graphic. Look at the code:

package  
{
	import net.flashpunk.Entity;
	import net.flashpunk.graphics.Text;

	public class GameScore extends Entity 
	{
		private var _score:int;
		
		public function GameScore() 
		{
			graphic = new Text("Score: 0");
			
			_score = 0;
		}
		
		public function addScore(points:int):void
		{
			_score += points;
			
			Text(graphic).text = "Score: " + _score.toString();
		}
		
		public function destroy():void
		{
			graphic = null;
		}
		
	}

}

As you can see, we will call the addScore() function to add points to the game's score. First, we need to add it to the world. In GameWorld.as:

		private var _score:GameScore;
		
		public function GameWorld() 
		{
			_playerShip = new PlayerShip();
			
			add(_playerShip);
			
			_enemy = new Enemy(0, generateEnemyPath(1), this);
			
			_score = new GameScore();
			
			_score.x = 300;
			_score.y = 470;
			
			add(_score);
		}
		
		public function get score():GameScore 
		{
			return _score;
		}
		
		public function get score():int 
		{
			return _score;
		}

If we hit compile, we get just a static score on the bottom of the screen:

The game score

We need to add something to the score every time an enemy is killed. In Enemy.as:

		public function takeDamage():void
		{
			_health -= 50;
			
			if (_health <= 0)
			{
				addExplosion();
				
				GameWorld(world).score.addScore(1);
				
				_myWorld.remove(this);
				
				_added = false;
				
				destroy();
			}
		}

Hit compile now and the score will always increase when an enemy is killed!


Step 9: Upgrades

Time to add upgrades! We will not have a nice screen to choose upgrades. Instead, we will create upgrades based on the score: each time the score goes up by 5 (up to 45), the player's speed will increase a bit. When the score reaches 25, the player will be able to shoot two shots on every click. We will make the score 50 be the end of the game.

Let's begin by coding the player speed upgrade. For that, we will need to add a multiplier to the speed. In PlayerShip.as:

		public var speedMultiplier:Number = 1;
		
		override public function update():void
		{
			calculateDistances();
			
			if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED * speedMultiplier * speedMultiplier)
			{
				x = Input.mouseX;
				y = Input.mouseY;
			}
			else
			{
				x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier;
				y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier;
			}
			
			if (Input.mousePressed)
			{
				world.add(new PlayerBullet(GameWorld(world).generateBulletPath(3), x, y));
			}
		}

Now it's all about changing the multiplier in GameWorld.as:

		private var _speedUpgradeNumber:int = 0;
		
		override public function update():void
		{
			super.update();
			
			if (_enemy)
				_enemy.update();
			
			_bulletList = new Vector.<PlayerBullet>();
			
			getType("PlayerBullet", _bulletList);
			
			for each (var bullet:PlayerBullet in _bulletList)
			{
				if (bullet.collideWith(_enemy, bullet.x, bullet.y))
				{
					_enemy.takeDamage();
					
					remove(bullet);
					
					bullet.destroy();
				}
			}
			
			if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5))
			{
				_playerShip.speedMultiplier += 0.1;
				
				_speedUpgradeNumber++;
			}
		}

Done! Now, every 5 enemy deaths the player will receive a 10% increase in moving speed!

For the double bullet upgrade, we will make a boolean in the PlayerShip class indicating whether or not the ship has the upgrade. Then we will check that when shooting. Here is it:

		public var hasDoubleShoot:Boolean = false;
		
		override public function update():void
		{
			calculateDistances();
			
			if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED * speedMultiplier * speedMultiplier)
			{
				x = Input.mouseX;
				y = Input.mouseY;
			}
			else
			{
				x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier;
				y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier;
			}
			
			if (Input.mousePressed)
			{
				if (hasDoubleShoot)
				{
					world.add(new PlayerBullet(GameWorld(world).generateBulletPath(3), x - 5, y));
					world.add(new PlayerBullet(GameWorld(world).generateBulletPath(3), x + 5, y));
				}
				else
				{
					world.add(new PlayerBullet(GameWorld(world).generateBulletPath(3), x, y));
				}
			}
		}

Now, let's do the same as we did for the speed in GameWorld.as:

		override public function update():void
		{
			super.update();
			
			if (_enemy)
				_enemy.update();
			
			_bulletList = new Vector.<PlayerBullet>();
			
			getType("PlayerBullet", _bulletList);
			
			for each (var bullet:PlayerBullet in _bulletList)
			{
				if (bullet.collideWith(_enemy, bullet.x, bullet.y))
				{
					_enemy.takeDamage();
					
					remove(bullet);
					
					bullet.destroy();
				}
			}
			
			if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5))
			{
				_playerShip.speedMultiplier += 0.1;
				
				_speedUpgradeNumber++;
			}
			
			if (_score.score >= 25)
			{
				_playerShip.hasDoubleShoot = true;
			}
		}

And that's it! Hit compile and your game will have upgrades!


Step 10: Increasing the Difficulty

If you played the game until getting the double shot upgrade in the last step, you may have noticed that the game is too easy right now. What do you think of increasing its difficulty based on the player's score?

That's the idea: the game will now be able to put on the screen more than one enemy. And for each kill the player gets, we decrease the timer of spawning a new enemy. Interesting, isn't it? But that's not the only thing. What about increasing the enemies' healths and decreasing the damage they take from the player? Now we're getting somewhere!

Let's jump to the coding: first we will make the health increase and damage decrease every kill. They will be just like the upgrades on the player ships, but this time we will need to change our approach as to where to keep the multipliers. In Enemy.as:

		private var _health:int;
		
		public static var healthMultiplier:Number = 1;
		public static var damageMultiplier:Number = 1;
		
		public function Enemy(timeToAct:uint, pathToFollow:Vector.<Point>, worldToBeAdded:World) 
		{
			graphic = new Image(IMAGE);
			
			graphic.x = -15;
			graphic.y = -8;
			
			mask = new Pixelmask(IMAGE, -15, -8);
			
			_timeToAct = timeToAct;
			
			_pathToFollow = pathToFollow;
			
			_currentPoint = 0;
			
			_myWorld = worldToBeAdded;
			_added = false;
			
			type = "Enemy";
			
			health = 100 * Enemy.healthMultiplier;
		}
		
		public function takeDamage():void
		{
			_health -= 50 * Enemy.damageMultiplier;
			
			if (_health <= 0)
			{
				addExplosion();
				
				GameWorld(world).score.addScore(1);
				
				_myWorld.remove(this);
				
				_added = false;
				
				destroy();
			}
		}

And now, in GameWorld.as:

		private var _enemyUpgradeNumber:int = 0;
		
		override public function update():void
		{
			super.update();
			
			if (_enemy)
				_enemy.update();
			
			_bulletList = new Vector.<PlayerBullet>();
			
			getType("PlayerBullet", _bulletList);
			
			for each (var bullet:PlayerBullet in _bulletList)
			{
				if (bullet.collideWith(_enemy, bullet.x, bullet.y))
				{
					_enemy.takeDamage();
					
					remove(bullet);
					
					bullet.destroy();
				}
			}
			
			if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5))
			{
				_playerShip.speedMultiplier += 0.1;
				
				_speedUpgradeNumber++;
			}
			
			if (_score.score >= 25)
			{
				_playerShip.hasDoubleShoot = true;
			}
			
			if (_score.score > _enemyUpgradeNumber)
			{
				Enemy.damageMultiplier -= 0.015;
				Enemy.healthMultiplier += 0.02;
				
				_enemyUpgradeNumber++;
			}
		}

And now our enemies are getting stronger after each kill! Take that, evil player!

Now we need to modify GameWorld in order to spawn enemies based on time. It's a simple thing: we will just need a timer and a spawn interval. Here's all the modified code:

		private var _enemyList:Vector.<Enemy>;
		
		private var _enemySpawnInterval:int = 5000;
		private var _enemySpawnTimer:int;
		
		public function GameWorld() 
		{
			_playerShip = new PlayerShip();
			
			add(_playerShip);
			
			_enemyList = new Vector.<Enemy>();
			
			_score = new GameScore();
			
			_score.x = 300;
			_score.y = 470;
			
			add(_score);
			
			_enemySpawnTimer = 0;
		}
		
		override public function update():void
		{
			super.update();
			
			_enemySpawnTimer--;
			
			if (_enemySpawnTimer <= 0)
			{
				_enemySpawnTimer = _enemySpawnInterval;
				
				_enemyList.push(new Enemy(uint(Math.random() * 30), generateEnemyPath(2), this));
				
				add(_enemyList[_enemyList.length - 1]);
			}
			
			_bulletList = new Vector.<PlayerBullet>();
			
			getType("PlayerBullet", _bulletList);
			
			for each (var bullet:PlayerBullet in _bulletList)
			{
				for each (var enemy:Enemy in _enemyList)
				{
					if (bullet.collideWith(enemy, bullet.x, bullet.y))
					{
						enemy.takeDamage();
						
						remove(bullet);
						
						bullet.destroy();
					}
				}
			}
			
			if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5))
			{
				_playerShip.speedMultiplier += 0.1;
				
				_speedUpgradeNumber++;
			}
			
			if (_score.score >= 25)
			{
				_playerShip.hasDoubleShoot = true;
			}
			
			if (_score.score > _enemyUpgradeNumber)
			{
				Enemy.damageMultiplier -= 0.015;
				Enemy.healthMultiplier += 0.02;
				
				_enemyUpgradeNumber++;
			}
		}
		
		override public function remove(e:Entity):Entity
		{
			if (e is Enemy)
			{
				_enemyList.splice(_enemyList.indexOf(e), 1);
			}
			
			return super.remove(e);
		}

Notice that I gave the enemies a few random frames of "wait time" before appearing. That will make their appearance unpredictable. Now all that's left is to decrease the spawn interval after every kill:

		override public function update():void
		{
			super.update();
			
			_enemySpawnTimer--;
			
			if (_enemySpawnTimer <= 0)
			{
				_enemySpawnTimer = _enemySpawnInterval;
				
				_enemyList.push(new Enemy(uint(Math.random() * 30), generateEnemyPath(2), this));
				
				add(_enemyList[_enemyList.length - 1]);
			}
			
			_bulletList = new Vector.<PlayerBullet>();
			
			getType("PlayerBullet", _bulletList);
			
			for each (var bullet:PlayerBullet in _bulletList)
			{
				for each (var enemy:Enemy in _enemyList)
				{
					if (bullet.collideWith(enemy, bullet.x, bullet.y))
					{
						enemy.takeDamage();
						
						remove(bullet);
						
						bullet.destroy();
					}
				}
			}
			
			if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5))
			{
				_playerShip.speedMultiplier += 0.1;
				
				_speedUpgradeNumber++;
			}
			
			if (_score.score >= 25)
			{
				_playerShip.hasDoubleShoot = true;
			}
			
			if (_score.score > _enemyUpgradeNumber)
			{
				Enemy.damageMultiplier -= 0.015;
				Enemy.healthMultiplier += 0.02;
				
				_enemySpawnInterval -= 3;
				
				_enemyUpgradeNumber++;
			}
		}

With that, we basically have everything done in the game world!


Step 11: The Main Menu World

I think we have everything finished in the game world. The game's surprisingly difficult with the tougher enemies! What do you think of making a nice main menu now? I created a nice background for it:

The main menu screen

Create the MainMenuWorld class, which extends from net.flashpunk.World, and add the background in it. I'll leave the code for you. We will need a play button, which I also created:

The play button

In order to create the play button, we are going to use the Button class created in the first part of this tutorial series. Here's the code for the button in MainMenuWorld.as:

		[Embed(source = '../img/PlayGameButton.png')]
		private const PLAYBUTTON:Class;
		
		private var _playButton:Button;
		
		public function MainMenuWorld() 
		{
			addGraphic(new Image(TITLE));
			
			_playButton = new Button(playTheGame, null, 48, 395);
			
			_playButton.setSpritemap(PLAYBUTTON, 312, 22);
			
			add(_playButton);
		}
		
		private function playTheGame():void 
		{
			FP.world = new GameWorld();
			
			destroy();
		}
		
		public function destroy():void
		{
			removeAll();
			
			_playButton = null;
		}

Don't forget to change the Main class as well!

		override public function init():void
		{
			FP.world = new MainMenuWorld();
			
			FP.console.enable();
		}

Hit compile and... Yes! Our shiny main menu world works! Now to the game over world!


Step 12: The Game Over World

The game over world is going to be very simple. I have created two images: one for when the player dies and one for when the player wins the game. There will be a Quit button that will return the player to the main menu. It's basically the same thing as the main menu world. Here are the two images and the button:

Game over image when player loses
Game over image when player wins
The quit button

I will leave the coding to you. The only thing that will change in this class is that it will need an argument passed to the constructor, telling whether or not the player destroyed the enemies. Here's the code for the constructor:

		public function GameOverWorld(hasLost:Boolean)
		{
			if (hasLost)
			{
				addGraphic(new Image(BACKGROUNDLOST));
			}
			else
			{
				addGraphic(new Image(BACKGROUNDWON));
			}
			
			_quitButton = new Button(quitToMain, null, 166, 395);
			
			_quitButton.setSpritemap(QUITBUTTON, 69, 19);
			
			add(_quitButton);
		}

Step 13: The Boss - Movement

Finally, the moment everyone was waiting for. Every shoot-'em-up needs a boss, and this is ours!

The boss! Kill him!

What we need now is code it. First, the movements. And then, the bullets. This step is for the movements.

As you may have guessed, our boss won't go directly down the screen. Instead, it will move randomly around the top of the screen, to give the player a bit of difficulty defeating it. What we will need to do is a movement very similar to the player's. The only difference is that it will follow a point chosen randomly in the top of the screen, and not the mouse. Here's the full code for the Boss class!

package  
{
	import flash.geom.Point;
	import net.flashpunk.Entity;
	import net.flashpunk.graphics.Image;
	import net.flashpunk.masks.Pixelmask;

	public class Boss extends Entity 
	{
		[Embed(source = '../img/BossImage.png')]
		private const IMAGE:Class;
		
		private var _currentDistanceX:Number;
		private var _currentDistanceY:Number;
		
		private var _randomPoint:Point;
		
		private const SPEED:int = 3;
		
		public var speedMultiplier:Number = 1;
		
		public function Boss() 
		{
			graphic = new Image(IMAGE);
			
			graphic.x = -38;
			graphic.y = -35;
			
			mask = new Pixelmask(IMAGE, -38, -35);
			
			type = "BossEnemy";
			
			getRandomPoint();
		}
		
		private function getRandomPoint():void 
		{
			_randomPoint = new Point();
			
			_randomPoint.x = Math.random() * 400;
			_randomPoint.y = 38 + (Math.random() * 100);
		}
		
		override public function update():void
		{
			calculateDistances();
			
			if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED * speedMultiplier * speedMultiplier)
			{
				x = _randomPoint.x;
				y = _randomPoint.y;
				
				getRandomPoint();
			}
			else
			{
				x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier;
				y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier;
			}
		}
		
		private function calculateDistances():void
		{
			_currentDistanceX = _randomPoint.x - x;
			_currentDistanceY = _randomPoint.y - y;
		}
		
	}

}

We are basically using the same movement code from PlayerShip. Notice that we have kept the speed multipliers, because there will be a really fun thing in the end!

We need now to come up with a way to test this movement. Running the game and getting 50 kills is too much time to wait until we can see the boss (and end up realizing there's a bug and we will need to do it all again and again!). Let's just add the boss on the screen when the game begins (yes, with the other enemies going down the screen!) and check the movement! In GameWorld.as:

		private var _boss:Boss;
		
		public function GameWorld() 
		{
			_playerShip = new PlayerShip();
			
			add(_playerShip);
			
			_enemyList = new Vector.<Enemy>();
			
			_score = new GameScore();
			
			_score.x = 300;
			_score.y = 470;
			
			add(_score);
			
			_enemySpawnTimer = 0;
			
			_boss = new Boss();
			
			add(_boss);
		}

Hit compile and test the game! Our boss is moving nicely, isn't it? It's going to be hard to kill it!


Step 14: The Boss - Shooting

Time for the really evil boss bullets! Here is their image:

Boss bullet

But there is something really bothering me: they will have exactly the same behavior as the player bullet, but instead they will just follow the same enemy downward path, and will have a different FlashPunk type, but I don't want to copy and paste the same code in their class. What do you think about using some Object-Oriented Design and make an inheritance? Take all the code (yes, all the code) of PlayerBullet and copy it to a new class called Bullet. Remove the code related to the bullet's image, and this is what you get:

package  
{
	import flash.geom.Point;
	import net.flashpunk.Entity;

	public class Bullet extends Entity 
	{
		private var _pathToFollow:Vector.<Point>;
		
		private var _xPos:Number;
		private var _yPos:Number;
		
		public function Bullet(pathToFollow:Vector.<Point>, xPos:Number, yPos:Number) 
		{
			_pathToFollow = pathToFollow;
			
			_xPos = xPos;
			_yPos = yPos;
		}
		
		override public function update():void
		{
			x = _xPos + _pathToFollow[0].x;
			y = _yPos + _pathToFollow[0].y;
			
			_pathToFollow.shift();
			
			if (_pathToFollow.length == 0)
			{
				world.remove(this);
				
				destroy();
			}
		}
		
		public function destroy():void
		{
			_pathToFollow = null;
			
			graphic = null;
		}
		
	}

}

That's the basic behavior of the bullet. Now, what do we do with the PlayerBullet class? Put only the things related to the bullet image in there, and remove the rest. And also make it inherit from Bullet:

package  
{
	import flash.geom.Point;
	import net.flashpunk.graphics.Image;
	import net.flashpunk.masks.Pixelmask;

	public class PlayerBullet extends Bullet 
	{
		[Embed(source = '../img/BulletImage.png')]
		private const IMAGE:Class;
		
		public function PlayerBullet(pathToFollow:Vector.<Point>, xPos:Number, yPos:Number) 
		{
			super(pathToFollow, xPos, yPos);
			
			graphic = new Image(IMAGE);
			
			graphic.x = graphic.y = -3.5;
			
			mask = new Pixelmask(IMAGE, -3.5, -3.5);
			
			type = "PlayerBullet";
		}
		
	}

}

Do you mind making the same thing for the BossBullet class too? The only difference is that it will have a type of "BossBullet". I'll leave the code to you. Check the tutorial source if you need help!

We could have done the same for the boss and the enemies, but we would need to change more things, because the movement of the enemies isn't the same as the movement of the boss. It's better to leave it that way. And now, for the boss shooting. We will use the same timer strategy that we used for the enemy spawning. Here's the code:

		private var _shootingInterval:int = 75;
		private var _shootingTimer:int = 0;
		
		override public function update():void
		{
			calculateDistances();
			
			if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED * speedMultiplier * speedMultiplier)
			{
				x = _randomPoint.x;
				y = _randomPoint.y;
				
				getRandomPoint();
			}
			else
			{
				x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier;
				y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier;
			}
			
			_shootingTimer--;
			
			if (_shootingTimer <= 0)
			{
				_shootingTimer = _shootingInterval;
				
				world.add(new BossBullet(GameWorld(world).generateBossBulletPath(1.5), x - 10, y));
				world.add(new BossBullet(GameWorld(world).generateBossBulletPath(1.5), x + 10, y));
				world.add(new BossBullet(GameWorld(world).generateBossBulletPath(1.5), x, y + 5));
			}
		}

Compile and run the code and now you have a boss shooting bullets and flying around! Time to make the last battle happen in the next step :)


Step 15: Final Touches

You may have noticed that the boss's bullets don't hit the player ship, and the player's bullets don't hit the boss. That's because we haven't coded them to hit their enemies. That's what we are going to do now. We will also make the boss only appear when the score reaches 50, and check for when the player loses the game.

The first task: to make the boss only appear when the score reaches 50. We will do that by checking for the score in GameWorld.as, just like we did with the enemy and player upgrades.

		private var _bossAppeared:Boolean = false;
		
		public function GameWorld() 
		{
			_playerShip = new PlayerShip();
			
			add(_playerShip);
			
			_enemyList = new Vector.<Enemy>();
			
			_score = new GameScore();
			
			_score.x = 300;
			_score.y = 470;
			
			add(_score);
			
			_enemySpawnTimer = 0;
			
			_boss = new Boss();
			
			// Not adding the boss in the game this time
		}
		
		override public function update():void
		{
			super.update();
			
			_enemySpawnTimer--;
			
			if (_enemySpawnTimer <= 0 && !_bossAppeared)
			{
				_enemySpawnTimer = _enemySpawnInterval;
				
				_enemyList.push(new Enemy(uint(Math.random() * 30), generateEnemyPath(2), this));
				
				add(_enemyList[_enemyList.length - 1]);
			}
			
			_bulletList = new Vector.<PlayerBullet>();
			
			getType("PlayerBullet", _bulletList);
			
			for each (var bullet:PlayerBullet in _bulletList)
			{
				for each (var enemy:Enemy in _enemyList)
				{
					if (bullet.collideWith(enemy, bullet.x, bullet.y))
					{
						enemy.takeDamage();
						
						remove(bullet);
						
						bullet.destroy();
					}
				}
			}
			
			if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5))
			{
				_playerShip.speedMultiplier += 0.1;
				
				_speedUpgradeNumber++;
			}
			
			if (_score.score >= 25)
			{
				_playerShip.hasDoubleShoot = true;
			}
			
			if (_score.score > _enemyUpgradeNumber)
			{
				Enemy.damageMultiplier -= 0.015;
				Enemy.healthMultiplier += 0.02;
				
				_enemySpawnInterval -= 3;
				
				_enemyUpgradeNumber++;
			}
			
			if (_score.score == 50 && !_bossAppeared)
			{
				add(_boss);
				
				_bossAppeared = true;
			}
		}

First task done. Notice that we also changed the block that spawned new enemies. That way, when the boss appears, no more enemies will spawn.

Second task: make boss bullets hit the player and player bullets hit the boss. This is also done in GameWorld.as. Take a look at the code:

		private var _bulletList:Vector.<Bullet>;
		
		override public function update():void
		{
			super.update();
			
			_enemySpawnTimer--;
			
			if (_enemySpawnTimer <= 0 && !_bossAppeared)
			{
				_enemySpawnTimer = _enemySpawnInterval;
				
				_enemyList.push(new Enemy(uint(Math.random() * 30), generateEnemyPath(2), this));
				
				add(_enemyList[_enemyList.length - 1]);
			}
			
			_bulletList = new Vector.<Bullet>();
			
			getType("PlayerBullet", _bulletList);
			
			for each (var bullet:PlayerBullet in _bulletList)
			{
				for each (var enemy:Enemy in _enemyList)
				{
					if (bullet.collideWith(enemy, bullet.x, bullet.y))
					{
						enemy.takeDamage();
						
						remove(bullet);
						
						bullet.destroy();
					}
				}
				
				if (_bossAppeared)
				{
					if (bullet.collideWith(_boss, bullet.x, bullet.y))
					{
						_boss.takeDamage();
						
						if (!_boss)
						{
							endTheGame();
							
							return;
						}
						
						remove(bullet);
						
						bullet.destroy();
					}
				}
			}
			
			_bulletList = new Vector.<Bullet>();
			
			getType("BossBullet", _bulletList);
			
			for each (var bossBullet:BossBullet in _bulletList)
			{
				if (bossBullet.collideWith(_playerShip, bossBullet.x, bossBullet.y))
				{
					endTheGame();
					
					return;
				}
			} 
			
			if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5))
			{
				_playerShip.speedMultiplier += 0.1;
				
				_speedUpgradeNumber++;
			}
			
			if (_score.score >= 25)
			{
				_playerShip.hasDoubleShoot = true;
			}
			
			if (_score.score > _enemyUpgradeNumber)
			{
				Enemy.damageMultiplier -= 0.015;
				Enemy.healthMultiplier += 0.02;
				
				_enemySpawnInterval -= 3;
				
				_enemyUpgradeNumber++;
			}
			
			if (_score.score == 50 && !_bossAppeared)
			{
				add(_boss);
				
				_bossAppeared = true;
			}
		}

In the first part of the code, we check for a collision between the player's bullets and the boss. If there is a collision, we call the takeDamage() function from the boss, which is below. After checking the player's bullets, we check for the boss's bullets, and if we find a collision between any of them and the player, we end the game. This function is also below.

Now, for the takeDamage() function of the boss. We want the boss to have a lot of health, and we will make it fly faster after each hit. In Boss.as:

		private var _health:int = 1000;
		
		public function takeDamage():void
		{
			_health -= 50;
			
			speedMultiplier += 0.05;
			
			if (_health <= 0)
			{
				world.remove(this);
				
				destroy();
			}
		}
		
		public function destroy():void
		{
			graphic = null;
			
			mask = null;
			
			_randomPoint = null;
		}

Our boss battle is complete! It will die after 20 hits from the player, getting faster after each hit.

Now, the only thing remaining is to end the game and show the screen when the boss is killed, and also make the player lose before the boss battle if an enemy goes down the screen. For the end of the game, we need to check when the boss is killed and create the endTheGame() function in GameWorld.as. This function will basically remove every bullet from the screen, remove the player and boss and then add the game over screen. In GameWorld.as:

		override public function remove(e:Entity):Entity
		{
			if (e is Enemy)
			{
				_enemyList.splice(_enemyList.indexOf(e), 1);
			}
			
			if (e is Boss)
			{
				_boss = null;
			}
			
			return super.remove(e);
		}
		
		public function endTheGame():void
		{
			removeAll();
			
			while (_bulletList.length > 0)
			{
				_bulletList[0].destroy();
				
				_bulletList.shift();
			}
			
			while (_enemyList.length > 0)
			{
				_enemyList[0].destroy();
				
				_enemyList.shift();
			}
			
			_bulletList = null;
			_enemyList = null;
			_playerShip = null;
			_score = null;
			
			if (_boss)
			{
				FP.world = new GameOverWorld(true);
			}
			else
			{
				FP.world = new GameOverWorld(false);
			}
			
			_boss = null;
		}

And that's it! The endTheGame() function is basically the same as a destroy() function. It just cleans every reference in the game world.

The last part: making the player lose the game if an enemy has reached the end of the screen. For this one we're going to remember that an enemy only reaches the bottom of the screen if it still has health. So, in the remove() function of GameWorld.as:

		private var _gameEnded:Boolean = false;
		
		override public function remove(e:Entity):Entity
		{
			if (e is Enemy)
			{
				if (Enemy(e).health > 0)
				{
					endTheGame();
					
					return e;
				}
				
				_enemyList.splice(_enemyList.indexOf(e), 1);
			}
			
			if (e is Boss)
			{
				_boss = null;
			}
			
			return super.remove(e);
		}
		
		override public function update():void
		{
			super.update();
			
			if (_gameEnded)
			{
				return;
			}
			
			_enemySpawnTimer--;
			
			if (_enemySpawnTimer <= 0 && !_bossAppeared)
			{
				_enemySpawnTimer = _enemySpawnInterval;
				
				_enemyList.push(new Enemy(uint(Math.random() * 30), generateEnemyPath(2), this));
				
				add(_enemyList[_enemyList.length - 1]);
			}
			
			_bulletList = new Vector.<Bullet>();
			
			getType("PlayerBullet", _bulletList);
			
			for each (var bullet:PlayerBullet in _bulletList)
			{
				for each (var enemy:Enemy in _enemyList)
				{
					if (bullet.collideWith(enemy, bullet.x, bullet.y))
					{
						enemy.takeDamage();
						
						remove(bullet);
						
						bullet.destroy();
					}
				}
				
				if (_bossAppeared)
				{
					if (bullet.collideWith(_boss, bullet.x, bullet.y))
					{
						_boss.takeDamage();
						
						if (!_boss)
						{
							endTheGame();
							
							return;
						}
						
						remove(bullet);
						
						bullet.destroy();
					}
				}
			}
			
			_bulletList = new Vector.<Bullet>();
			
			getType("BossBullet", _bulletList);
			
			for each (var bossBullet:BossBullet in _bulletList)
			{
				if (bossBullet.collideWith(_playerShip, bossBullet.x, bossBullet.y))
				{
					endTheGame();
					
					return;
				}
			}
			
			if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5))
			{
				_playerShip.speedMultiplier += 0.1;
				
				_speedUpgradeNumber++;
			}
			
			if (_score.score >= 25)
			{
				_playerShip.hasDoubleShoot = true;
			}
			
			if (_score.score > _enemyUpgradeNumber)
			{
				Enemy.damageMultiplier -= 0.015;
				Enemy.healthMultiplier += 0.02;
				
				_enemySpawnInterval -= 3;
				
				_enemyUpgradeNumber++;
			}
			
			if (_score.score == 50 && !_bossAppeared)
			{
				add(_boss);
				
				_bossAppeared = true;
			}
		}
		
		public function endTheGame():void
		{
			removeAll();
			
			while (_bulletList.length > 0)
			{
				_bulletList[0].destroy();
				
				_bulletList.shift();
			}
			
			while (_enemyList.length > 0)
			{
				_enemyList[0].destroy();
				
				_enemyList.shift();
			}
			
			_bulletList = null;
			_enemyList = null;
			_playerShip = null;
			_score = null;
			
			if (_boss)
			{
				FP.world = new GameOverWorld(true);
			}
			else
			{
				FP.world = new GameOverWorld(false);
			}
			
			_boss = null;
			
			_gameEnded = true;
		}

This code will only end the game when an enemy reaches the end (is destroyed) with a health above 0. If the enemy dies, this function is also called, but then the enemy health will be below (or equal to) 0, skipping the code to end the game. We have also created the _gameEnded boolean because when an enemy reaches the end of the screen and gets removed, the world is still updating its entities. Only when it finished updating them (after the super.update() call in the class) is that we can end the game.

After all these lines and lines of code changed, remove the FlashPunk console, hit compile and test the game. It's finally done, your very first game entirely done in FlashPunk! Congratulations!


Step 16: Conclusion

Congratulations, you have created your first FlashPunk game! You have used pretty much all of FlashPunk's features for a very basic game, which means you are ready to create more FlashPunk games and spread the word! What do you think of improving this game? It could have different enemies, enemies that also shoot bullets, levels, more bosses, more upgrades and a lot of other things! Are you up to the challenge?

Advertisement