Welcome back to my Phaser 3 tutorial, this week we're going to look at how you can set up motion for the asteroids we created in part 2 of this tutorial and set up 'lasers' to be fired from the ship! If you're new to this tutorial check out part one here. Alright let's get started with the asteroids first.
Asteroids.js
In order to handle the movement of the asteroids, we'll create a new file in our scenes directory called asteroids.js. This file will handle all of the logic for the asteroids in the game. First we'll set up the file like so.
export default class Asteroid extends Phaser.Physics.Arcade.Sprite {
constructor (scene, x, y) {
super(scene, x, y, 'asteroid');
this.speed = Phaser.Math.GetSpeed(100, 1);
this.orbiting = false;
this.direction = 0;
this.factor = 1;
}
}
This is the beginning of our asteroids file. Here's we're creating a new class that will be extends the Phaser 3 arcade sprite class. We'll define the constructor and add some additional properties that will come into play later. Let's take a look at the other methods in the asteroids class now.
launch(shipX, shipY) {
this.orbiting = true;
this.setActive(true);
this.setVisible(true);
let xOrigin = Phaser.Math.RND.between(0, 800);
let yOrigin = 0;
this.setPosition(xOrigin, yOrigin);
if (shipY > xOrigin) {
let m = (shipY - yOrigin) / (shipX - xOrigin);
this.direction = Math.atan(m);
}
else {
this.factor = -1;
let m = (shipY - yOrigin) / (xOrigin - shipX);
this.direction = Math.atan(m);
}
this.angleRotation = Phaser.Math.RND.between(0.2, 0.9);
}
The launch method above will be responsible for launch an asteroid in the game scene. We'll set this up later to be run on an interval. A lot of the logic in the launch function is dependent on taking into account the current position of the ship to generate a travel vector for the new asteroid. For the sake of this blog, I won't go too deep into the math involved, but please reach out if you have any questions! Next we'll take a look at the update function that will traverse the asteroid across the screen during the game.
update(time, delta) {
this.x += this.factor * Math.cos(this.direction) * this.speed * delta;
this.y += Math.cos(this.direction) * this.speed * delta;
this.angle += this.angleRotation;
if (this.x < -50 || this.y < -50 || this.x > 800 || this.y > 600) {
this.setActive(false);
this.setVisible(false);
this.destroy();
}
}
Each time the update function is called the asteroid will generate a new x and y coordinate for itself as well as a new angle. This information will then be used by Phaser 3 to calculate the new location of the asteroid on the screen. Once again there's a bit of math involved for calculating these new coordinates but don't worry too much about it. Finally if the asteroid is beyond the deminsions of the game screen, setActive and setVisible will both be false and this.destroy() will be invoked to destroy the asteroid since its no longer on the screen.
Back to PlayScene.js
Alright now that we have our new asteroid class we'll need to actually use it within our PlayScene.js file. Let's take a look at how that will work.
// Be sure to import the Asteroid.js file into the play
scene
create() {
// New logic for handling the asteroids
this.asteroidsGroup = this.physics.add.group();
this.asteroidsArray = [];
this.asteroidTimedEvent = this.time.addEvent({
delay: 1000,
callback: this.addAsteroid,
callbackScope: this,
loop: true,
});
}
Here we're creating a new asteroids group within our game scene and creating an asteroids array. We're also setting up a new timed evet to create a new asteroid every second. The callback within this event will be the function that is executed. So let's take a look at what this.addAsteroid is doing.
addAsteroid() {
let asteroid = new Asteroid(this, 0, 0, 'asteroid', 0).setScale(0.02);
this.asteroidsGroup.add(asteroid, true);
this.asteroidsArray.push(asteroid);
}
Everytime addAsteroid is called we'll create a new asteroid that is generated from our Asteroid class. We'll also add the new asteroid to the asteroid group and array as well. Finally we'll update the update function (see what I did there?). Add this code block inside of the update funciton.
for (const asteroid of this.asteroidsArray) {
if (!asteroid.isOrbiting()) {
asteroid.launch(this.ship.x, this.ship.y);
}
asteroid.update(time, delta);
}
This loop will check the asteroids array and if any of the asteroids are not orbiting the asteroid will be launched into the game scene and the update function will be invoked so they move across the screen like actual asteroids!
Shoot.js
Alright now we've got asteroids moving across the screen so let's set up a way for the player to actually shoot them and destroy them! In order to do this we'll create a new file called shoot.js. In this file we'll handle all the logic for the 'laser' that the ship fires. For the sake of brevity, I'll include the entirety of shoot.js below.
import Phaser from 'phaser';
export default class Shoot extends Phaser.Physics.Arcade.Sprite {
constructor(scene, x, y) {
super(scene, x, y, 'shoot');
this.speed = Phaser.Math.GetSpeed(500, 1);
}
fire(x, y, direction) {
this.setPosition(x, y);
this.setActive(true);
this.setVisible(true);
this.direction = direction;
this.rotation = this.direction;
}
update(time, delta) {
this.x += Math.cos(this.direction) * this.speed * delta;
this.y += Math.sin(this.direction) * this.speed * delta;
if (this.x < -50 || this.y < -50 || this.x > 800 || this.y > 600) {
this.setActive(false);
this.setVisible(false);
this.destroy();
}
}
}
Now that we have our shoot class we'll need to use it within the PlayScene.js file. Let's take a look at the changes necessary for that file now. First we'll need to add a new image for the 'laser' sprite that we'll be generating. I used just a basic blue blob, but you can use any image you'd like as long as it's a PNG file type. We'll load up this image in the preload function first.
preload() {
this.load.image('shoot', shoot);
}
Next we'll update the create function to include the new image we loaded in within a new Phaser group as well as set up a collision handler for the laser and asteroid.
this.shootsGroup = this.physics.add.group({
classType: Shoot,
maxSize: 1,
runChildUpdate: true,
});
this.physics.add.overlap(this.shootsGroup, this.asteroidsGroup, this.collision, null, this);
Now that the create function is updated, we'll move onto the update function. We'll add an additional control that will use the spacebar to fire the lasers.
if (this.cursors.space.isDown) {
const shoot = this.shootsGroup.get();
if (shoot) {
shoot.fire(this.ship.x, this.ship.y, this.ship.rotation);
}
}
Great now whenever the spacebar is pressed we'll create a new laser object in the game. Finally we'll need to define that function that handles the collision of the two game objects. This function will be called collision.
collision(laser, asteroid) {
laser.destroy();
asteroid.destroy();
}
The collision function will destroy both sprites once they collide with each other. Now we can successfully destroy asteroids in the game!
Conclusion
We've gone from a basic understanding of Phaser 3 to having a full functional game with the ability to shoot asteroids and destroy them. While this is a basic example there's a lot that can be expanded upon here. For more reading I recommend checking out the Phaser 3 documents, there's a lot of helpful information there that can help you create more robust and interactive games! I hope you enjoyed this series and I'll be back again soon for more Phaser 3 tutorials!
Top comments (0)