Welcome to part 5 of my SHMUP tutorial series! This time we are going to tackle adding in some particle effects for killing enemies and dying! If you haven’t followed part 4 of the series you can download this project file to follow along.
Let’s go ahead and just start off with a new class! Let’s name it “ParticleController.as”
package { import flash.display.BitmapData; import net.flashpunk.Entity; import net.flashpunk.graphics.Emitter; import net.flashpunk.utils.Ease; public class ParticleController extends Entity { private var _emitter:Emitter; public function ParticleController() { _emitter = new Emitter(new BitmapData(3, 3, false, 0xFFFFFF), 3, 3); _emitter.newType("explosion", [0]); _emitter.setMotion("explosion", 0, 100, 2, 360, -40, 1, Ease.quadOut); _emitter.setAlpha("explosion", 1, 0.1); graphic = _emitter; layer = GC.LAYER_PARTICLES; } public function explosion(_x:Number, _y:Number, particles:int = 20 ):void { for (var i:uint = 0; i < particles; i++) { _emitter.emit("explosion", _x, _y); } } } } |
Yep! That’s it for our explosions! Let’s go over what we did here.
private var _emitter:Emitter; |
Here we are declaring a new variable of the type Emitter from the Flashpunk Library. This is what handles all of your particles.
_emitter = new Emitter(new BitmapData(3, 3, false, 0xFFFFFF), 3, 3); |
Now we are going to set our emitter to a new Emitter, now since we are just going to use some white squares we can just create a new bitmap to pass to it instead of embedding an image. With new BitmapData I tell it how wide and tall I want it, then if I want it to have transparency, and finally I set it’s fill color. After that we tell the emitter how tall and wide each frame in your bitmap is for each particle you want to use. It’s very similar to a spritemap that we covered in our last tutorial.
_emitter.newType("explosion", [0]); |
Here we setup a new emitter type, setting it’s name and what frame it will display. Since we only have one frame in our bitmap we made then it can only use that first frame. If we passed our emitter an image with multiple particles on it we could setup an animation that our particle would play once through it’s life by passing the frame numbers you want to use eg. [0, 1, 2, 3].
_emitter.setMotion("explosion", 0, 100, 2, 360, -40, 1, Ease.quadOut); |
This is the meat of our particle animation. This tells our emitter how a certain particle will move.
First we tell it which particle type we are setting the motion of, then we set our initial angle, followed by the distance our particle will travel during it’s life from it’s initial location, then we have the duration which is how long our particle will live in seconds before being removed, next is the angle range which adds a random number to our initial angle. 360 means that it will pick a random number between 0 and 360 and add it to our initial angle. Using 360 means our explosion particles move in every direction, this brings us to the distance range this adds a random number to our distance. I like to use a negative number so that it subtracts a number from our initial distance and we get a number that is between 60 and 100, and then we have the duration Range which like the other ranges adds a random number to our duration, finally we have our Ease method. Easing is what makes our particles move in different speeds from the start of their life to the end. I chose quadOut because it gives a nice explosion pop at the start the fades off slowly. I must apologize for how horribly this section is written, English has never been my strongest subject but hopefully it still makes some sense.
_emitter.setAlpha("explosion", 1, 0.1); |
This is a lot easier to explain. This just sets our alpha for our particles from the start of their life to the end.
graphic = _emitter; layer = GC.LAYER_PARTICLES; |
Next we set our graphic to be our emitter. And our layer to equal.. Hey what is this GC? Well it turns out I’m going to sneak in some refactoring in this tutorial as well. More on that later, but for now just know that this is accessing a global constant to tell it what layer it should render at.
public function explosion(_x:Number, _y:Number, particles:int = 20 ):void { for (var i:uint = 0; i < particles; i++) { _emitter.emit("explosion", _x, _y); } } |
Finally this brings us to the end of our Particle Controller. This is the magic that let’s other classes tell it to create an explosion at a set location, and if you so desire how many particles it will emit. We do this with a simple for loop, and then the emit function passing the location that we want our new explosion particles to start at.
Awesome! Particles! But what was this about a GC reference? Let’s make a new class and name it “GC.as” this stands for Global Constants, a naming convention that I picked up from The Village Blacksmith.
package { public class GC { public static const LAYER_BACKGROUND:int = 5; public static const LAYER_PARTICLES:int = 4; public static const LAYER_BULLETS:int = 3; public static const LAYER_PLAYER:int = 2; public static const LAYER_ENEMY:int = 1; } } |
Not much to this class, but it really saves us a lot of headaches later on. This let’s us sort out our layers for all of our different objects in one place instead of having to dig into each file and rename them. You might notice there are more layers than just Particles… yep that’s right we are going to change all of our entities to reference these constants!
Before we go into that, let’s first make another very handy class “GV.as” this stands for Global Variables, yet another naming convention that I picked up from The Village Blacksmith.
package { public class GV { public static var PARTICLE_EMITTER:ParticleController = new ParticleController; public static var PLAYER:Player = new Player; } } |
There are only two variables here, but this let’s us easily access them from any of our other classes, which is very useful for our particle emitter!
Excellent, that’s it for new files in this tutorial! Let’s continue and start by updating our “GameWorld.as”
package { import net.flashpunk.Entity; import net.flashpunk.FP; import net.flashpunk.graphics.Backdrop; import net.flashpunk.World; public class GameWorld extends World { private var cameraSpeed:Number = 50; private var nextSpawn:Number = 1; public function GameWorld() { GV.PLAYER = new Player; GV.PLAYER.x = 64; GV.PLAYER.y = 240; add(GV.PLAYER); GV.PARTICLE_EMITTER = new ParticleController; add(GV.PARTICLE_EMITTER); var b:Backdrop = new Backdrop(Assets.GRAPHIC_STARS, true, true); b.scrollX = 0.5; b.scrollY = 0.5; var e:Entity = new Entity; e.graphic = b; e.layer = GC.LAYER_BACKGROUND; add(e); } override public function update():void { super.update(); camera.x += cameraSpeed * FP.elapsed; GV.PLAYER.x += cameraSpeed * FP.elapsed; nextSpawn -= FP.elapsed; if (nextSpawn <= 0) { nextSpawn = 1; var _enemy:Enemy = create(Enemy) as Enemy; _enemy.x = FP.camera.x + FP.width + 100; _enemy.y = FP.rand(FP.height); add(_enemy); } } } } |
Okay first off, you should note that we have removed our player variable from this class and updated our player to use the Global variables. We then add our particle emitter in the same way. And then our final update is to our background that we added, it is now referencing our GC.BACKGROUND_LAYER constant.
Next open up your “Enemy.as” file. There are only going to be two lines added here so I won’t bother posting the whole file this time.
First off, right after :
setHitbox(32, 10, 16, 5); |
Add in the line :
layer = GC.LAYER_ENEMY; |
Now our enemies are using our global constant for their layers.
Next, update our destroy function, to look like this :
GV.PARTICLE_EMITTER.explosion(x, y); FP.world.recycle(this); |
This will tell our particle emitter to create an explosion at our enemies location whenever our enemies are destroyed.
Awesome! This should work now to show us some explosions, but we still have a little bit of refactoring I would like to get done in this tutorial to make the rest of the series go smoother.
Next open up our “Player.as” and update our Player function to this :
public function Player() { _image = new Image(Assets.GRAPHIC_SHIP_PLAYER); _image.centerOO(); graphic = _image; type = "Player"; setHitbox(32, 32, 16, 16); layer = GC.LAYER_PLAYER; } |
We had a lot of definitions that didn’t really belong in our player script, and we needed to set our player’s layer to the global constant.
Next let’s update what happens when we collide into an enemy in our update function.
if (collide("Enemy", x, y)) { GV.PARTICLE_EMITTER.explosion(x, y); x = FP.camera.x + 32; y = FP.halfHeight; _image.alpha = 0; } |
Here we are just telling our particle emitter to create an explosion at our ship’s location before we move it. This gives us more of an indication of dying.
Great! We are almost done refactoring! But surprise! There is one more new file that I would like to add in before our final refactor update!
Make a new file named “MainMenu.as”
package { import net.flashpunk.Entity; import net.flashpunk.FP; import net.flashpunk.graphics.Text; import net.flashpunk.utils.Input; import net.flashpunk.World; public class MainMenu extends World { public function MainMenu() { var t:Text = new Text("Press space to start playing"); var e:Entity = new Entity(FP.halfWidth - (t.width / 2), FP.halfHeight, t); add(e); } override public function update():void { super.update(); if (Input.pressed("fire")) FP.world = new GameWorld; } } } |
This will be our main menu when we start up the game, instead of jumping right into playing. As you can see here, the text class makes adding text to our game very easy. Another thing to take note is unlike when we added a new entity for our background, this time we use the entity initialization properties to set it’s x / y location and it’s graphic. Finally we override our update method to change the World when our player presses the “fire” button. This is the main reason why we wanted to move our input declarations out from our player script, so we can use them before we create an instance of the Player class.
That finally brings us to our last refactor! Open up your “Main.as”
package { import net.flashpunk.Engine; import net.flashpunk.FP; import net.flashpunk.utils.Input; import net.flashpunk.utils.Key; public class Main extends Engine { public function Main() { super(560, 280, 60, false); } override public function init():void { super.init(); FP.world = new MainMenu; Input.define("right", Key.RIGHT, Key.D); Input.define("left", Key.LEFT, Key.A); Input.define("down", Key.DOWN, Key.S); Input.define("up", Key.UP, Key.W); Input.define("fire", Key.SPACE); FP.console.enable(); } } } |
Now our input is declared as soon as our engine is started, if you don’t want your console enabled feel free to leave out adding in that line. This tutorial might be a bit long for what we did, but it’s handy to refactor early when you know it will need to be done to make your life easier later!
You can download a zip containing the entire project for this tutorial here.