An Introduction to FlashPunk: Creating a Spaceship Shoot-’Em-Up
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: 400×500 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:

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:

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:

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:

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:

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:

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:

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:

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:



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!

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:

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?
View full post on Activetuts+

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: 400×500 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: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
Enginestarts. You’ll get the following: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’sEngineand NOT using theFP.elapsedproperty.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.Inputin 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:
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
Enemyclass is done. Now it’s time to modifyGameWorlda 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:
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 currentremove()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:
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 thegenerateBulletPath()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” toEnemyand “PlayerBullet” toPlayerBullet.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,EnemyandPlayerBullet, 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, inGameWorld.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 thetakeDamage()function of theEnemyclass, 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:
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 ofExplosionand add it to the world. TheExplosionclass 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
callbackparameter of theSpritemapclass: when the animation ends, that function will be called, and then it removes itself from the world.Now, back to
Enemy.asto 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
Textclass. Start by creating aGameScoreclass, which will hold the score of the game. SinceTextis aGraphic, we will makeGameScoreanEntityand add its text as agraphic. 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. InGameWorld.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:
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
PlayerShipclass 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
GameWorldin 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:
Create the
MainMenuWorldclass, which extends fromnet.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:In order to create the play button, we are going to use the
Buttonclass created in the first part of this tutorial series. Here’s the code for the button inMainMenuWorld.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
Mainclass 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:
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!
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
Bossclass!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:
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) ofPlayerBulletand copy it to a new class calledBullet. 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
PlayerBulletclass? Put only the things related to the bullet image in there, and remove the rest. And also make it inherit fromBullet: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
BossBulletclass 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. InBoss.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 inGameWorld.as. This function will basically remove every bullet from the screen, remove the player and boss and then add the game over screen. InGameWorld.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 adestroy()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 ofGameWorld.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
_gameEndedboolean 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 thesuper.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?
In the previous session I went over how to create a basic TimelineLite. Today I will show you the methods and properties that you will use to control the playback of your TimelineLite. By combining these methods and properties you can extend the built in functionality of TimelineLite to create fast-forward and play/pause toggle controls. I’ll also show you how easy it is to set up a Slider component to use as a TimelineLite scrubber.
TimelineLite in Action
Let’s take a look at the final result we will be working towards:
You can find all the files used to create the SWF above in the source files for this tutorial.
Watch the Screencast
Don’t like ads? Download the screencast, or subscribe to Activetuts+ screencasts via iTunes!
TimelineLite Playback Methods
The following methods give you precise control over the playback of your TimelineLite
reverse( false ).Custom Playback Controls
By combining the built-in methods and properties its easy to create advanced functionality:
Fast-Forward – You can create a fast-forward control by increasing the
timeScaleproperty and forcing playback in a forward direction.private function fastForwardHandler(e:MouseEvent):void { tl.timeScale = 4; tl.play(); }Play/Pause Toggle – To toggle between the playing and paused states just negate the
pausedproperty. It is important to make suretimeScaleandreversedproperties are set to the normal values as they could be altered by the fast-forward and reverse buttons respectively.private function playPauseHandler(e:MouseEvent):void { resetTimeScale(); tl.reversed = false; tl.paused = !tl.paused; }Slider Component Scrubber – The Slider component makes it fairly easy to scrub through the timeline by altering the
currentProgressof the timeline. The Slider is set to output values between 0 and 100. In the code this value gets converted to a number between 0 and 1.import fl.controls.Slider; import fl.events.SliderEvent; slider.addEventListener(SliderEvent.THUMB_DRAG, sliderChange); private function sliderChange(e:SliderEvent):void { tl.currentProgress = slider.value * .01; //forces the timeline to stop when the scrubber is released. tl.pause(); }Homework
What? You are giving me homework? Yes! I want you to be your best. Flex your mind muscle with this little challenge.
In order to really learn this material nothing is better than diving in and getting your hands a little dirty.
Conclusion
So far I’ve given you a fair amount of information on creating and controlling TimelineLite animations. Although there are some obvious similarities in the way you control TimelineLite and Flash IDE timeline animations, I really love how TimelineLite gives animators so much more control with the
reverse(),restart(), andresume()methods. ThetimeScaleandcurrentProgressproperties open up some really interesting possibilities that will be discussed even more in the future.In the next video I will be showing you how to add labels to TimelineLite instances so that you can easily navigate to certain sections of your timelines. TimelineLite labels work very similarly to frame labels in the Flash IDE but with some added functionality. It’s going to be a lot of fun.
If you have any questions or comments on this tutorial – or your homework
– feel free to post a comment below.
Thanks for watching!
Twice a month, we revisit some of our readers’ favorite posts from Activetuts+ history. This week’s retro-Active tutorial, first published in April, is a guide to Euclidean vectors: what they are, why you’d use them, and how to implement them in Flash with AS3.
Euclidean vectors are objects in geometry with certain properties that are very useful for developing games. They can be seen as points, but they also have a magnitude and a direction. They are represented as arrows going from the initial point to the final point, and that’s how we will draw them in this article.
Euclidean vectors are commonly used in mathematics and physics for a lot of things: they can represent velocity, acceleration and forces in physics, or help prove a lot of important theorems in mathematics. In this tutorial, you’ll learn about Euclidean vectors, and build a class that you can use in your own Flash projects.
Please note that Euclidean vectors are different than ActionScript’s Vector class, and also different than vector drawing.
Vectors can be used in the Flash environment to help you achieve complex tasks that would otherwise require a lot of effort if done without them. In this article you will learn how to use them in Flash, as well as learn a lot of cool tricks with vectors.
Step 1: Cartesian Coordinates and Flash’s Coordinates
Before jumping into vectors, let’s introduce Flash’s coordinate system. You are probably familiar with the Cartesian coordinate system (even if you don’t know it by name):
Flash’s system is very similar. The only difference is that the y-axis is upside-down:
When we start working with vectors in flash, we need to remember that. However, good news: this different system doesn’t make much difference. Working with vectors in it will be basically like working with vectors in the Cartesian system.
Step 2: Defining a Vector
For the purpose of this tutorial, we will define and work with all vectors’ initial points as being the registration point of the stage, just as they are commonly used in mathematics. A vector will then be defined just like a common point, but it will have magnitude and angle properties. Take a look at some example vectors defined in the stage:
As you can see, a vector is represented by an arrow, and each vector has a certain length (or magnitude) and points along a certain angle. The tail of each vector is at the registration point
(0, 0).We will create a simple EuclideanVector class for this tutorial, using the Point class to hold the vector’s coordinates. Let’s create the basic vector class now:
package { import flash.geom.Point; public class EuclideanVector { public var position:Point; public var magnitude:Number; public var angle:Number; public function EuclideanVector(endPoint:Point) { position = endPoint; } } }During this tutorial, we will talk about the sense and the direction of a vector. Note that the direction just defines a line that “contains” the vector. The sense is what defines which way the vector points along this line.
Step 3: Inverse of a Vector
In this tutorial we will use the expression “inverse of a vector”. The inverse of a vector is another vector with the same magnitude and direction, but a contrary sense. That translates to a vector with the opposite signal of the first vector’s coordinates. So a vector with an endpoint of (x, y) would have an inverse vector with an endpoint of (-x, -y).
Let’s add a function to our EuclideanVector class to return the inverse vector:
public function inverse():EuclideanVector { return new EuclideanVector(new Point(-position.x, -position.y)); }Step 4: Basic Operations Addition
Now that we have learned how to define a vector, let’s learn how to add two vectors: it’s as simple as adding their coordinates separately. Look at this image:
If you notice in the image, the result of the addition of two vectors is another vector, and you can see that its coordinates are the sum of the coordinates of the other two vectors. In code, it would look like this:
public function sum(otherVector:EuclideanVector):EuclideanVector { position.x += otherVector.position.x; position.y += otherVector.position.y; return this; }So we can say that:
Step 5: Basic Operations Subtraction
Subtraction works almost the same as addition, but instead we will be adding the inverse of the second vector to the first vector.
It is already known how to sum two vectors, so here’s the code for subtraction:
public function subtract(otherVector:EuclideanVector):EuclideanVector { position.x -= otherVector.position.x; position.y -= otherVector.position.y; return this; }This code is extremely useful to get a vector that goes from the point of a vector to the point of another. Look again at the image and you will see this is true. It will be used a lot in the later examples.
Step 6: Basic Operations Multiplication by a Number
The multiplication between a vector and a number (regular numbers are known as “scalars” in vector math) results in a vector which has had magnitude multiplied by this number, but still pointing in the same direction; it’s “stretched” if the scalar is larger than 1, and squashed if the scalar is between 0 and 1. The sense of the new vector will be the same as the original vector if the scalar is positive, or the opposite if negative. Basically, this number “scales” the vector. Look at the picture:
In the code, we only multiply a vector’s coordinates by the number, which will then scale the vector:
public function multiply(number:Number):EuclideanVector { position.x *= number; position.y *= number; return this; }Step 7: Getting a Vector’s Magnitude
In order to get a vector’s magnitude, we will use the Pythagorean theorem. If you forgot what is it, here is a quick refresher:
(More info here.)
The code is very simple:
public function magnitude():Number { return Math.sqrt((position.x * position.x) + (position.y * position.y)); }You should also remove the line
public var magnitude:Number, as this is what we’ll use from now on.The magnitude of a vector will always be positive, since it is the square root of the sum of two positive numbers.
Step 8: Getting the Angle of a Vector
The angle of a vector is the angle between the x-axis and the vector’s direction line. The angle is measured going from the x-axis and rotating anti-clockwise until the direction line in the cartesian system:
However, in Flash’s coordinate system, since the y-axis is upside down, this angle will be measured rotating clockwise:
This can be easily calculated using the following code. The angle will be returned in radians, in a range from 0 to 2pi. If you don’t know what radians are or how to use them, this tutorial by Michael James Williams will help you a lot.
public function angle():Number { var angle:Number = Math.atan2(position.y, position.x); if (angle < 0) { angle += Math.PI * 2; } return angle; }Step 9: Dot Product
The dot product between two vectors is a number with apparently no meaning, but it has two useful uses. Let’s first take a look at how the dot product can be calculated:
But it also can be obtained by each vector’s coordinates:
The dot product can tell us a lot about the angle between the vectors: if it’s positive, then the angle ranges from 0 to 90 degrees. If it’s negative, the angle ranges from 90 to 180 degrees. If it’s zero, the angle is 90 degrees. That happens because in the first formula only the cosine is responsible for giving the dot product a “signal”: the magnitudes are always positive. But we know that a positive cosine means that the angle ranges from 0 to 90 degrees, and so on for negative cosines and zero.
The dot product can also be used to represent the length of a vector in the direction of the other vector. Think of it as a projection. This proves extremelly useful in things like the Separation of Axis Theorem (SAT) and its implementation in AS3 for collision detection and response in games.
Here is the practical code to get the dot product between two vectors:
public function dot(otherVector:EuclideanVector):Number { return (position.x * otherVector.position.x) + (position.y * otherVector.position.y); }Step 10: Smallest Angle Between Vectors
The angle between vectors, as seen in Step 9, can be given by the dot product. Here is how to calculate it:
public function angleBetween(otherVector:EuclideanVector):Number { return Math.acos(dot(otherVector) / (magnitude() * otherVector.magnitude())); }Step 11: Ranged Angle Between Vectors
There is also another way to calculate the angle, which gives results between -pi and pi and always calculates the angle that goes from the first vector to the second vector; this is useful when you want to easily integrate with a display object’s rotation (which ranges from -180 to 180).
The method works by getting the angle for both vectors, then subtracting the angles and working on the result.
The code:
public function rangedAngleBetween(otherVector:EuclideanVector):Number { var firstAngle:Number; var secondAngle:Number; var angle:Number; firstAngle = Math.atan2(otherVector.position.y, otherVector.position.x); secondAngle = Math.atan2(position.y, position.x); angle = secondAngle - firstAngle; while (angle > Math.PI) angle -= Math.PI * 2; while (angle < -Math.PI) angle += Math.PI * 2; return angle; }Note that this angle returns positive if secondAngle is higher than firstAngle, so the order in which you get the ranged angle will affect the result!
Step 12: Normalizing a Vector
Normalizing a vector means making its magnitude be equal to 1, while still preserving the direction and sense of the vector. In order to do that, we multiply the vector by
1/magnitude. That way, its magnitude will be reduced, or increased, to 1.public function normalize():EuclideanVector { var m:Number = magnitude(); position.x /= m; position.y /= m; return this; }Step 13: Getting the Normal of a Vector
The normal of a vector is another vector that makes a 90 degree angle to the first. It can be calculated by the following formulas:
The formulas rely on the fact that, since the normal is always perpendicular to a vector, we only need to change the order of the x and y coordinates and invert one of them in order to get a normal. The following image shows the process:
In the image, Vec is the original vector, Vec2 is the vector with Vec‘s swapped coordinates, and Vec3 is a vector with Vec2‘s negative y coordinate. Ang and Ang2 are variable, but the angle between Vec and Vec3 is always 90 degrees.
And the code is simple
public function normalRight():EuclideanVector { return new EuclideanVector(new Point(-position.y, position.x)); } public function normalLeft():EuclideanVector { return new EuclideanVector(new Point(position.y, -position.x)); }Step 14: Rotating a Vector
In order to rotate a vector, we assume the (0, 0) position (its initial point) will be the rotation center. The rotated point is given by the formula:
This formula is obtained by applying a rotation matrix to that vector. We would be going beyond the scope of this tutorial if we went into the matrix and how it works, so I will just leave the formula here.
The code is pretty much the same:
public function rotate(angleInRadians:Number):EuclideanVector { var newPosX:Number = (position.x * Math.cos(angleInRadians)) - (position.y * Math.sin(angleInRadians)); var newPosY:Number = (position.x * Math.sin(angleInRadians)) + (position.y * Math.cos(angleInRadians)); position.x = newPosX; position.y = newPosY; return this; }This is the end of our basic vector operations. What you will see next is ways to use this class to do interesting things. Here is our class so far:
package { import flash.geom.Point; public class EuclideanVector { public var position:Point; public var angle:Number; public function EuclideanVector(endPoint:Point) { position = endPoint; } public function inverse():EuclideanVector { return new EuclideanVector(new Point(-position.x, -position.y)); } public function sum(otherVector:EuclideanVector):EuclideanVector { position.x += otherVector.position.x; position.y += otherVector.position.y; return this; } public function subtract(otherVector:EuclideanVector):EuclideanVector { position.x -= otherVector.position.x; position.y -= otherVector.position.y; return this; } public function multiply(number:Number):EuclideanVector { position.x *= number; position.y *= number; return this; } public function magnitude():Number { return Math.sqrt((position.x * position.x) + (position.y * position.y)); } public function angle():Number { var angle:Number = Math.atan2(position.y, position.x); if (angle < 0) { angle += Math.PI * 2; } return angle; } public function dot(otherVector:EuclideanVector):Number { return (position.x * otherVector.position.x) + (position.y * otherVector.position.y); } public function angleBetween(otherVector:EuclideanVector):Number { return Math.acos(dot(otherVector) / (magnitude() * otherVector.magnitude())); } public function rangedAngleBetween(otherVector:EuclideanVector):Number { var firstAngle:Number; var secondAngle:Number; var angle:Number; firstAngle = Math.atan2(otherVector.position.y, otherVector.position.x); secondAngle = Math.atan2(position.y, position.x); angle = secondAngle - firstAngle; while (angle > Math.PI) angle -= Math.PI * 2; while (angle < -Math.PI) angle += Math.PI * 2; return angle; } public function normalize():EuclideanVector { position.x /= magnitude(); position.y /= magnitude(); return this; } public function normalRight():EuclideanVector { return new EuclideanVector(new Point(-position.y, position.x)); } public function normalLeft():EuclideanVector { return new EuclideanVector(new Point(position.y, -position.x)); } public function rotate(angleInRadians:Number):EuclideanVector { var newPosX:Number = (position.x * Math.cos(angleInRadians)) - (position.y * Math.sin(angleInRadians)); var newPosY:Number = (position.x * Math.sin(angleInRadians)) + (position.y * Math.cos(angleInRadians)); position.x = newPosX; position.y = newPosY; return this; } } }OK, we’ve covered building the vector class, now let’s take a took at utilising it.
Step 15: Determining Whether a Point is Inside a Polygon
The action begins here. Determining whether a point lies inside a polygon or not is a very interesting topic, and there are many methods of achieving it. In this article I will present the three methods that are generally used:
All these algorithms will rely on the fact that you know the coordinates of the vertices (corners) that define the polygon.
Step 16: The Crossing Number or Even-Odd Rule Algorithm
This algorithm can be used for any shape. This is what you read: any shape, have it holes or not, be it convex or not. It is based on the fact that any ray cast from the point you want to check out to infinity will cross an even number of edges if the point is outside the shape, or odd number of edges if the point is inside the shape. This can be proven by the Jordan curve theorem, which implies that you will have to cross a border between some region and other region if you want to move from one to other. In our case, our regions are “inside the shape” and “outside the shape”.
The code for this algorithm is the following:
public function isPointInsideShape1(point:EuclideanVector, shapeVertices:Vector.<EuclideanVector>):Boolean { var numberOfSides:int = shapeVertices.length; var i:int = 0; var j:int = numberOfSides - 1; var oddNodes:Boolean = false; while (i < numberOfSides) { if ((shapeVertices[i].position.y < point.position.y && shapeVertices[j].position.y >= point.position.y) || (shapeVertices[j].position.y < point.position.y && shapeVertices[i].position.y >= point.position.y)) { if (shapeVertices[i].position.x + (((point.position.y - shapeVertices[i].position.y) / (shapeVertices[j].position.y - shapeVertices[i].position.y)) * (shapeVertices[j].position.x - shapeVertices[i].position.x)) < point.position.x) { oddNodes = !oddNodes; } } j = i; i++; } return oddNodes; }It will return
falseif the point is not inside the shape, ortrueif the point is inside the shape.Step 17: The Winding Number Algorithm
The winding number algorithm use the sum of all the angles made between the point to check and each pair of points that define the polygon. If the sum is close to 2pi, then the point being checked is inside the vector. If it is close to 0 then the point is outside.
public function isPointInsideShape2(point:EuclideanVector, shapeVertices:Vector.<EuclideanVector>):Boolean { var numberOfSides:int = shapeVertices.length; var i:int = 0; var angle:Number = 0; var rawAngle:Number = 0; var firstVector:EuclideanVector; var secondVector:EuclideanVector; while(i < numberOfSides) { firstVector = new EuclideanVector(new Point(shapeVertices[i].position.x - point.position.x, shapeVertices[i].position.y - point.position.y)); secondVector = new EuclideanVector(new Point(shapeVertices[(i + 1) % numberOfSides].position.x - point.position.x, shapeVertices[(i + 1) % numberOfSides].position.y - point.position.y)); angle += secondVector.rangedAngleBetween(firstVector); i++; } if(Math.abs(angle) < Math.PI) return false; else return true; }The code uses the ranged angle between vectors and gives space for imprecisions: notice how we are checking the results of the sum of all angles. We do not check if the angle is exactly zero or 2pi. Instead, we check if it is less than pi and higher than pi, a considerable median value.
Step 18: The Concave Polygon Algorithm
The concave polygon algorithm relies on the fact that, for a concave polygon, a point inside it is always to the left of the edges (if we are looping through them in a counter-clockwise sense) or to the right of the edges (if we are looping through them in a clockwise sense).
Imagine standing in a room shaped like the image above, and walking around the edges of it with your left hand trailing along the wall. At the point along the wall where you are closest to the point you are interested in, if it’s on your right then it must be inside the room; if it’s on your left then it must be outside.
The problem lies in determining whether a point is to the left or right of an edge (which is basically a vector). This is done through the following formula:
That formula returns a number less than 0 for points to the right of the edge, and greater than 0 for points to the left of it. If the number is equal to 0, the point lies on the edge, and is considered inside the shape. The code is the following:
public function isPointInsideShape3(point:EuclideanVector, shapeVertices:Vector.<EuclideanVector>):Boolean { var numberOfSides:int = shapeVertices.length; var i:int = 0; var firstEdgePoint:EuclideanVector; var secondEdgePoint:EuclideanVector; var leftOrRightSide:Boolean; while(i < numberOfSides) { firstEdgePoint = shapeVertices[i]; secondEdgePoint = shapeVertices[(i + 1) % numberOfSides]; if(i == 0) { // Determining if the point is to the left or to the right of first edge // true for left, false for right leftOrRightSide = ((point.position.y - firstEdgePoint.position.y) * (secondEdgePoint.position.x - firstEdgePoint.position.x)) - ((point.position.x - firstEdgePoint.position.x) * (secondEdgePoint.position.y - firstEdgePoint.position.y)) > 0; } else { // Now all edges must be on the same side if(leftOrRightSide && ((point.position.y - firstEdgePoint.position.y) * (secondEdgePoint.position.x - firstEdgePoint.position.x)) - ((point.position.x - firstEdgePoint.position.x) * (secondEdgePoint.position.y - firstEdgePoint.position.y)) < 0) { // Not all edges are on the same side! return false; } else if(!leftOrRightSide && ((point.position.y - firstEdgePoint.position.y) * (secondEdgePoint.position.x - firstEdgePoint.position.x)) - ((point.position.x - firstEdgePoint.position.x) * (secondEdgePoint.position.y - firstEdgePoint.position.y)) > 0) { // Not all edges are on the same side! return false; } } i++; } // We looped through all vertices and didn't detect different sides return true; }This code works regardless of whether you have the shape’s vertices defined clockwise or counter-clockwise.
Step 19: Ray Casting
Ray casting is a technique often used for collision detection and rendering. It consists of a ray that is cast from one point to another (or out to infinity). This ray is made of points or vectors, and generally stops when it hits an object or the edge of the screen. Similarly to the point-in-shape algorithms, there are many ways to cast rays, and we will see two of them in this post:
In the next two steps we will look into both methods. After that, we will see how to make our ray stop when it hits an object. This is very useful when you need to detect collision against fast moving objects.
Step 20: The Bresenham’s Line Algorithm
This algorithm is used very often in computer graphics, and depends on the convention that the line will always be created pointing to the right and downwards. (If a line has to be created to the up and left directions, everything is inverted later.) Let’s go into the code:
public function createLineBresenham(startVector:EuclideanVector, endVector:EuclideanVector):Vector.<EuclideanVector> { var points:Vector.<EuclideanVector> = new Vector.<EuclideanVector>(); var steep:Boolean = Math.abs(endVector.position.y - startVector.position.y) > Math.abs(endVector.position.x - startVector.position.x); var swapped:Boolean = false; if (steep) { startVector = new EuclideanVector(new Point(startVector.position.y, startVector.position.x)); endVector = new EuclideanVector(new Point(endVector.position.y, endVector.position.x)); } // Making the line go downward if (startVector.position.x > endVector.position.x) { var temporary:Number = startVector.position.x; startVector.position.x = endVector.position.x; endVector.position.x = temporary; temporary = startVector.position.y; startVector.position.y = endVector.position.y endVector.position.y = temporary; swapped = true; } var deltaX:Number = endVector.position.x - startVector.position.x; var deltaY:Number = Math.abs(endVector.position.y - startVector.position.y); var error:Number = deltaX / 2; var currentY:Number = startVector.position.y; var step:int; if (startVector.position.y < endVector.position.y) { step = 1; } else { step = -1; } var iterator:int = startVector.position.x; while (iterator < endVector.position.x) { if (steep) { points.push(new EuclideanVector(new Point(currentY, iterator))); } else { points.push(new EuclideanVector(new Point(iterator, currentY))); } error -= deltaY; if (error < 0) { currentY += step; error += deltaX; } iterator++; } if (swapped) { points.reverse(); } return points; }The code will produce an AS3 Vector of Euclidean vectors that will make the line. With this Vector, we can later check for collisions.
Step 21: The DDA Method
An implementation of the Digital Differential Analyzer is used to interpolate variables between two points. Unlike the Bresenham’s line algorithm, this method will only create vectors in integer positions for simplicity. Here’s the code:
public function createLineDDA(startVector:EuclideanVector, endVector:EuclideanVector):Vector.<EuclideanVector> { var points:Vector.<EuclideanVector> = new Vector.<EuclideanVector>(); var dx:Number; var dy:Number; var _x:Number = startPoint.position.x; var _y:Number = startPoint.position.y; var m:Number; var i:int; dx = endPoint.position.x - startPoint.position.x; dy = endPoint.position.y - startPoint.position.y; if (Math.abs(dx) >= Math.abs(dy)) m = Math.abs(dx); else m = Math.abs(dy); points.push(new EuclideanVector(new Point(int(_x), int(_y)))); i = 1; while (i <= m) { _x += dx / m; _y += dy / m; points.push(new EuclideanVector(new Point(int(_x), int(_y)))); i++; } return points; }This code will also return an AS3 Vector of Euclidean vectors.
Step 22: Checking for Collisions Using Rays
Checking collision via rays is very simple. Since a ray consists of many vectors, we will check for collisions between each vector and a shape, until one is detected or the end of the ray is reached. In the following code,
shapeToCheckwill be a shape just like the ones we have been using in Steps 13-16. Here’s the code:public function checkRayCollision(ray:Vector.<EuclideanVector>, shape:Vector.<EuclideanVector>):Boolean { var rayLength:int = ray.length; var i:int = 0; while(i < rayLength) { if(isPointInsideShape1(ray[i], shape)) { return true; } i++; } return false; }You can use any point-inside-shape function you feel comfortable with, but pay attention to the limitations of the last one!
Conclusion
You’re ready to start using this knowledge everywhere now! It will be useful many times, and will save you a lot of extra calculations when trying to do more complex things in Flash.
I recently bought my first Bamboo, a Wacom tablet that recognises letters from shapes drawn with the stylus. It tickled memories of my first experience with gesture-controlled application: using mouse gestures, web browsers such as Maxthon (and later Opera) allowed users to quickly move back and forth through webpages in the history, switch between different tabs, and so on. I was facinated by its neat user interface, as it takes away traditional mouse clicks. Of course, sophisticated gesture-controlled devices such as the Kinect, iPad and iPhone are now available – but it all started with the good old PC. In this tutorial, you’ll learn how to develop a photo gallery that recognises singular mouse gestures.
Final Result Preview
Let's take a look at the final result we will be working towards. To pan the photo galley in the four main directions, click and drag the mouse in the relevant direction. To scale a photo, drag the mouse South-East to scale up and drag the mouse North-West to scale back to the default size.
(Note: this doesn’t snap to center the photo when panning; it’s sensitive to the length of the line that you draw.)
Step 1: Tutorial Flow
Here’s what you’ll learn in this tutorial, and the order in which you’ll learn it:
Step 2: Gesture Detection: Vector Analysis
It is important to understand the vector math involved in detecting mouse gestures. Having understood detection of a single direction, one can easily extend the understanding to apply to all eight directions.
The Flash presentation below shows steps of detecting a single mouse gesture to due right. To scroll through frames in the Flash presentation below, (mouse down – mouse move – mouse up) in any of the following directions:
Step 3: Gesture Detection: Angle Alleviations
Implementing Step 2 will be easy. However, chances are 90% that users' gestures will fail. Diagram below shows commonly commited gestures (middle); they seldom comply with a rigid Vector pointing to due right (left). Thus, it's better to give alleviation for gesture inaccuracies (right).
For example, we can give an alleviation of 30° on both sides of the Vector pointing due right so that angle of any gesture's Vector that falls within that range will be accepted and interpreted as a gesture to due right.
Step 4: Gesture Detection: Sample Implementation
Below is an implementation of gesture detection to due right. Press down on mouse, move mouse to the right, and release mouse within the Flash presentation below. Try to gesture a little off the absolute right to check out the implementation of alleviation.
Step 5: Variables
Lets look at the variables on our hard-coded implementation in Step 4. I’ve highlighted the important Vector2D variables. Take note of comments I’ve placed at the end of each variable.
Step 6: Hard-Coded Implementation
I assume you already know the basics of putting a
TextFieldinto your project so I shall focus on the ActionScript implementation of mouse gesture. The implementation as indicated below is heavily commented. Important Vector calculations are highlighted as well. I encourage readers to examine these comments, especially those highlighted, to understand operations at different events at runtime.(The Vector2D class is the same one I’ve used in previous tutorials, like this one.)
public function HardCoded() { //Creating a textbox t = new TextField(); t.selectable = false; t.width = 300; t.x = stage.stageWidth/2; t.y = stage.stageHeight - 30; addChild(t); //Start of gesture detection stage.addEventListener(MouseEvent.MOUSE_DOWN, start); } //Register mouse location upon mouse down private function start(e:MouseEvent):void { //Register mouse suppress location earlier = new Vector2D(e.localX, e.localY); //Start to draw line graphics.lineStyle(3); graphics.moveTo(earlier.x, earlier.y); //add mouse move and mouse release listeners stage.addEventListener(MouseEvent.MOUSE_MOVE, move); stage.addEventListener(MouseEvent.MOUSE_UP, up); } //Draw gesture upon mouse move private function move(e:MouseEvent):void { graphics.lineTo(e.localX, e.localY); } //Evaluate gesture upon mouse release private function up(e:MouseEvent):void { //Register mouse release location latter = new Vector2D(e.localX, e.localY); //Calculating vector of mouse gesture var result:Vector2D = latter.minus(earlier); //Calculating angle from absolute RIGHT to gesture vector. var deviation:Number = RIGHT.angleBetween(result); deviation = Math2.degreeOf(deviation); //Interpreting gesture with alleviation if (Math.abs(deviation) < 30) t.text = "RIGHT gesture detected"; else t.text = ""; //Clear screen of previous drawing. graphics.clear(); //remove mouse move and mouse up listeners stage.removeEventListener(MouseEvent.MOUSE_MOVE, move); stage.removeEventListener(MouseEvent.MOUSE_UP, up); }Step 7: Summary
For clarification purposes, here’s a summmary of the hard-coded implementation:
Step 8: Gesture Angle Alleviations
Making an accurate gesture using a mouse is hard. It’s hard to make straight lines (East, South, West, North) but it’s even harder to make diagonal lines (South-East, South-West, North-West, North-East) because we have to estimate that extra 45°. So I have given diagonal lines more alleviations than straight lines. Notice the larger grayed angle for diagonal Vector compared to that of the straight Vector.
Step 9: Gesture Sensitivity
I'd to point out another issue – sensitive gestures. Sensitive gestures identifies gesture directions when mouse pointer makes the slightest shifts in location, even to adjacent pixels. Diagram below illustrate scenarios of sensitive gestures.
If the user changes his mind after mouse press and mouse releases immediately, a gesture will still be detected if his pointer makes a slightest move to the adjacent pixels. We should allow users to undo gesture detection. In this tutorial, I’ve enforced a minimum magnitude the Vector of current gesture must exceed to be valid. I’ve included a diagram as below.
Step 10: Class Variables
In order to detect singular mouse gestures I have implemented
MGesture. Do download and examine this ActionScript file. I shall go through its class variables first, then the class methods.mainBoxDisplayObjectContainerdirectionsVector._deviationFromMainsNumberdirections)_deviationFromDiagonalsNumberdirections)_minDistNumber_earlierVector2D_latterVector2DBelow is the code implementation of class variables. I’ve allowed a deviation of 10° from main directions. For example, -10°-10° is considered due East, -80°-100° is considered due South, etc. I’ve also allowed a deviation of 30° from diagonal directions. So a Vector with orientation between 15°-75° will be considered due South-East, and so on. Also, the minimum magnitude to exceed is 10px.
Step 11: Numbering Directions
You may have guessed it already from reference to code implementation of
directions. For clarification, here are the main directions' integer representations.Step 12: Class Methods & Property
Below are class methods for
MGesture._earlier,_latter) initiates_minDist),Vector2D_latterand returns current gesture Vector (_earlierto_latter)_minDist),Vector2D_minDistintdirectionsStep 13: Methods
The essential methods tabled in Step 12 are all documented here. Do read through the comments.
/** * Initiate variables * @param container where mouse is detected from */ public function MGesture(container:DisplayObjectContainer) { //setting container from which mouse is moving mainBox = container; } /** * Method to register initial mouse location */ public function start ():void { var startMX:Number = mainBox.mouseX; var startMY:Number = mainBox.mouseY; _earlier = new Vector2D(startMX, startMY); //pointer location, initially _latter = new Vector2D(startMX, startMY); //pointer location, to be updated later } /** * Method to update mouse location * @return a Vector2D of current mouse location relative to that when start() is called; */ public function update ():Vector2D { _latter = new Vector2D(mainBox.mouseX, mainBox.mouseY); var vecUpdate:Vector2D = _latter.minus(_earlier); return vecUpdate; } /** * Method to validate a gesture. * @param newLoc Vector2D to new mouse location * @return null if invalid gesture, a Vector2D if valid */ private function validMagnitude ():Vector2D { var gestureVector:Vector2D = update(); var newMag:Number = gestureVector.getMagnitude(); //if magnitude condition is not fulfilled, reset gestureVector to null if (newMag < _minDist) gestureVector = null; return gestureVector; } /** * Method to evaluate gesture direction * @return Integer indicative of direction. Invalid gesture, -1. */ public function evalDirections():int { //Pessimistic search (initialise with unsuccessful search) var detectedDirection:int = -1; //validate magnitude condition var newDirection:Vector2D = validMagnitude(); //if gesture exceed minimum magnitude if (newDirection != null) { //evaluation against all directions for (var i:int = 0; i < directions.length; i++) { var angle:Number = directions[i].angleBetween(newDirection); angle = Math.abs(angle); //check against main directions if ( i < 4 && angle < _deviationFromMains) { detectedDirection = i; break; } //check against diagonal directions else if (i > 3 && angle < _deviationFromDiagonals) { detectedDirection = i; break; } } //update mouse location for next evaluation _earlier = _latter; } //return detected direction return detectedDirection }Step 14: Photo Gallery
Now that the
MGestureclass is set, we shall proceed with an demo application (photo gallery) of it. I have included a source file here. Do download and follow along. First of all, get all images into the "lib" folder in your existing project. Images I’ve used here are courtesy of my wife and daughter.Step 15: Embed Images
Create a new Actionscript class and name it
PhotoView. We shall use these images to construct our gallery.Classobject.ClassintoBitmapobjects so that we can manipulate it further.Bitmapobjects in aVectorarray for easy selection later.Step 16: Display List Management
It is important to clarify here the management of
PhotoView's display list. I’ve included a Flash presentation here. To use it, make a gesture to right or left. For further details, refer to Step 2.Step 17: Positioning Image
In the
PhotoView's constructor, we initiate all necessary display objects and position them into place. Plus, we initiateMGestureand attach events listeners to start gesture detection. I’ve highlighted the event listeners. Their details are explained over the next two steps.public function PhotoView() { panel = new Sprite(); //Initate panel panel.x = stage.stageWidth / 2; //Centering panel horizontally panel.y = stage.stageHeight / 2; //Centering panel vertically addChild(panel); var currentBmp:int = 0; //Current image selected for positioning var bmpGaps:Number = 60; //Spacing between images var bmpOnX:int = 3; //Number of images on horizontal axis var bmpOnY:int = 3; //Number of images on vertical axis var bmp:Bitmap; //Bitmap object to hold image var container:Sprite; //Sprite container to hold bmp //scrolling through Y for (var j:int = -1*Math.floor(bmpOnY/2); j < Math.ceil(bmpOnY/2); j++) { //scrolling through X for (var i:int = -1*Math.floor(bmpOnX/2); i < Math.ceil(bmpOnX/2); i++) { bmp = list[currentBmp]; bmp.x = -1 * bmp.width / 2; //Bitmap centered horizontally in container bmp.y = -1 * bmp.height / 2; //Bitmap centered vertically in container container = new Sprite(); container.x = (bmp.width + bmpGaps )* i; //Positioning container on x accordingly container.y = (bmp.height + bmpGaps )* j; //Positioning container on y accordingly container.addChild(bmp); //Add bitmap into container container.addEventListener(MouseEvent.MOUSE_DOWN, select); panel.addChild(container); //Add container into panel currentBmp++ //Scroll to next bitmap } } gesture = new MGesture(stage); //Initiate MGesture for gesture detection stage.addEventListener(MouseEvent.MOUSE_DOWN, start); }Step 18: Selecting Image to Scale
Line 99 highlighted is not related to detection of gesture, but merely to select image for scaling & placing it on top of all other images.
private function select(e:MouseEvent):void { //Setting current image to scale & //Placing it on top of all other images ImgSelected = e.currentTarget as Sprite; panel.swapChildrenAt(panel.numChildren - 1, panel.getChildIndex(ImgSelected)); }Step 19: Start, End and Evaluate Mouse Gesture
First function below is executed upon mouse down. The second is executed upon mouse up. I’ve highlighted
start()andevalGesture()as well as evant listeners.private function start(e:MouseEvent):void { //Start gesture detection & //Listen for mouse up event gesture.start(); stage.addEventListener(MouseEvent.MOUSE_UP, end); } private function end(e:MouseEvent):void { //Prepare current gesture's magnitude for animation purpose //implement a maximum cap on gesture's magnitude gestureMag = gesture.update().getMagnitude() / 2; gestureMag = Math.min(gestureMag, maxMag); //Evaluate current gesture direction = gesture.evalGesture(); //Once a valid gesture is detected, perform animation //No further gestures will be detected until animation ends if (direction > -1) { stage.addEventListener(Event.ENTER_FRAME, move); stage.removeEventListener(MouseEvent.MOUSE_DOWN, start); } }Step 20: Animating the Panel and Images
Once the directions has been detected, animation will begin. Depending on the gesture made, the whole panel may move in four directions or one single image may enlarge or shrink.
private function move(e:Event):void { var currentMag:Number //Motion of panel translation if (direction < 4) { //Function of easing motion currentMag = gestureMag * Math.cos(currentAngle += 0.1); if (direction == 0) panel.x += currentMag; else if (direction == 1) panel.y += currentMag; else if (direction == 2) panel.x -= currentMag; else if (direction == 3) panel.y -= currentMag; } //Motion of image scaling else { //Setting a maximum cap on motion gestureMag = Math.min(0.30, gestureMag); //Function of easing motion currentMag = gestureMag * Math.cos(currentAngle += 0.1); //Conditions to scale up: //Gesture is to South-East & //Image is not scaled up already if (direction == 4 && ImgSelected.scaleX < 1.30){ ImgSelected.scaleX = ImgSelected.scaleY = -1 * currentMag + 1.30 } //Conditions to scale down: //Gesture is to North-West & //Image is scaled up else if (direction == 6 && ImgSelected.scaleX > 1){ ImgSelected.scaleX = ImgSelected.scaleY = currentMag + 1; } } //If angle on easing function exceeds 90 degrees/ 0.5 Pi radian, //stop animation & enable gesture detection if (currentAngle > Math.PI/2) { stage.removeEventListener(Event.ENTER_FRAME, move); stage.addEventListener(MouseEvent.MOUSE_DOWN, start); direction = -1; //Reset direction currentAngle = 0; //Reset angle } }Step 21: Publish PhotoView
Now all is set. You may finally publish your work by pressing Ctrl + Enter on FlashDevelop. Again. here's a piece of the final product.
Conclusion
This is not the end of it. In the next part, we shall look at detection of a gesture sequence, which will be even more interesting than this part (which really just showed the basics). Do drop comments and let me know if
MGesturehad been useful to you, as well as any bugs, if you encountered any. Finally, terima kasih for the time reading. I’m hoping to entertain my fellow readers in Part 2.Have you ever dived right in to developing a game, but found yourself having to constantly change aspects of the design or the gameplay due to a lack of planning? You should consider using a game design document: a guiding vision of the game as a whole, pulling together ideas and plans for the design, development, and business sides of your game.
Introduction
To put it simply: we like to tell stories. Some true, some not so much. But the point is that we have been crafting tales for a very long time, and as time went by these tales began to evolve, becoming more complex, with richer details, with more and more fantastic backgrounds and appealing plots. Whole new worlds were born from thin air, hammered into shape in the anvils of the human brain.
And as the stories grew in complexity, so did the tools used in their making. Art diverged into several different categories, music became more elaborated and movies found their way into the world. Technological enhancements allowed sharing of information, spreading art all around the globe. New fantasy worlds were created each day. Worlds so rich made people began to desire becoming a part of them. A new concept was being brought to life.
Although video games were first just about getting the highest score possible when faced with a determined task, developers soon realized the endless possibilities laying ahead of them. Playing a video game is more than simply sitting through another story. For the first time one could have a say in how the tale told itself. Players could take hold of characters and live the hardships of the journey themselves, diving into that particular world and mastering it, making theirs the protagonist’s conquests and failures.
A game has the potential to bond player and story in a way never seen before. This connection can be established in a variety of ways. Be it the fantastic landscapes in which the story unravels, the soundtracks or the well-constructed personality of a particular character. It forces the player to thrive in order to see more of what he wants.
Unfortunately, since a game is composed of so many different elements, different experts from different areas are required in its creation, making the coordination of the development process a rather tricky job. In order to help developers do their job, a document known as a GDD, or Game Design Document, is often employed.
A GDD is a tool that helps merging the components of a game together. It registers the general ideas of every aspect of it, from graphics design to story line. In short it registers the game concept, creating the closer feeling of the finished product.
Although the writing of a GDD is not a vital part of the creation process, it is of major help to the team of developers, especially when in major projects, involving large amounts of personnel. Also, there is not only one way of writing a GDD. In fact, GDDs differ vastly among game development companies, but as a general rule, most games are built around these documents.
So without further ado, here is what you need to know about this important tool.
Overview
A Game Design Document must teach everyone who reads it how the game that you’re talking about works. In order to do this, you need to explain not just the mechanics, but also how the game’s objects (characters, enemies, puzzles, weapons, environment, and so on) interact with each other, what your game is about, and how it looks. In a GDD, these points are discussed in some general sections.
Marketing
Marketing is a big section subdivided in many subsections that explain the major commercial aspects of the game, like public target, deadlines, competitors and selling points. This section is very helpful for business, since it shows what your game has in advantage over others and how it meets the consumer demand. In others words, it shows the game’s appeal.
High Concept
Before you start to tell the reader how your game works, you must clarify the core concept of your game, i.e., you must talk about the major aspects of your game in a very short way, so that the reader can anticipate what will be said in the GDD and pay attention to what is important to the game. For this, there is the High Concept section, which explains all of it, so that the reader won’t have to read many pages of the document just to know what your game is about.
For example: if you tell the reader that your project is a futuristic space shooter game, he will be able to imagine what kind of weapons, movements, enemies and others things will be used in the game.
Gameplay
This section is one of the most important in the GDD, because it explains how to control the objects in the game and how to make them interact with the other parts. Also, it explains how the player will execute the possible moves. Moreover, it’s interesting to comment the way that the game flows and what happens during the course of the game.
First Minutes
This is a subsection of the Gameplay section, and it exposes what the player will see in the game when it has just finished loading. It exposes the actions and reactions between the game and the players during this interval, helping understand the game’s progress throughout the gameplay and give a better idea of how to play it. It’s also an important subsection, since it will determine whether or not the game is fun.
Gameflow
This is a more detailed subsection of the Gameplay than the First Minutes. It describes all the options that the player can choose while he is playing. It’s a kind of flowchart that shows which reaction each option has, giving a picture of the game as a whole. Generally it shows a flow of screens (e.g. from the “Main menu” screen it goes to the “Select level” screen), but you can also put actions and consequences in it (e.g. if the player chooses the “Mage” character, all the backgrounds will have a “magical” feeling). It literally explains the way that the game flows, as the name suggests.
Victory Conditions
You also need to teach the reader in the Victory Conditions subsection, what must be done to win, when the player loses and under which conditions this happens. In other words, this section explains the goals of the game.
Number of Players
It’s important to specify how many people can play, because this implies the type of multiplayer – where applicable – that the game will support; for example: split-screen, LAN connections, Internet connections. Note: this section has influence over the Victory Conditions, since the players will need to do different things to win in a competition than in a cooperative game.
Art
Once you explained how to play your game, it’s import to show how your game will look like and which kind of art is behind it, since it’ll influence how the elements of your game’s universe will coexist, mixing the emotions of who is playing. This is a crucial point in the game’s marketing, because it shows the appearance of the game and the feelings it will pass to the player.
Technical Aspects
Another section that must be put in a GDD is the Technical aspects, since it defines the physical game requirements needed to play and specifies on which platforms the game will be developed, which engines will be used, and more. This affects the Marketing, as the kind of hardware used affects both the fanbase and the public target, i.e., the people who consume the game.
Is There a Formula?
All things said, you need to keep in mind that even if some general subsections are common between the GDDs, there is no static form to make this kind of document, and no such thing as a perfect formula. Every game designer has his own way to do this and you must discover yours. This is a hard job, but in this article we’ll give some tips explaining how to create each subsection of the GDD – however, it’s up to you to decide which of them are necessary to design your game.
Always be clear and concise in your text and use a lot of images, because they give the reader a faster and more real view about the game’s final result and they also ease the explanation about puzzles (if your game has them) and how characters, environment, monsters, screens, weapons and other objects from the game will work.
Moreover, you can also find new topics to add in your GDD, as long as it’s necessary to the understanding of the game’s core. Some things that deserve attention are the innovations and the particularities of your game. For example, if your game project brings up a new way of playing, or a specific graphic concept or if it’s focused in music (like a music game), you should discuss it in the document, to convince everyone why this innovation is a good idea.
Guidelines
A good way to start your Game Design Document is with the Marketing section, because it will be the section that your investor or client is interested in, thus allowing them to gain interest in your game faster. In indie game development, it is not a common section due to the common lack of investors, however, if you think in other projects not related to commercial purposes, such as a free game on App Store of Apple to help a charity institution, it’s important to keep track of plans related to the marketing aspect, since it will be really important to have a publishing plan.
After this, it’s important to put the High Concept, so the reader immediately will understand the core of the game and pay attention in the major aspects. You will figure out that in GDDs it’s common to always start with a basic and summarized definition of the game, and go on to present every detail step-by-step.
In the next section you should write about the Gameplay, which should include, as sub-sections, the First Minutes, the Game Flow, the Victory Conditions and Number of Players.
After that, you need to show how your game will look, so talk about the Art, using as many images as you can. In the end, you can talk about the Specific Sections, which should bring topics that explain: the innovations, the aspects that not necessarily all games have, like story, artificial intelligence, characters and others particulars things.
All the things said above are represented in the flowchart bellow, but it’s just a general schema and you can (and should) adapt to your game. Remember: there isn’t a perfect formula. Now that you have a kind of skeleton of the GDD, you will find in the Composition topic of this tutorial a more detailed explanation about what which section of a GDD holds.
Composition
Although there isn’t such a thing as a perfect formula for composing your GDD sections, it’s important for you to include some crucial topics in it, as well as avoid some major mistakes. This section teaches you how to detail the sections presented in the Overview topic, while showing examples of how it’s done and some common mistakes.
The Marketing Section
There is no correct way of dealing with this subject, since your objectives for it will depend on your game. It’s also not really needed; you can either concatenate it all in a major subsection or spread it across the document, as some of the topics discussed here have much in common with others elsewhere. Despite the way you choose to do it, some topics should always be addressed:
Target Audience
Who will play it? This is no ordinary section, so don’t settle for a simple “for children” description, for example. There are endless ways to “classify” gamers, and you must explore this. Comment on how it will appeal to each category and try not to leave anyone out; they might share little in common with your product, but they still share something.
Right:
Wrong:
(“A large audience” isn’t a valid fanbase, and it doesn’t explain why they would enjoy it. No mention of the ERSB rating or whether the game has any age-restricted material.)
Another good example:
Notice all the classifications in the example: gender, age, nationality and genre. Keep in mind that many more categories may arise depending on your game. Predictions on the ESRB rating are also welcomed, therefore some restrictions regarding violence, sexual content and language should be addressed if needed.
Platform
Extremely straight-forward section. Just enumerate the platforms that your game is being designed for. An estimate of the system requirements are also a good call. If needed, you can comment on porting the game and the difficulties involved.
Competitors
This is a key subsection of your document. In here you must compare your game to others already developed. It is important to give a small description of the game being compared to, and point the similarities between both. This is an excellent opportunity to expand the comparisons that were already made across the GDD and give the reader a better picture of what the game will actually be.
At the end summarise your product’s strong points and convince the reader why would your product sell despite its competitors. This is the trickiest part, because you must pick good opponents, otherwise the reader just won’t know what you are talking about, and still keep your game’s image shining; therefore a good writing is crucial. Your ‘adversaries’ also help on the notion of how big your market can be.
Milestone Schedule
The Milestone Schedule subsection is where you must define each necessary steps in order to develop the game, which is basically a timeline of the intended completion of phases of your game. Through that, not only you, but also the investors, can have a very rough estimate of the interval of time needed to complete the project.
Other Subsections
You may choose to add some heavy market-related topics such as Costs Overview, that can comprehend equipment costs, people costs, additional costs and expected profit.
Future Plans
Sometimes there are so many ideas to complement a game that some of it must be put aside in order to meet the tight schedule of development. This section is specifically made to store those ideas, so that you can work on it later depending on how things work out. DLCs, possible sequels, minor improvements to gameplay, graphics and so on, all comes in here. You can also gather some ideas of what to do with the game once it is finished.
Example:
The Introduction Section
The introduction section should provide the reader with a basic overview of the game itself, first with a light approach with the High Concept subsection and then with a broader one within the Summary subsection. You can also highlight the more innovative aspects of your game in a Key Features subsection.
High Concept
A one paragraph description of what your game is about. This should sound like the summary of a summary. Avoid any technical aspects, graphic or sound designs, complex gameplay features, or marketing details that aren’t strictly required (for example, if you’re making a rhythm game you should mention what kind of music style you will be using, whereas if your game is a puzzle you can just forget about music for now; it’s better to describe what type of puzzle the player will have to solve instead). The idea is to describe your game in the most non-technical and shortest way. A good tip is to use well-known games as examples for comparison, such as “X is a three-dimensional racing game with power-ups like Mario Kart”.
Right:
Wrong:
(Keep it short and simple)
Summary Overview
A more detailed description of your game, with less restrictions than the High Concept subsection. Start with the core aspects of the gameplay, describing what role the player will take, what’s his goal, what he will have to do in order to accomplish it, what will hold him back and why the game will be entertaining.
Next, do a quick introduction to the game’s setting and a brief description of the history (if any). It’s always nice to use an image instead of describing what the graphics will look like, so if you don’t have any sketches or conceptual art you should just paste pictures with similar art to what you will be using (that includes screenshots of other games as well!).
Key Features
The best way to compose this is using short topics (i.e. bullet lists) instead of long paragraphs. Basically you should tell the reader right away about all of the creative ideas you had which you thought would make your game a great game.
Right:
Wrong:
(Too many ideas at once makes the reader lose the train of thought.)
Third-Party Software Used
A little explanation of the programming languages, libraries and software you will be using to create your game, as well as the programs you will use to adjust your graphics and sound engines and any other engines your game may need (like a networking one for multiplayer games).
If you’re under some heavy software/hardware restrictions, you should specify that in here (i.e. if you’re making a game for Apple devices, you have to tell you’ll be using iOS-compatible technology). Also if your game is aimed at PCs and you have an idea what the minimum requirements will be you should note them here. Although the non-programming people of the project probably won’t understand what the heck a “NVIDIA Cg 1.2.1” is, they’ll have to know it by name since that’s what the game will run on.
The Gameplay Section
This section is designed to describe how the game will effectively work, describing the game’s objective as well as its elements (menus, victory conditions, enemies, powerups, stages, …), and the interaction between each one of these elements with the player. If you feel like one subsection, such as “Enemies”, has too much content to be just a subsection you may promote it to a section of its own.
First Minute
It’s interesting for you to describe what the player’s reaction is going to be like as soon as the game loads, such as describing whether he can start playing right away or if he can navigate through menus to change some options beforehand, whether the player will have to learn the controls by trial and error or a tutorial will be presented to him, whether all stages will be available at the get-go or if he will have to unlock them in progression, and so on. Given you have already planned some stages ahead, you could narrate a short run of the player clearing a stage, describing the enemies and/or puzzles he had to go through in said stage.
Right:
Wrong:
(Although being essentially how the game will run, it needs more details.)
Gameflow
A nice complement to the “First minute” would be the “Game Flow”, which is usually represented as a flowchart. In contrast to the previous subsection, this one won’t focus on the first impression but rather give an overview of the whole picture, showing step-by-step which actions the player can take from the moment the game is loaded to when the player hits the “exit button” – i.e., ends his gaming session – including the gameplay itself in a somewhat high concept.
Right:
(Example from Drexel Game Design’s Scavenger Hunt GDD.)
Wrong:
(Nothing THIS simple. Include, at least, all the screens that the player will run through!)
Victory Conditions
Here you state what is required for the player to clear a stage, win a match, or advance another level, whether your game is a puzzle, where the player advances to the next level when all pieces are combined in a certain way, or a sidescrolling shooter where the player advances a stage when he defeats the boss at the end, or whatever. Obviously, this depends entirely on what kind of game you’re designing.
Example:
Graphics
You can’t really provide the reader with screenshots or video footage of something you may haven’t even designed yet, so in this subsection you should simply describe how do you plan to handle your graphical engine and maybe show some sketches of your game or a few drawings in the art style you intend to use. Planning the game HUD from the beginning will save you a lot of time later on, for example.
HUDs
The head-up-display is the in-game interface the player will have when playing the game. Rather than in-game menus like settings or inventory screens, this refers specifically to the floating windows and bars which don’t normally interact with the game and serve a information-only purpose. This includes health bars, mini-maps, time counters, equipped items and their amounts, money and etc. Although the size of the HUD will vary according to the game type (MMORPGs and RTSs will have big HUDs while sidescrollers and puzzles will have very small ones) keep in mind that a HUD shouldn’t occupy too much of the screen.
Example:
Sounds
On the other hand, one cannot sketch sounds, so you’ll just have to detail your sound engine here, and maybe the style of songs your game will use. Although for most games you will simply state that there will be different background music for different situations, it goes without saying that this subsection is most important for a rhythm game.
Controls
Stating which buttons/keys do what can be troublesome in the case where a single button does more than one action (i.e. The ‘A’ button in any 3D Zelda). Start by putting a simple picture of a controller or a keyboard with each button highlighted with their function in a more general sense. After that, if your game has advanced combos or something similar to that, explain them carefully, stating under which conditions each combo is “activated”.
Example:
(Image from CrunchTime Games Inc’s Shred Nedbula document.)
Game-Specific Subsections
Puzzles could have a “Pieces” subsection, sidescrollers will probably have a “Level Design” one, space shooters may have “Enemies” and so on. As the title in bold above says, each game will have their own specific subsections, and since we can’t compose a subsection for all the possible ones that one GDD can have, we will provide you with the three bold subsections presented here as examples.
Pieces
Suppose we have a puzzle game, where the player rotates different pieces in order to create a line of matching pieces to gain points. This would be a nice subsection to show some sketches of the many different types of pieces, as well as explaining their rotation pattern, stating their points value, and maybe describe their positioning placement. Pictures are welcome as always!
Example:
(Image from Colin Fahey’s Tetris article.)
Level Design
Now let’s pretend we have a typical 2D platformer. One of the core elements of the game is the stages the player has to go through. It’s important that each stage feels unique so the player won’t feel like he’s just repeating the same thing over and over again. On the other hand, the player should still be familiar with the flow of the stage, i.e. if there’s always a checkpoint somewhere halfway through it, or some collectible items along the way.
What are the different types of enemies, terrains, doodads and power ups and do they allow the level designers to come with many different stages? You could present some beta stage diagrams to illustrate how will they be carried out.
Example:
(Map from Super Metroid; image from jansenprice.com.)
Enemies
It’s very popular for space shooters to have many kinds of enemies, each one with different attacks and movement patterns, as well as different values for health, speed and targetable area. As such, it’s no surprise you would need an extra section to present all the game’s foes and their stats. Also, you could state some of their more obscure behaviour like shooting an extra beam when their health is low and so on.
Example:
(Image from CrunchTime Games Inc’s Shred Nedbula document.)
Plot
Many games are set in fictional worlds, each with their own geography, history and characters, in which the player will undoubtedly play a large role as the protagonist. If your game has a particularly interesting setting, it would be interesting to include a little insight on the game’s storyboard, describing the protagonist’s main events during his adventures and details about the lore.
Characters
Lots of games aren’t made of enemies alone. There may be a protagonist and allies to help him overcome his foes. For example, even a tower-defense game without a controlled character can still have side-characters like a tutorial-NPC giving you tips on how to overcome certain challenges at the beginning of each stage. If you do have a protagonist that the player controls, then what’s he like? Does he have any abilities and powers? Keep in mind that this shouldn’t feel like a “How to Play” subsection.
Artificial Intelligence
Any game will need a persisting world to handle all the player’s actions to the game and the other way around. That includes enemy movements, player controls, collision handling, time counting, random number generators and many other things one could need in a game. Although people not directly related to the programming may not understand this subsection entirely, they should at least grasp the basic of it. Most of all, keep the coding out of here and simply state the enemies’ moving patterns, the chain puzzle piece falling algorithm, maybe illustrate the combat system with a flowchart and so on.
Example:
Technical Aspects Section
The technical aspects consist of a series of game data, such as the system requirements on which it will play and the framework in which it was developed, the method or algorithm it was based on, and the maximum number of elements that can be rendered on screen. The graphical technical aspects consists of software used, modeling type, art style and others according to these topics.
The system requirements are the necessary computer settings for the game to be played, like the size it occupies on the computer’s HD and how much RAM is needed.
Another important technical aspect not to forget is the ESRB (Entertainment Software Rating Board) rating (or similar), already explained earlier. Some of the ratings are shown below.
To Include or Not to Include? When? Why?
Technical aspects interest the companies that will distribute it or that will use the technology developed in the game, so always add something in it if you’re showing this to someone that will approve or disapprove the game. There has to be some care when writing technical aspects. You can write something in the wrong subject. For example: limiting the platform and distribution game mode belongs to Marketing Aspects, not to Technical Aspects.
More Examples
For professional examples of Design Documents provided by the developers, we have: Shred Nebula, Play With Fire, Grim Fandango Puzzle Document, and many more avaliable at gamepitches.com.
For more material about the structure and composition of a GDD, one could try the featured Gamasutra article The Anatomy of a Design Document, Part 1 and Part 2; the self explanatory Creating a Great Design Document; and a more general How to write an effective Design Document, which isn’t about GDD, but about software development.
More on Game Design: The Two C’s of Video Game Design.
Moreover, there are other visions of how should documentation be done in the Game Industry, as seen in Game Design Logs and Return of the GDD. Although they seem to contradict what had been told here, this should fall in a case-by-case analysis considering team size, budget and deadlines.
Conclusion
For designers who need the approval of an investor: truth be told, before you can make any progress with an investor, you must first get his attention, and to do so, the following key points of your document must be in excellent shape.
High concept: you never get a second chance to make a first impression, and here is where you will make it. We have already given you the tools to make this section, now just remember to give its construction a high priority and point everything that makes your game more appealing here.
Pictures: do not be fooled that the reader will always go through your entire GDD, there are some documents that surpass a thousand pages (yes, this is true!). But he will surely take a better look if something catches his attention, and what better way of doing so than with pictures? After all, one image is worth a thousand words.
It goes without saying that your document must have a great appearance. Take your time to make everything readable and nice. Also, don’t forget that this article only presented a skeleton structure of a GDD for you; you will have to adapt it to your own game!
In the first part of this series we took a general look at the capabilities of TimelineLite. In this video I’ll show you how to get up and running with your first TimelineLite animation. You’ll learn about the various methods and properties that will be a foundation for all of the lessons moving forward.
TimelineLite in Action
You can find all the files used to create the SWF above in the source files for this tutorial.
Watch the Screencast
Don’t like ads? Download the screencast, or subscribe to Activetuts+ screencasts via iTunes!
TimelineLite Basic Methods
The following methods are used to add tweens to a TimelineLite. It’s very important to understand the subtle differences as covered in the video.
insert() – Adds tweens to a timeline at a specific time. If no insertion time is specified the tween will be inserted at a start time of zero seconds.
//this tween will be inserted at the beginning of the timeline tl.insert( TweenLite.to( mc, 1, {x:100} ) ); //this tween will be inserted 2 seconds into the timeline tl.insert( TweenLite.to( mc, 1, {x:100} ), 2);append() – Adds tweens to a timeline relative to the end of the timeline. The offset value can be positive or negative. A negative offset will allow tweens to overlap.
//this tween will directly after all previous tweens have finished tl.append( TweenLite.to( mc, 1, {x:100} ) ); //this tween will play 1 second before all previous tweens have finished tl.append( TweenLite.to( mc, 1, {x:100} ), -1 );prepend() – Adds tweens to the beginning of a timeline and pushes all existing tweens forward in time.
//this tween occur before any other tweens that exist in the timeline tl.prepend( TweenLite.to( mc, 1, {x:100} ) );TimelineLite Basic Properties
The following properties are very useful for adding functionality to your timelines and for debugging:
TimelineLite Special Properties
When constructing a TimelineLite you can pass a number of "special properties" into the constructor. The video demonstates onUpdate, onComplete, and paused. The special properties are all contained in a vars object.
//this timeline will be paused initially and when it is done //it will call a function called completeHandler tl = new TimelineLite( {onComplete:completeHandler, paused:true} );TimelineLite has many more methods, properties and “special properties” which are too numerous to list here. I urge you to investigate all there is to offer in the Official TimelineLite Documentation. The ones listed above are the most important to understand when getting started. As this series progresses I will be introducing more of the tools you will be using to gain advanced control over the setup and playback of your animation sequences.
The next video in this series will focus on controlling a TimelineLite while it is playing. It will cover everything from basic
play()andreverse()to adding an interactive scrubber control.TimelineLite Code Sample
Below is a sample of the code used in the video to illustrate the basic structure of a TimelineLite.
//constructor tl = new TimelineLite(); //tweens that introduce car. //insert() puts them all at a time of 0 seconds tl.insert( TweenMax.from(gti_mc, .5, {x:-500, blurFilter:{blurX:140}}) ); tl.insert( TweenLite.from(gti_mc.wheel1_mc, .5, {rotation:-180}) ); tl.insert( TweenLite.from(gti_mc.wheel2_mc, .5, {rotation:-180}) ); //append() adds tweens relative to the end of the timeline //.5 seconds after previous tweens end this text will show for 1 second and then fade out tl.append( TweenMax.from(hello_mc, .5, {alpha:0, repeat:1, repeatDelay:1, yoyo:true}) ,.5); //introduce second text .5 seconds after previous tween ends tl.append( TweenMax.from( colors_mc, .5, {alpha:0}), .5 ); //tint sequence tl.append( TweenMax.to( gti_mc.body_mc, .2, {tint:blue}) , .5); tl.append( TweenMax.to( gti_mc.body_mc, .2, {tint:red}) , .5); tl.append( TweenMax.to( gti_mc.body_mc, .2, {tint:black}) , .5); //last text tl.append( TweenMax.from( black_mc, .5, {alpha:0}), 1 ); //optional: inserts black box reveal at the beginning of the timeline tl.prepend( TweenLite.from ( cover_mc, .5, {y:0}) );TimelineLite is the ultimate tool for creating elaborate and precise sequences of scripted animation. It is an integral part of the GreenSock Tweening Platform that allows you to make the most of TweenLite and TweenMax. This series of screencasts will walk you step by step through everything you need to know to take your AS3 tweening skills to the next level.
Basic Features of TimelineLite
Along the way we will be diving into TimelineMax which extends the power of TimelinLite with even more features. Everything you learn how to do in TimelineLite will also apply to TimelineMax.
Also, although this guide was produced solely with AS3, TimelineLite and TimelineMax have the exact same features in AS2, and use identical syntax!
Meet TimelineLite
In this first introductory video I’m going to explain exactly what TimelineLite is and what it can do. I’ll walk you through some inspiring examples of TimelineLite that highlight its many features. Along the way I’ll show you a few resources that are going to help you get the most out of your TimelineLite training. Once you see how concise and flexible TimelineLite code can be, you’ll never tween without it.
Sit back and relax as I set the groundwork for all the amazing things we will be covering in this series.
Don’t like ads? Download the screencast, or subscribe to Activetuts+ screencasts via iTunes!
TimelineLite in Action
Resources Mentioned in this Video
In this series of videos I am going to be referring to the TimelineLite documentation many times. I strongly suggest you visit and bookmark the following:
To get the most out of TimelineLite its imperative that you have a basic understanding of TweenLite and TweenMax. For a quick crash course please read GreenSock’s Getting Started Tweening article.
Come back tomorrow for the next video in the series, where we’ll be looking at basic methods and properties of TimelineLite.
QR codes are everywhere these days: magazine advertisements, billboards, even TV commercials. Chances are you have a phone in your pocket that can read a QR code and decode the URL or message contained within. In this tutorial, you’ll learn how to create a SWF that can reverse the process: create a QR code from a URL or message. Read on!
Final Result Preview
Let’s take a look at the final app we will be working towards:
Step 1: Brief Overview
Using pre-made graphic elements we’ll create good looking interface that will be powered by several ActionScript 3 classes.
The code will make good use of a QR Code Encoder class, created by Jean-Baptiste Pin.
Step 2: Flash Document Settings
Open Flash and create a 480 pixels wide, 480 pixels tall document. Set the Frame rate to 24fps.
Step 3: Interface
A colorful nice looking interface will be displayed, this involves multiple shapes, buttons and more.
Simple shapes were created using the Flash Drawing Tools so it won’t be necessary to include their creation.
Step 4: Instance Names
The image above shows the Instance Names used in the MovieClips. The ones that start with a Capital Letter are Library Class Names and should not be on stage.
Step 5: TweenNano
We’ll use a different tween engine from the default included in Flash, this will increase performace as well as being easier to use.
You can download TweenNano from its official website.
Step 6: QR Code Library
We’ll make use of a fantastic QR Code Encoder library which can be downloaded from here. You can learn more about using external libraries in your code with this tutorial.
Editor’s note: Use the SWC file, not the classes in the \org\ folder, for best results.
Step 7: Set Main Class
Add the class name to the Class field in the Publish section of the Properties panel to associate the FLA with the Main document class.
Step 8: Create a New ActionScript Class
Create a new (Cmd + N) ActionScript 3.0 Class and save it as Main.as in your class folder.
Step 9: Class Structure
Create your basic class structure to begin writing your code.
package { import flash.display.Sprite; public class Main extends Sprite { public function Main():void { // constructor code } } }Step 10: Required Classes
These are the classes we’ll need to import for our class to work, the
importdirective makes externally defined classes and packages available to your code.Step 11: Variables
These are the variables we’ll use, read the comments in the code to know more about them, some of their names are self explaining so there will be no comment there.
Step 12: Constructor
The constructor is a function that runs when an object is created from a class, this code is the first to execute when you make an instance of an object or runs using the Document Class.
It calls the necessary functions to start the app. Check those functions in the next steps.
public final function Main():void { addTextView(); addListeners(); }Step 13: Add Text View
The first function executed by the constructor. It will instantiate the TextView and add it to the stage. This will be the default view that will be shown starting the application. It includes a call to remove the currently visible view (in case there’s one) and also performs a Tween as a detail to the interface.
private final function addTextView():void { removeLastView(); textView = new TextView(); textView.x = stage.stageWidth * 0.5; textView.y = 110; addChild(textView); TweenNano.from(textView, 0.5, {y: textView.y - 10, alpha:0, ease:Expo}); lastView = textView; }Step 14: SMS View
This code handles the SMSView position and animation. It is called when the SMS button tab is pressed.
private final function addSMSView():void { removeLastView(); smsView = new SMSView(); smsView.x = stage.stageWidth * 0.5; smsView.y = 150; addChild(smsView); TweenNano.from(smsView, 0.5, {y: smsView.y - 10, alpha:0, ease:Expo}); lastView = smsView; }Step 15: Email View
The EmailView code. It will place and animate this view on the stage.
private final function addEmailView():void { removeLastView(); emailView = new EmailView(); emailView.x = stage.stageWidth * 0.5; emailView.y = 155; addChild(emailView); TweenNano.from(emailView, 0.5, {y: emailView.y - 10, alpha:0, ease:Expo}); lastView = emailView; }Step 16: Link View
This is the final tab, it removes the last visible view and adds the LinkView to the stage.
private final function addLinkView():void { removeLastView(); linkView = new LinkView(); linkView.x = stage.stageWidth * 0.5; linkView.y = 110; addChild(linkView); TweenNano.from(linkView, 0.5, {y: linkView.y - 10, alpha:0, ease:Expo}); lastView = linkView; }Step 17: Remove Last View
This function removes the currently visible view from the stage and frees it up for garbage collection.
private final function removeLastView():void { if(lastView != null) { removeChild(lastView); lastView = null; } }Step 18: Add Listeners
The next code links the buttons to their corresponding functions. This will enable the tab based navigation.
private final function addListeners():void { abcBtn.addEventListener(MouseEvent.MOUSE_UP, indicatorHandler); smsBtn.addEventListener(MouseEvent.MOUSE_UP, indicatorHandler); emailBtn.addEventListener(MouseEvent.MOUSE_UP, indicatorHandler); linkBtn.addEventListener(MouseEvent.MOUSE_UP, indicatorHandler); refreshBtn.addEventListener(MouseEvent.MOUSE_UP, refreshCode); }Step 19: Indicator Handler
The Indicator MovieClip is the little arrow that shows the active tab. This function places it in the correct position and calls the tab function.
private final function indicatorHandler(e:MouseEvent):void { indicator.x = e.target.x; switch(e.target.name) { case 'abcBtn': addTextView(); break; case 'smsBtn': addSMSView(); break; case 'emailBtn': addEmailView(); break; case 'linkBtn': addLinkView(); break; default: trace('Button Names Error'); } }Step 20: Refresh QR Code
This function will run when the Refresh button is pressed, it determines the current string to convert and the QRObject
encode()method to generate a bitmap that is next added to the stage.private final function refreshCode(e:MouseEvent):void { switch(lastView) { case textView: currentTarget = textView.textTF.text; break; case smsView: currentTarget = 'SMSTO:' + smsView.phoneTF.text + ':' + smsView.contentTF.text; break; case emailView: currentTarget = 'SMTP:' + emailView.toTF.text + ':' + emailView.subjectTF.text + ':' + emailView.bodyTF.text; break; case linkView: currentTarget = linkView.linkTF.text; if (currentTarget.indexOf('://') == -1) { currentTarget = 'http://' + currentTarget; //automatically add http:// to the front of links if required } break; default: trace('Target Error'); } if(qrImg != null) { removeChild(qrImg); qrImg = null; } var qrObj:QRCode = new QRCode(); qrObj.encode(currentTarget); qrImg = new Bitmap(qrObj.bitmapData); qrImg.x = stage.stageWidth * 0.5 - (qrImg.width * 0.5); qrImg.y = 300 - (qrImg.height * 0.5); addChild(qrImg); TweenNano.from(qrImg, 1, {alpha:0, ease:Expo}); }Conclusion
Use this application to generate your custom QR Codes and remember to explore the source files.
I hope you liked this tutorial, thank you for reading!
A very common ActionScript error is Error 1120, the “undefined property” error. In fact, this may be the most common compile-time error. Its cause is very simple, but there are a number of ways it can actually be induced. We’ll take a quick tour of some of the scenarios in which you’ll encounter this error, and how to fix them.
The Explanation
First, let’s get the general idea of what’s going on with this error. This will be our theoretical discussion; we’ll get to more practical examples in the remainder of the tip.
You find this error occurring when you reference a variable (or property) in a line of code, but that variable doesn’t exist. If you’re used to writing ActionScript in the Script panel, and not in classes, then don’t be put off by the term “property.” I won’t get into a lengthy discussion on why we have these two terms, but suffice it to say that given the way ActionScript compiles code from the Script panel, a variable is actually a property.
If the ActionScript compiler can’t find the property you’ve specified, it flags it with compiler error 1120. You’ll see something like this:
Where “
foo” is the name of your offending property name.Solving this usually as easy as getting the name right. Let’s take a look.
Example 1: No Variable Declaration
Take this code for example (if you like, you can either open up the “missing” example project (that is, the project named “missing” in the source download) or create this project on your own: just create a new Flash file and a document class for it, and put this code in the document class):
package { import flash.display.*; public class Missing extends Sprite { public function Missing() { foo = "Moo."; } } }Compile this FLA, and you’ll get the
1120: Access of undefined property foo.error, directed at line 7.Again, the cause is probably very clear; you’ve never declared the
fooproperty and so using it in line 7 as if it already existed causes problems. The solution is to simply declare the variable. Technically, this can exist anywhere in the appropriate scope. Each of the following are acceptable solutions:Declare it as a parameter to the method:
public function Missing(foo:String) { foo = "Moo."; }Declare it as a local variable inside the method:
public function Missing() { var foo:String; foo = "Moo."; }Declare it as an instance property in the class:
package { import flash.display.*; public class Missing extends Sprite { private var foo:String; public function Missing() { foo = "Moo."; } } }The solution depends on what’s appropriate to what you’re trying to do. It’s not common to declare a method parameter and then simply set it in the method body, although there are cases where you want to affect the parameter values.
What may be interesting is the way in which Flash will search for a variable of the name. First, it looks for a local variable of the name
foo. If one was not declared, then it looks at the parameters for that name. If it can’t find one there, it looks for an instance property of the same name. If none of these are found, you’ll see error 1120.Example 2: A Typo
Our next example will be rather similar. Again, you can simply open up the “typo” project from the download; or create a simple FLA with document class and paste/type the following code into the class; or just read along.
package { import flash.display.*; public class Typo extends Sprite { var foo:String; public function Typo() { boo = "Moo."; } } }There isn’t much going on here. The idea is to declare a property called
foo, and then in the constructor set that to the value"Moo.". The meat of this is on lines 5 and 9, highlighted above. The rest is just document class boilerplate.Again, the error is probably painfully obvious. And of course it is, when there are only two lines of code to concern yourself with. Clearly we have a typo; we meant to write:
And if you fix the typo, you’ll be able to compile the FLA without the 1120 error.
While this one is obvious, I want to make two notes. The first is that typos like this are actually somewhat common and harder to deduce when you have thousands of lines of code in your project. A good IDE or text editor can help you by providing autocompletion or other kinds of error checking, but as a failsafe ActionScript will also help you out by producing the Error 1120.
The second note is that the error that ActionScript is actually reporting is that you are attempting to reference a property called “
boo“, but it is not finding one. In this example, we have produced the error by introducing a typo into our code. But ActionScript doesn’t know that you really meant “foo“; it assumes that when you write “boo = "Moo.";” that you meantboo, and warns you when it can’t find a property of that name.This is a rather subtle distinction, but it’s helpful to keep in mind the true cause of the error.
Example 3: Declaring Stage Instances
Our final example involves the Flash IDE in particular, and can be safely ignored if you’re not using Flash CS3/4/5, and instead using Flash Builder with the Flex Framework. Setting up the error will take a few simple steps.
You can find the problematic project in the stage-instance folder of the download package. Or you can re-create it by following these steps:
instance_mc.Add the following code to the document class:
package { import flash.display.*; public class StageInstance extends Sprite { public function StageInstance() { instance_mc.rotation = 45; } } }At this point, go ahead and run the movie. You’ll once again get the 1120 error.
As you might be able to surmise, turning off “Automatically declare stage instances” had a lot to do with the generation of the error. It is an option that is by default turned on, so you’d probably have a reason for — and be aware of — turning it off.
What is does if fairly self-explanatory, but I’ll add my two cents. When you have a document class (or a custom class linked to a symbol in the library), that class extends
SpriteorMovieClip. The typical rules for creating variables and properties apply just like any other class, but because you have a visual interface to this object — the Flash IDE — you may be implicitly adding properties by way of drawing more symbols on the stage and giving them instance names.If “Automatically declare stage instances” is on, then Flash will go through the objects added to the stage through the IDE and insert property declarations to the appropriate classes for each symbol instance with a name.
If it’s off, then Flash will do nothing with the instances on the stage.
To avoid the error, you can simply turn this option back on. Alternatively, you can manually declare the stage instances. If we made our document class look like this:
package { import flash.display.*; public class StageInstance extends Sprite { public var instance_mc:MovieClip; public function StageInstance() { instance_mc.rotation = 45; } } }This would also avoid the error, because the property is still declared; it just hasn’t been declared automatically by Flash Professional.
Note that if the access modifier is not
publicyou will get a runtime error. I’m not going to go into detail on this error; that will be for another Quick Tip.Now, why would you ever want to turn that option off in the first place? I think the reasons all have to do with moving beyond Flash as a development tool. If you use Flash Builder to code, or ASDoc, or integrate Flash content within a Flex project, you might appreciate the ramifications of turning the option off. If you turn it off, you are forced to then declare the properties manually. The opposite is also true: if you turn it on, you are forced to not manually declare those properties.
Thus, if you run your class files through ASDoc, it will most likely find a reference to a stage instance, but not its declaration, and you’ll receive error 1120. Turning the option on is a convenience, but turning it off forces you to write more “pure” code that is more portable and self-contained.
I Declare This Quick Tip Done
Like many errors, Error 1120 is simple at heart, and easy to fix once you know what to look for. Thanks for reading, and stay tuned for more debugging Quick Tips!
In this Quick Tip you will learn how to make a simple points system that can be extended into your own games. This tutorial is for total beginners, and in it you’ll learn how to create a set of buttons that the player can click to add or subtract points to or from their score. Simple!
Although this tutorial is for beginners to Flash programming, you will need to know a little bit about using the drawing tools in Flash Professional. If you need to learn how to use the drawing tools, read this article.
Final Result Preview
Let’s take a look at the final result we will be working towards:
Step 1: Setting Up Your Flash File
Open up Flash and create a new Flash Document. Set the Document Size to 550x400px, the FPS (Frames per Second) to 24 and set the document class to
Main.Step 2: Creating the Graphics
These are the graphics you will need to create.
To create the graphics, use the Oval Tool to create four circles with a #000000 (Black) outline and a stroke size of 12.50.
Each circle should have a different fill color. The fill colours are as follows:
After creating the coins, use static textboxes to write their respective values as given in the image above. The font I will be using throughout this tutorial is Futura LT Heavy, with a size of 50, but feel free to use your own font and font size. After completing the instruction you should have a 10 Coin, 50 Coin, 100 Coin, and a -30 Coin.
We will now convert each coin into a MovieClip. Select the 10 Coin and press F8. You should see a dialog like this:
The image has everything filled out. Make sure you write down the same things in your dialog.
Repeat this step for the 50 Coin, 100 Coin and the -30 Coin and input the following under the Name and Class boxes for each Coin:
Once you have created MovieClips out of all the Coins, select them all and delete them off the stage. We will be adding them back again later on, using code.
To finish off this step create a static textbox with the text “SCORE:” and position it with an X value of 135 and a Y value of 327.
Step 3: Setting Up the Package and Main Class
In this step we will set up our package and the Main Class.
Create a new ActionScript Class and under Class Name type
Main. Once you have created the class, save it in the same folder as your Flash file, and make sure it is saved asMain.as.Make sure your dialog looks like this.
Enter the code below into your Main.as file, then save it.
package { //imports the necessary classes import flash.display.MovieClip import flash.events.MouseEvent; import flash.text.TextField; import flash.text.TextFormat; import flash.text.TextFieldType; import flash.events.Event; public class Main extends MovieClip { var tenCoin:TenCoin = new TenCoin(); //Creates a new instance of the Ten Coin var fiftyCoin:FiftyCoin = new FiftyCoin(); //Creates a new instance of the Fifty Coin var hundredCoin:HundredCoin = new HundredCoin(); //Creates a new instance of the Hundred Coin var badCoin:BadCoin = new BadCoin(); //Creates a new instance of the -30 Coin var score:Number = 0; //Sets the score to zero var scoreText:TextField = new TextField(); //Creates a textfield to display the score var scoreTextFormat:TextFormat = new TextFormat("Futura LT Heavy", 50, 0x000000); //Creates a new format for Score textfield, replace "Futura LT Heavy" with the font that you are using public function Main() { displayObjects(); //The function that we will use to display all of the graphic on the stage setUpEventListeners(); //The function that we will use to add our Event Listeners }First we import the classes that we need and then we set up our document class. We then extend the Main Class from MovieClip; we could use Sprite, but it doesn’t really matter. After that, we declare the variables that we are using and we add code to our
Main()function (our “constructor function”).The
Main()function will display all of the coins we created on the stage and it will also add the Event Listeners that we need to use.Note: You’ll need to embed the font into your FLA in order to make it display in dynamic text fields if your user doesn’t have the font installed. I haven’t done this here, to keep things simple.
Step 4: Coding the
displayObjects()FunctionThe
displayObjects()Function is called from theMain()function. The purpose of this function is to add the coins and the score textbox to the stage and to position them at their proper location.function displayObjects() { // Sets the position of the Ten Coin tenCoin.x = 72; tenCoin.y = 200; // Sets the position of the Fifty Coin fiftyCoin.x = 207; fiftyCoin.y = 200; // Sets the position of the Hundred Coin hundredCoin.x = 342; hundredCoin.y = 200; // Sets the position of the -Thirty Coin badCoin.x = 477; badCoin.y = 200; //Positions the score textbox and sets its type to dynamic so that it can be changed through code scoreText.type = TextFieldType.DYNAMIC; scoreText.x = 305; scoreText.y = 327; scoreText.width = 300; //Sets the format of the score textbox scoreText.defaultTextFormat = scoreTextFormat; //Adds everything to the stage addChild(tenCoin); addChild(fiftyCoin); addChild(hundredCoin); addChild(badCoin); addChild(scoreText); }Step 4: Coding the
setUpEventListeners()Function and the Event HandlersNow that we have added objects to the stage we have to create event handler functions that are triggered when clicking on the coins. We will also need an
updateScore()function to use so that the score gets updated and doesn’t stay the same.function setUpEventListeners() { //Changes all of our MovieClips into Buttons tenCoin.buttonMode = true; fiftyCoin.buttonMode = true; hundredCoin.buttonMode = true; badCoin.buttonMode = true; //Adds the event listeners to add points to the score tenCoin.addEventListener(MouseEvent.CLICK, addTenPoints); fiftyCoin.addEventListener(MouseEvent.CLICK, addFiftyPoints); hundredCoin.addEventListener(MouseEvent.CLICK, addHundredPoints); badCoin.addEventListener(MouseEvent.CLICK, removeThirtyPoints); //Adds the update function to update the score stage.addEventListener(Event.ENTER_FRAME, updateScore); }Now we must code the functions that change and update the score.
function addTenPoints(evt:MouseEvent){ score += 10; //Adds 10 points to the score } function addFiftyPoints(evt:MouseEvent){ score += 50; //Adds 50 points to the score } function addHundredPoints(evt:MouseEvent){ score += 100; //Adds 100 points to the score } function removeThirtyPoints(evt:MouseEvent){ score -= 30; //Subtracts 30 points from the score // This if statement doesn't allow the score to go below zero if(score < 10) { score -= score; } } function updateScore(evt:Event){ scoreText.text = String(score); //This converts the score variable from a number to a string, because our score textbox can only display strings } } //Closes the class } //Closes the packageYour code is now ready for testing. Press CTRL+Enter (CMD+Enter on a Mac) and watch your points system come to life!
Review
Now you might be thinking, “I’ve wasted my time, all this tutorial taught me to do was to create some buttons that give you points when you click them” – but you have learned more than this.
Look at this as a foundation for a more advanced points system. You learned how to use += and -= to add or subtract from a number, you learned how to use event listeners, you learned how to create a function to update objects while the SWF is running, and you learned how to change a Number to a String and display it in a dynamic textbox that you created entirely through code!
Conclusion
This points system can easily be extended into your own games. For those of you who are more experienced at Flash, try creating a game that uses a hitTestObject function and adds points when you hit an object.
I hope you learned something new today. Thanks for reading!