Learn the basics of how FlashPunk works - an amazing library to save you time and help you create the perfect game!
Final Result Preview
Let's take a look at the final result we will be working towards:
Use the arrow keys to move your character (the blue guy). The red/brown guy is an NPC; the shaded red area is a danger zone, and the green box is a button. You'll learn how to create all this in this tutorial.
Step 1: What Is FlashPunk?
FlashPunk is an ActionScript 3 library created for the development of Flash games. Basically, it does all the hard work for you and lets you focus entirely on developing your game, rather than on the engine behind it. The best part about it is that you don't need Flash Pro to work with it: you can do everything with a free code editor like FlashDevelop. Not to mention it's way faster when it comes to drawing things on screen, since it uses blitting!
This tutorial will go through all the basics of FlashPunk. After following it, you'll be ready to make a simple game with this amazing library!
Step 2: Initializing the Engine
Begin by downloading the latest version of FlashPunk from the official site (this tutorial uses the version from August 30, 2011). Put the "net" folder, with all its contents, in your "src" folder.
FlashPunk has a class called Engine
. This class is what starts everything in the library. Think of it as a Main
class, but with special code to power up all the classes in FlashPunk. In order to use the Engine
class, we will modify the Main
class a little bit.
package { import net.flashpunk.Engine; [Frame(factoryClass="Preloader")] public class Main extends Engine { public function Main():void { } } }
Now, our class extends Engine
. In Main
's constructor, we need to make a call to the Engine
constructor: this is what sets the important information about the game: width, height, framerate and whether the engine should run at a fixed framerate or not.
public function Main():void { super(550, 400, 30, false); }
There is a function that can (and must be) overridden from the Engine
class: the init()
function. It will run only once, and will initialize everything to get the game working.
override public function init():void { trace("The game has started!"); }
I'm pretty sure everyone wants to put something on the screen and see this engine working! Because of that, the next few steps will cover the very basics of the elements of FlashPunk, adding depth as the tutorial goes on.
Step 3: Worlds and Entities
In FlashPunk, there are elements called Worlds
and Entities
. These are the main elements of the library, and you'll work with them from the beginning to the very end of your game.
Worlds are pretty much like what is commonly known as a "screen". Everything in your game will happen in a world: the main menu is a world that will give you access to the actual game world, where you will fight some enemies and die, which will lead you to the game over world, with your scores and statistics about how well you did. More about worlds will be explained later.
Entities are exactly what they seem to be; they live in a world and do something in it: a button is an entity; your character is an entity; enemies and bullets are entities. They are the things that give life to the game.
Given that, we will create the game world (there's time to make the main menu world later, let's jump to some action!) by extending FlashPunk's World
class:
package { import net.flashpunk.World; public class GameWorld extends World { public function GameWorld() { } } }
Now that you have created a world, you need to tell FlashPunk that you want this world to be the active one. Let's do it in Main.as
:
private var _gameWorld:GameWorld; public function Main():void { super(550, 400, 30, false); _gameWorld = new GameWorld(); } override public function init():void { trace("The game has started!"); FP.world = _gameWorld; }
And don't forget to import net.flashpunk.FP
!
Step 4: Adding an Entity, and Giving It an Image
Now that we have our world, we can make an entity by extending the Entity
class and adding it to our game world:
package { import net.flashpunk.Entity; public class GameEntity extends Entity { public function GameEntity() { } } }
And in GameWorld.as
:
private var _gameEntity:GameEntity; public function GameWorld() { _gameEntity = new GameEntity(); add(_gameEntity); }
Notice that if you compile and run the game, the entity doesn't appear in the screen. That's because it has no image yet! Every entity can have a graphic object. This graphic can be a single image, a spritesheet with animations, tiled images -- pretty much anything.
We will add this little image to our entity:
An entity's graphic can be accessed by the graphic
property. That's how we are going to put the image in it! First, embed it; then, just pass it to Image
's constructor and FlashPunk will take care of transforming that into something visible for you. Compile and run now. Surprise! Our entity is there!
package { import net.flashpunk.Entity; import net.flashpunk.graphics.Image; public class GameEntity extends Entity { [Embed(source = "/../img/EntityImage.png")] private const IMAGE:Class; public function GameEntity() { graphic = new Image(IMAGE); } } }
This is what you should get:
Step 5: Making the Entity Move
Now that we have our entity on the screen, what about making it move? Each Entity
has a function called update()
, which you must override to use. This function is called by every world in the beginning of each frame. If you need to make your entity move, that's the place where you put your code!
override public function update():void { x += 10 * FP.elapsed; y += 5 * FP.elapsed; }
And don't forget to import:
import net.flashpunk.FP;
See it in action! (Refresh the page if you can't see anything here.)
You may have noticed the use of FP.elapsed
. FP.elapsed
gives the amount of time that elapsed since the last frame (in seconds), making it very easy to create time-based motion. However, for that to work, you must have set the fourth parameter to the Engine
's constructor to false
. Remember that (Step 2)? Setting it to false
means that you want FlashPunk to run with a variable timestep, whereas setting it to true
makes FlashPunk run on a fixed timestep. Doing the latter, you don't need to use FP.elapsed
. You will know that every time the update()
function is called, a frame has passed.
Step 6: Move the Entity as You Wish With Keyboard Input
We've got the entity moving on just one direction in the last step. Introducing keyboard input: now you will be able to move the entity to where you want!
FlashPunk has a class called Input
which takes care of both keyboard and mouse input. In this tutorial, we will only use keyboard input for movement. It is very easy:
override public function update():void { if (Input.check(Key.A) || Input.check(Key.LEFT)) { x -= 50 * FP.elapsed; } else if (Input.check(Key.D) || Input.check(Key.RIGHT)) { x += 50 * FP.elapsed; } if (Input.check(Key.W) || Input.check(Key.UP)) { y -= 50 * FP.elapsed; } else if (Input.check(Key.S) || Input.check(Key.DOWN)) { y += 50 * FP.elapsed; } }
And the import statements:
import net.flashpunk.utils.Input; import net.flashpunk.utils.Key;
Input.check()
returns true
if the Key
passed as an argument is being pressed at the time the function has been called. There are other very useful functions, like Input.pressed()
, which returns true
if the key has been pressed at the time the function has been called (i.e. the key was up a frame ago and is now down), or Input.released()
, which does exactly the opposite.
Another interesting thing that the Input
class allows us to do is to define many keys under a single name. For example, we could define Key.UP
, Key.W
and Key.I
as "UP"
, and only check for Input.check("UP")
. That way, we can improve our function:
public function GameEntity() { graphic = new Image(IMAGE); Input.define("UP", Key.W, Key.UP); Input.define("DOWN", Key.S, Key.DOWN); Input.define("LEFT", Key.A, Key.LEFT); Input.define("RIGHT", Key.D, Key.RIGHT); } override public function update():void { if (Input.check("LEFT")) { x -= 50 * FP.elapsed; } else if (Input.check("RIGHT")) { x += 50 * FP.elapsed; } if (Input.check("UP")) { y -= 50 * FP.elapsed; } else if (Input.check("DOWN")) { y += 50 * FP.elapsed; } }
And this is what you should get:
Step 7: More About Entities
Entities can do a lot more than just move around and have images. Let's take a look at what surprises they can hold!
Entities have a property called type
. You can set this property to any string you want. This allows you to organize your entities into groups, which will prove very useful in the next step (about worlds). We can, for example, set our entity's type to "GameEntity":
public function GameEntity() { graphic = new Image(IMAGE); Input.define("UP", Key.W, Key.UP); Input.define("DOWN", Key.S, Key.DOWN); Input.define("LEFT", Key.A, Key.LEFT); Input.define("RIGHT", Key.D, Key.RIGHT); type = "GameEntity"; }
Following on that, we have the useful world
property and the added()
and removed()
functions. The world
property allows you to access the world from within the entity's code once the entity has been added to an world. It is like the stage
property in common Flash development; the functions are like the ADDED_TO_STAGE
and REMOVED_FROM_STAGE
event listeners. Here's an example of the functions working in GameEntity.as
:
override public function added():void { trace("The entity has been added to the world!"); trace("Entities in the world: " + world.count); } override public function removed():void { trace("The entity has been removed from the world!"); }
Step 8: Deeper Look at Worlds
It is time to take a deeper look at worlds and how they work. First of all, FlashPunk can only have one world running at once, but your game can have as many worlds as you wish, as long as only one remains active every time.
Worlds have update()
functions just as entities do, but their function is a little different: there is actual code in the World
class. That means you'll have to call super.update()
every time you override this function.
Apart from entities, worlds can also have graphics added to them. Graphics are images that don't need to be updated by you (FlashPunk still creates an entity to add them to the world, so the engine will still send a call to an update()
function). You can add them by calling addGraphic()
.
The most important thing about worlds is that they have several functions to retrieve certain entities: getType()
, getClass()
, getAll()
, getLayer()
and getInstance()
. That way, you can have the world return an array of all the bullets currently in the game, so that you can perform a check against all of them for collision. Very handy, I must say!
Take a look at the code added to World.as
. We will use a second image as well:
[Embed(source = "/../img/EntityImage2.png")] private const IMAGE:Class; public function GameWorld() { _gameEntity = new GameEntity(); add(_gameEntity); addGraphic(new Image(IMAGE), 0, 50, 50); } override public function update():void { super.update(); var entityArray:Array = []; getType("GameEntity", entityArray); for each (var entity:Entity in entityArray) { entity.x = entity.x > 550 ? 550 : entity.x; entity.y = entity.y > 400 ? 400 : entity.y; } }
And don't forget to import net.flashpunk.graphics.Image
!
In this code, the addGraphic()
function call adds another graphic similar to _gameEntity
's graphic - think of it as a NPC! - to the world in the position (50, 50). Lines 23-31 show an example of retrieving only entities of a particular kind: we call getType()
to get only entities of the "GameEntity" type (currently only one entity). After that, we iterate through every entity retrieved and prevent them from getting past the right and bottom borders. (So, the entity can move outside the screen, but not far.) Simple, isn't it?
Step 9: Animations
Time for something more interesting! FlashPunk supports animations of all kinds. All you have to do is, instead of creating an instance of Image
, create an instance of Spritemap
. This class receives a spritesheet and allows you to map frames and link to animations.
In our entity's class, embed this spritemap:
Then, create an instance of Spritemap
and pass the spritesheet as a parameter to the constructor. After that, it's all about calling the add()
and play()
functions!
[Embed(source = "/../img/EntitySheet.png")] private const SHEET:Class; private var _timeInterval:Number; public function GameEntity() { graphic = new Spritemap(SHEET, 40, 20, onAnimationEnd); Spritemap(graphic).add("Stopped", [0]); Spritemap(graphic).add("Blinking", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 24); Input.define("UP", Key.W, Key.UP); Input.define("DOWN", Key.S, Key.DOWN); Input.define("LEFT", Key.A, Key.LEFT); Input.define("RIGHT", Key.D, Key.RIGHT); type = "GameEntity"; Spritemap(graphic).play("Blinking"); } private function onAnimationEnd():void { Spritemap(graphic).play("Stopped"); _timeInterval = 0; } override public function update():void { _timeInterval += FP.elapsed; if (_timeInterval >= 3) { Spritemap(graphic).play("Blinking"); } if (Input.check("LEFT")) { x -= 50 * FP.elapsed; } else if (Input.check("RIGHT")) { x += 50 * FP.elapsed; } if (Input.check("UP")) { y -= 50 * FP.elapsed; } else if (Input.check("DOWN")) { y += 50 * FP.elapsed; } }
The constructor of Spritemap
(line 19) takes four arguments: a source to get a graphic from, the width and height of each frame of the spritesheet and a callback function to call when the animation ends (optional). In GameEntity
's constructor, we create the Spritemap
and define two animations: "Stopped", which only contains the first frame and runs at 0 fps (stopped!) and "Blinking", which contains all frames and runs at 24 frames per second.
The rest of the code is there to play the "Blinking" animation every three seconds.
Take a look at our entity blinking:
Step 10: Collision
With everything running well, it's time to introduce another feature: collision detection. FlashPunk has a great collision detection system: all we need to do is set hitboxes for our entities and ask the world to check for collisions. For that, we will create another entity called Box
which will contain the following graphic:
package { import net.flashpunk.Entity; import net.flashpunk.graphics.Image; public class Box extends Entity { [Embed(source = "/../img/BoxImage.png")] private const IMAGE:Class; public function Box() { graphic = new Image(IMAGE); setHitbox(60, 60); } } }
And inside GameWorld.as
:
private var _box:Box; public function GameWorld() { _gameEntity = new GameEntity(); _box = new Box(); add(_gameEntity); add(_box); _box.x = 200; _box.y = 150; addGraphic(new Image(IMAGE), 0, 50, 50); }
The setHitbox()
function sets a rectangle that will act as a hit box for the entity. The first two parameters are the width and height of the box. The next two parameters (optional) are the origin coordinates (x and y) of the rectangle. Doing the same for GameEntity
:
public function GameEntity() { graphic = new Spritemap(SHEET, 40, 20, onAnimationEnd); Spritemap(graphic).add("Stopped", [0]); Spritemap(graphic).add("Blinking", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 24); Input.define("UP", Key.W, Key.UP); Input.define("DOWN", Key.S, Key.DOWN); Input.define("LEFT", Key.A, Key.LEFT); Input.define("RIGHT", Key.D, Key.RIGHT); type = "GameEntity"; Spritemap(graphic).play("Blinking"); setHitbox(40, 20); }
Now that we have both our entity and the box set up with hitboxes, we need to check for collisions in the world class:
override public function update():void { super.update(); var entityArray:Array = []; getType("GameEntity", entityArray); for each (var entity:Entity in entityArray) { entity.x = entity.x > 550 ? 550 : entity.x; entity.y = entity.y > 400 ? 400 : entity.y; } if (_gameEntity.collideWith(_box, _gameEntity.x, _gameEntity.y)) { trace("Collision!"); } }
The collideWith()
function checks collision with the entity passed as an argument, virtually placing the first entity (in this case, _gameEntity
) in the position specified by the second and third arguments.
Once a collision is detected, there must be a response to it. We will only change the position of the moving entity:
override public function update():void { super.update(); var entityArray:Array = []; getType("GameEntity", entityArray); for each (var entity:Entity in entityArray) { entity.x = entity.x > 550 ? 550 : entity.x; entity.y = entity.y > 400 ? 400 : entity.y; } if (_gameEntity.collideWith(_box, _gameEntity.x, _gameEntity.y)) { _gameEntity.x = _gameEntity.y = 0; } }
Take a look at the example. Try to move the entity into the box.
Step 11: Creating a Simple Button - Adding an Image
FlashPunk doesn't have any buttons by default. Almost all games need buttons, so in this step we will create a Button
class. First of all, a button has three states (as you may know from common Flash development): "Up", "Over" and "Down". This spritesheet illustrates that:
And now let's start the class:
package { import net.flashpunk.Entity; import net.flashpunk.graphics.Spritemap; public class Button extends Entity { protected var _map:Spritemap; public function Button(x:Number = 0, y:Number = 0) { super(x, y); } public function setSpritemap(asset:*, frameW:uint, frameH:uint):void { _map = new Spritemap(asset, frameW, frameH); _map.add("Up", [0]); _map.add("Over", [1]); _map.add("Down", [2]); graphic = _map; setHitbox(frameW, frameH); } override public function render():void { super.render(); } } }
The setSpritemap()
function sets a spritemap for the button and sets "animations" for the button. Always the image must have first the "Up" frame, then the "Over", followed by the "Down" frame. There's also a call to setHitbox()
. The hitbox will be used to check whether the mouse is or isn't over the button's box.
Step 12: Creating a Simple Button: Up/Over/Down Controls, Callback
Now that we have our Button successfully showing an image, it's time to create up, over and down controls. We will do it by creating two Boolean attributes: "over" and "clicked". We will also detect whether the mouse is over the button's hit box or not. Add these functions in Button.as
:
protected var _over:Boolean; protected var _clicked:Boolean; override public function update():void { if (!world) { return; } _over = false; _clicked = false; if (collidePoint(x - world.camera.x, y - world.camera.y, Input.mouseX, Input.mouseY)) { if (Input.mouseDown) { mouseDown(); } else { mouseOver(); } } } protected function mouseOver():void { _over = true; } protected function mouseDown():void { _clicked = true; } override public function render():void { if (_clicked) { _map.play("Down"); } else if (_over) { _map.play("Over"); } else { _map.play("Up"); } super.render(); }
And don't forget to import net.flashpunk.utils.Input
.
Following the logic in update()
: first of all, both attributes (_clicked
and _over
) are set to false. After that, we check if the mouse is over the button. If it isn't, the attributes will remain false and the button will be in the "Up" state. If the mouse is over, we check whether the mouse button is currently down. If that's true, the button is in the "Down" state and _clicked
is set to true; if it's false, then the button is in the "Over" state and the _over
attribute is set to true. These attributes will define which frame the spritemap should go to.
This button will be useless if you can't detect when the user has effectively clicked it. Let's change the class a bit in order to support callback functions:
protected var _callback:Function; protected var _argument:*; public function Button(callback:Function, argument:*, x:Number = 0, y:Number = 0) { super(x, y); _callback = callback; _argument = argument; } override public function update():void { if (!world) { return; } _over = false; _clicked = false; if (collidePoint(x - world.camera.x, y - world.camera.y, Input.mouseX, Input.mouseY)) { if (Input.mouseReleased) { clicked(); } else if (Input.mouseDown) { mouseDown(); } else { mouseOver(); } } } protected function clicked():void { if (!_argument) { _callback(); } else { _callback(_argument); } }
Our button is done! This code will allow you to pass a callback function (and optionally an argument) to your button, so whenever the user clicks the button, the function will be called.
Step 13: Creating a Simple Button: Adding It to the Screen
Many steps and nothing on the screen... Time to put a button in there! It's as simple as adding this code in GameWorld.as
:
[Embed(source = "/../img/ButtonSheet.png")] private const BUTTONSHEET:Class; private var _button:Button; public function GameWorld() { _gameEntity = new GameEntity(); _box = new Box(); _button = new Button(onButtonClick, null); _button.setSpritemap(BUTTONSHEET, 50, 40); add(_gameEntity); add(_box); add(_button); _box.x = 200; _box.y = 150; _button.x = 400; _button.y = 200; addGraphic(new Image(IMAGE), 0, 50, 50); } private function onButtonClick():void { FP.screen.color = Math.random() * 0xFFFFFF; trace("The button has been clicked!"); }
Now all you have to do is compile the project and the button will be there!
Step 14: The Console
And now the final feature from FlashPunk that will be presented in this tutorial! The Console
is FlashPunk's tool for debugging: it features logs, which are pretty much like traces; shows the time taken to run important engine step; and displays how many entities are on screen and the current FPS. It's a great tool to use when developing your game. To enable it, just add the following line to Main.as
:
override public function init():void { trace("The game has started!"); FP.console.enable(); FP.world = _gameWorld; }
And to log anything in it, use the FP.log()
function. For example, let's change that trace()
call:
override public function init():void { FP.console.enable(); FP.log("The game has started!"); FP.world = _gameWorld; }
That's pretty much it! You'll see that the "Output" part from the debugging console now shows the log. You can go ahead and change all the trace()
calls in our code to calls to FP.log()
.
Conclusion
And that's our introduction to FlashPunk, covering the most important aspects of this amazing library: entities, worlds, images and animations; collision, buttons, input and movement. I hope you'll like this library as much as I do - it really makes work easier!
Comments