Shooting bullets using Phaser groups
- 4 Min. Read.
Groups are one of the most powerful features of PhaserJS, let’s take a look at how they work, by creating a small spaceship shooter example.
Finished example
What we’ll cover
- How to set up a Phaser Group
- Create an Arcade physics body for movement
- Detect when lasers are out of bound
- Object Pooling using Phaser Groups for improving performance
- Phaser mouse or touch input
- Phaser keyboard input events
Setup
We have use a very simple HTML snippet for this example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>SpaceType Shoot demo</title> <link rel="stylesheet" href="static/css/reset.css"/> <link rel="stylesheet" href="static/css/screen.css"/> <script src="static/js/libs/phaser.js"></script> </head> <body class="home"> <div id="container"></div> <script src="static/js/main.js"></script> </body> </html> |
Create your main.js file, set up a basic Phaser game instance and preload the assets.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
var GAME_WIDTH = 450; var GAME_HEIGHT = 700; // Game Variables var ship; var lasers; var mouseTouchDown = false; // Create a Phaser game instance var game = new Phaser.Game( GAME_WIDTH, GAME_HEIGHT, Phaser.AUTO, 'container', { preload: preload, create: create, update: update, init: init, render: render } ); // Preload assets function preload() { var dir = 'static/img/assets/'; game.load.image('ship', dir + 'playerShip1_red.png'); game.load.image('laser', dir + 'laserBlue02.png'); } // Init function init() { } // Assets are available in create function create() { } // Update function update() { } // Render some debug text on screen function render() { game.debug.text('CodeCaptain Shooting Demo', 10, 30); game.debug.text('Click or press space / enter to shoot', 10, 55); } |
Add a space ship to the stage
We’ll add a space ship on the stage in the create function. This is where the assets are available for use.
1 2 3 4 5 6 7 8 9 10 |
function create() { /* Create a ship using the sprite factory game.add is an instance of Phaser.GameObjectFactory, and helps us to quickly create common game objects. The sprite is already added to the stage */ ship = game.add.sprite(game.world.centerX, game.world.centerY, 'ship'); // Set the anchorpoint to the middle ship.anchor.setTo(0.5, 0.5); } |
When you fire up your HTML file in the browser, you’ll see your ship on screen.
Create the lasers group
Next up, we’ll create the group containing the lasers in create(). The code is documented throughout, and we’ll highlight some special things afterwards. Put this code before you create the ship.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
function create() { // Create the group using the group factory lasers = game.add.group(); // To move the sprites later on, we have to enable the body lasers.enableBody = true; // We're going to set the body type to the ARCADE physics, since we don't need any advanced physics lasers.physicsBodyType = Phaser.Physics.ARCADE; /* This will create 20 sprites and add it to the stage. They're inactive and invisible, but they're there for later use. We only have 20 laser bullets available, and will 'clean' and reset they're off the screen. This way we save on precious resources by not constantly adding & removing new sprites to the stage */ lasers.createMultiple(20, 'laser'); /* Behind the scenes, this will call the following function on all lasers: - events.onOutOfBounds.add(resetLaser) Every sprite has an 'events' property, where you can add callbacks to specific events. Instead of looping over every sprite in the group manually, this function will do it for us. */ lasers.callAll('events.onOutOfBounds.add', 'events.onOutOfBounds', resetLaser); // Same as above, set the anchor of every sprite to 0.5, 1.0 lasers.callAll('anchor.setTo', 'anchor', 0.5, 1.0); // This will set 'checkWorldBounds' to true on all sprites in the group lasers.setAll('checkWorldBounds', true); // ... } function resetLaser(laser) { // Destroy the laser laser.kill(); } |
The callAll and setAll function might be a bit difficult to understand, but look at it like this:
These functions are basically saving you time by looping over every object for you. There’s no need for you to manually loop over the sprites and apply the properties or call the functions.
If we take a look at the Phaser source code for setAll, this is what’s happening.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Phaser.Group.prototype.setAll = function (key, value, checkAlive, checkVisible, operation, force) { if (checkAlive === undefined) { checkAlive = false; } if (checkVisible === undefined) { checkVisible = false; } if (force === undefined) { force = false; } key = key.split('.'); operation = operation || 0; for (var i = 0; i < this.children.length; i++) { if ((!checkAlive || (checkAlive && this.children[i].alive)) && (!checkVisible || (checkVisible && this.children[i].visible))) { this.setProperty(this.children[i], key, value, operation, force); } } }; |
It’s looping over all children and it sets the given property on the child, simple as that.
Time to make the ship shoot
Whenever we click, tap or press space / enter, we’ll make the ship shoot a bullet.
We’ll start with clicking and tapping, since those are easiest.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
function update() { // Game.input.activePointer is either the first finger touched, or the mouse if (game.input.activePointer.isDown) { // We'll manually keep track if the pointer wasn't already down if (!mouseTouchDown) { touchDown(); } } else { if (mouseTouchDown) { touchUp(); } } } function touchDown() { // Set touchDown to true, so we only trigger this once mouseTouchDown = true; fireLaser(); } function touchUp() { // Set touchDown to false, so we can trigger touchDown on the next click mouseTouchDown = false; } function fireLaser() { // Get the first laser that's inactive, by passing 'false' as a parameter var laser = lasers.getFirstExists(false); if (laser) { // If we have a laser, set it to the starting position laser.reset(ship.x, ship.y - 20); // Give it a velocity of -500 so it starts shooting laser.body.velocity.y = -500; } } |
When you click the screen now, lasers will start shooting out of the ship! When the lasers are out of bounds, they are killed and put back in the pool for reuse.
Adding keyboard input
For listening to keyboard input, we first have to let Phaser know which keys we want to observe.
1 2 3 4 5 6 7 8 |
function init() { // Listen to space & enter keys var keys = [Phaser.KeyCode.SPACEBAR, Phaser.KeyCode.ENTER]; // Create Phaser.Key objects for listening to the state phaserKeys = game.input.keyboard.addKeys(keys); // Capture these keys to stop the browser from receiving this event game.input.keyboard.addKeyCapture(keys); } |
Then in update() we loop over the keys, and check whether or not they were just pressed. If they were, we fire a laser.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function update() { // Loop over the keys for (var index in phaserKeys) { // Save a reference to the current key var key = phaserKeys[index]; // If the key was just pressed, fire a laser if (key.justDown) { fireLaser(); } } // ... } |
And that concludes this overview of how Phaser Groups work, I hope you learned something new today, and you can find the completed demo on github.