Shooting bullets in Phaser 3 using Arcade Physics Groups
- 3 Min. Read.
Phaser Physics Arcade Groups are one of the most powerful features of Phaser3, let’s take a look at how they work, by creating a small spaceship shooter example.
What we’ll cover
- How to set up a Phaser 3 Arcade Physics Group
- Create an Arcade physics group for pooling objects
- Use the mouse to make the space ship move
- Detect when lasers are out of bound and reset them
- Object Pooling using Phaser Groups for improving performance
- Phaser mouse and keyboard input events
Setup
Clone our repository and switch to the ‘start‘ branch using git checkout start
Running a webserver
Because of the way Phaser loads assets, we need a webserver to make it work correctly. You can use PHP for this:
1 |
php -S localhost:9000 |
This will fire up a webserver in the current working directory. You can now navigate to http://localhost:9000 in your browser
Adding our ship
Open up main.js
and let’s add our space ship in the create
function:
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 |
class SpaceScene extends Phaser.Scene { constructor() { super(); // To keep things clean, I like to initialise empty variables in the constructor this.ship; } // Load all the assets preload() { this.load.image('laser', '/static/img/assets/laserBlue02.png'); this.load.image('ship', '/static/img/assets/playerShip1_red.png'); } create() { this.cameras.main.setBackgroundColor(0x1D1923); // Add our ship this.addShip(); } // Create this function addShip() { const centerX = this.cameras.main.width / 2; const bottom = this.cameras.main.height - 90; this.ship = this.add.image(centerX, bottom, 'ship') } update() { } } |
Making the ship follow the mouse
To make the ship follow the mouse, we’ll make use of a pointermove
event. Create a new function called addEvents
with the following content:
1 2 3 4 5 6 7 8 9 10 11 |
addEvents() { this.input.on('pointermove', (pointer) => { this.ship.x = pointer.x; }); } // In create, call the function addEvents create() { // ... this.addEvents(); } |
This will make the ship follow the mouse on the horizontal axis.
Creating a laser group
We’ll create a class called LaserGroup
which inherits from Phaser.Physics.Arcade.Group
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class LaserGroup extends Phaser.Physics.Arcade.Group { constructor(scene) { // Call the super constructor, passing in a world and a scene super(scene.physics.world, scene); // Initialize the group this.createMultiple({ classType: Laser, // This is the class we create just below frameQuantity: 30, // Create 30 instances in the pool active: false, visible: false, key: 'laser' }) } } class Laser extends Phaser.Physics.Arcade.Sprite { constructor(scene, x, y) { super(scene, x, y, 'laser'); } } |
And next up create a new instance of LaserGroup
in the create
function (don’t forget to initialize the variable in the constructor):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class SpaceScene extends Phaser.Scene { constructor() { // ... this.laserGroup; } // ... create() { // ... this.laserGroup = new LaserGroup(this); this.addShip(); this.addEvents(); } } |
Shooting a laser
In our function addEvents
we’ll add a new listener to capture mouse clicks. This event is called pointerdown
:
1 2 3 4 5 6 |
addEvents() { // ... this.input.on('pointerdown', pointer => { this.shootLaser(); }); } |
Afterwards create the function shootLaser
:
1 2 3 |
shootLaser() { this.laserGroup.fireLaser(this.ship.x, this.ship.y - 20); } |
And in our LaserGroup
class we’ll add the function fireLaser
:
1 2 3 4 5 6 7 8 9 10 11 |
class LaserGroup extends Phaser.Physics.Arcade.Group { // ... fireLaser(x, y) { // Get the first available sprite in the group const laser = this.getFirstDead(false); if (laser) { laser.fire(x, y); } } } |
And finally, we’ll add the fire
method to the Laser
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Laser extends Phaser.Physics.Arcade.Sprite { // ... fire(x, y) { this.body.reset(x, y); this.setActive(true); this.setVisible(true); this.setVelocityY(-900); } } |
Refresh your browser & try clicking, the laser beams should now shoot! … But after shooting for 30 times the bullets have run out, what gives?
Our pool with 30 Laser
instances has depleted, since we never clean the lasers we shot. To do that, add the following code in the Laser class:
1 2 3 4 5 6 7 8 9 10 11 12 |
class Laser extends Phaser.Physics.Arcade.Sprite { // ... preUpdate(time, delta) { super.preUpdate(time, delta); if (this.y <= 0) { this.setActive(false); this.setVisible(false); } } } |
This will mark the laser as inactive, after it reached the end of the screen. Now we can infinitely shoot our bullets!
Adding keyboard events
To fire lasers with the spacebar / enter key, we can add 2 listeners:
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 |
class SpaceScene extends Phaser.Scene { constructor() { // ... this.inputKeys; } // ... addEvents() { this.inputKeys = [ this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE), this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.ENTER), ]; } // ... update() { // Loop over all keys this.inputKeys.forEach(key => { // If key was just pressed down, shoot the laser. We use JustDown to make sure this only fires once. if (Phaser.Input.Keyboard.JustDown(key)) { this.shootLaser(); } }); } } |
To make sure our laser only shoots once, we can use Phaser.Input.Keyboard.JustDown(key)
. This function will only return true once until the key is pressed up again.