PhaserByExample v2 4
PhaserByExample v2 4
ease: "Linear",
duration: 3000,
});
}
If a player fails, we will show a message to the player and then we reveal the
current scoreboard.
showShame(playerName) {
const rants = [
"You're a disgrace",
"Shame on you",
"You dishonor us all",
"You're a disappointment",
"You're a failure",
"You dishonor this dojo",
];
this.scoreText1.setText(Phaser.Math.RND.pick(rants)).setAlpha(1);
this.scoreText2.setText(`${playerName}`).setAlpha(1);
this.tweens.add({
targets: [this.scoreText1, this.scoreText2],
alpha: { from: 1, to: 0 },
ease: "Linear",
duration: 3000,
onComplete: () => {
this.showResult();
},
});
}
This shows the current number and the next operation.
showNextOperation(operator, nextNumber) {
this.numberText.setText(this.number);
this.operatorText.setText(`${operator}${nextNumber}`);
}
}
List: game.js
321
The biggest issue with this type of game is that you may have several users
trying to guess. This implementation just considers one winner, but you can
try other approaches.
Chat
This is the differential class in the game. As the name implies, this class
takes care of chat connection and parsing commands. The settings for the
connection are taken from the URL string that is set in the initial HTML
splash form. Once the connection is established, this class just parses and
passes commands to the game.
import tmi from "tmi.js";
this.init();
}
This is where we create the connection to the chat. We just specify the chan-
nel, but we could add and identity to log in with a user and send messages to
the channel or do actions during the game. With just the channel connection,
we will be able to read the chat and act accordingly, which could be enough
for some games.
init() {
console.log(
"Chat channel: ",
322
this.channel,
"feedback: ",
this.feedback,
"maxPlayers: ",
this.maxPlayers
);
this.client = new tmi.Client({
options: { debug: false },
// identity: {
// username: "devdiaries", // We could actualy log in with a user
// password: NOPE // and send messages or do actions
// },
channels: [this.channel],
});
this.client
.connect()
.then((ok) => {
console.log("Connected!, loading game");
this.scene.loadGame();
})
.catch(console.error);
this.setOnJoinListener();
this.setOnMessageListener();
this.setOnChatListener;
}
We add a listener to the join event, so we can add the player to the game
when they join the chat.
setOnJoinListener() {
this.client.on("join", (channel, username, self) => {
console.log("Somebody joined the chat: ", channel, username);
if (self) {
this.scene.addPlayer(username);
}
});
}
323
Messages to the chat can come with two different events: message or chat.
We will process them both in the same way, but we need different event
callbacks because the data comes in different formats.
setOnMessageListener() {
this.client.on("message", (channel, tags, message, self) => {
console.log(`Message: ${tags.username} just ${message}`);
this.processMessage(tags.username, message);
});
}
setOnChatListener() {
this.client.on("chat", async (channel, user, message, self) => {
if (user.mod) {
// User is a mod.
}
const messageParts = message.toLowerCase().split(" ");
console.log("Received chat: ", channel, user, messageParts);
this.processMessage(user["display-name"], message);
});
}
Once we isolate the username and the message, we can process the message.
In this case, we will check if the message is a number and if it is, we will send
it to the game to check if it is the correct answer.
processMessage(username, message) {
if (this.isValidNumber(message)) {
this.scene.guess(username, +message);
}
}
We are not using this function but I leave it here: in case you want to send
actions to the chat, like /me does.
sendAction(channel, msg) {
console.log("Sending action: ", this.feedback, channel, msg);
if (!this.feedback) return;
this.client.action(channel, msg);
324
}
We are not using this function either, but this is how you do it in case you
want to send messages to the chat.
say(msg) {
if (!this.feedback) return;
this.client.say(this.channel, msg);
}
We use these two functions to validate the number sent by the user. It must
be a number within a limit.
isValidNumberWithMax(number, limit = 100) {
return this.isValidNumber(number) && +number > 0 && +number <= limit;
}
isValidNumber(number) {
return !isNaN(number);
}
}
List: chat.js
This example connection just uses read-only privileges on the chat channel.
You can go further and set users with special permission to let the game
post messages and even perform commands like temporal bans or any other
action that you may consider making a part of the game.
325
Figure 38: Twitch game screen
326
Figure 39: Twitch game screen
Chatdefense
Source code: https://github.com/pxai/phasergames/tree/master/chatdefense
Play it here: https://pello.itch.io/chatdefense
Chatdefense
Source code: https://github.com/pxai/phasergames/tree/master/chatdefense
Play it here: https://pello.itch.io/chatdefense
327
8. 3D: Fate
328
Init
This time the init will be a bit different, as we need to set the game to be
3D.
import * as Phaser from "phaser";
import { enable3d, Canvas } from "@enable3d/phaser-extension";
import Bootloader from "./scenes/bootloader";
import Outro from "./scenes/outro";
import GameOver from "./scenes/game_over";
import Splash from "./scenes/splash";
import Story from "./scenes/story";
import Game from "./scenes/game";
const config = {
type: Phaser.WEBGL,
transparent: true,
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
width: 1280,
height: 720,
},
scene: [Bootloader, Story, Splash, Game, Outro, GameOver],
...Canvas(),
};
We need this specific way to load the game because we are using the 3D
extension.
window.addEventListener("load", () => {
enable3d(() => new Phaser.Game(config)).withPhysics("./assets/ammo");
});
List: main.js
Also, notice that the last command differs from the previous ones we were
using in simple 2D games with arcade physics or matter physics.
329
Bootloader
We can alternate ordinary 2D scenes so we can use our bootloader to load
assets in different methods. Pay attention to the new type of assets: videos!
import { Scene3D } from "@enable3d/phaser-extension";
330
this
);
this.load.on(
"complete",
() => {
this.scene.start("story");
},
this
);
}
This is a method to load the fonts.
loadFonts() {
this.load.bitmapFont(
"pixelFont",
"assets/fonts/mario.png",
"assets/fonts/mario.xml"
);
this.load.bitmapFont(
"computer",
"assets/fonts/computer.png",
"assets/fonts/computer.xml"
);
}
We load this logo that looks old to match the style of the splash.
loadImages() {
this.load.image("pello_logo_old", "assets/images/pello_logo_old.png");
}
We need to keep track of the deviation -hits- and the number of probes.
setRegistry() {
this.registry.set("deviation", "0");
this.registry.set("probes", "20");
}
We load the sounds and the music.
loadAudios() {
331
Array(4)
.fill(0)
.forEach((e, i) => {
this.load.audio(`thunder${i}`, `./assets/sounds/thunder${i}.mp3`);
});
Array(2)
.fill(0)
.forEach((e, i) => {
this.load.audio(`passby${i}`, `./assets/sounds/passby${i}.mp3`);
});
Array(4)
.fill(0)
.forEach((_, i) => {
this.load.audio(`hit${i + 1}`, `assets/sounds/hit${i + 1}.mp3`);
});
this.load.image("logo", "assets/images/logo.png");
this.load.audio("hymn", "assets/sounds/hymn.mp3");
this.load.audio("music", "assets/sounds/music.mp3");
this.load.audio("type", "assets/sounds/type.mp3");
this.load.audio("shot", "assets/sounds/shot.mp3");
this.load.audio("voice_start", "assets/sounds/voice_start.mp3");
this.load.audio("voice_drop", "assets/sounds/voice_drop.mp3");
this.load.audio("voice_hit", "assets/sounds/voice_hit.mp3");
}
In this game, we are using videos! They will be player in the presentation
scene that comes before the Splash.
loadVideos() {
Array(4)
.fill(0)
.forEach((e, i) => {
this.load.video(
`video${i}`,
`./assets/videos/video${i}.mp4`,
"loadeddata",
false,
true
);
332
});
}
As you may already now, this is a method to create the loading bars.
createBars() {
this.loadBar = this.add.graphics();
this.loadBar.fillStyle(0x06e18a, 1);
this.loadBar.fillRect(
this.cameras.main.width / 4 - 2,
this.cameras.main.height / 2 - 18,
this.cameras.main.width / 2 + 4,
20
);
this.progressBar = this.add.graphics();
}
}
List: bootloader.js
In this particular case, after the bootloader, we will show a Story scene.
Story
This is a scene that we show before the splash screen. It starts with some
text explaining an enigma and then it changes to a video with credits. Pretty
much as we would see in any movie. The videos change as we show more
information about the game.
import { Scene3D } from "@enable3d/phaser-extension";
import Utils from "../gameobjects/utils";
333
Figure 41: Intro screen
334
this.game.sound.stopAll();
this.width = this.sys.game.config.width;
this.height = this.sys.game.config.height;
this.center_width = this.width / 2;
this.center_height = this.height / 2;
this.utils = new Utils(this);
this.loadAudios();
this.showIntro();
this.cameras.main.setBackgroundColor(0x000000);
this.input.keyboard.on("keydown-SPACE", () => this.startGame(), this);
}
If the player presses the space bar, we start the game by cutting the typing
and the music.
startGame() {
if (this.utils.typeAudio) this.utils.typeAudio.stop();
if (this.theme) this.theme.stop();
this.scene.start("splash");
}
With this method, we load the music that will be played during the story.
The next method loads audio files (just the type) and then we have a method
to play them.
playMusic(theme = "hymn") {
this.theme = this.sound.add(theme);
this.theme.stop();
this.theme.play({
mute: false,
volume: 0.7,
rate: 1,
detune: 0,
seek: 0,
loop: true,
delay: 0,
});
}
loadAudios() {
335
this.audios = {
type: this.sound.add("type"),
};
}
playAudio(key) {
this.audios[key].play();
}
This is a text intro that is shown before the videos. It is typed and then
removed.
showIntro() {
let text1, text2;
text1 = this.utils.typeText(
" IN 1968 YURI GAGARIN DIED\nDURING A ROUTINE FLIGHT",
"computer",
this.center_width,
this.center_height
);
this.time.delayedCall(
5500,
() => {
text2 = this.utils.typeText(
" OR SO THEY MADE US BELIEVE...",
"computer",
this.center_width,
this.center_height + 100
);
},
null,
this
);
336
this.aGameBy();
},
null,
this
);
}
This function generates the first part of the video. The programmer logo,
some text and the first video.
aGameBy() {
let text2;
let text1 = this.utils.typeText(" A GAME BY\nPELLO", "computer", 1250, 10);
let pelloLogo = this.add
.image(990, 120, "pello_logo_old")
.setScale(0.2)
.setOrigin(0.5);
let video = this.add.video(400, 300, "video0");
this.time.delayedCall(
5000,
() => {
this.utils.removeTyped([text1]);
pelloLogo.destroy();
text2 = this.utils.typeText(
" MINIJAM #96\nFATE",
"computer",
1250,
400
);
},
null,
this
);
this.time.delayedCall(9000, () => {
this.utils.removeTyped([text2]);
video.stop();
video.destroy();
337
this.tools();
});
video.play(true);
}
This is the second part of the video. It shows the tools used to create the
game.
tools() {
let text2;
let text1 = this.utils.typeText(
" TOOLS: PHASER AND ENABLE3D",
"computer",
550,
10
);
let video = this.add.video(this.center_width, 500, "video1").setOrigin(0.5);
this.time.delayedCall(
5000,
() => {
this.utils.removeTyped([text1]);
text2 = this.utils.typeText(" MY FIRST 3D GAME!", "computer", 550, 50);
},
null,
this
);
this.time.delayedCall(9000, () => {
this.utils.removeTyped([text2]);
video.stop();
video.destroy();
this.otherTools();
});
video.play(true);
}
338
This is the third part of the video. It shows other tools used to create the
game and the amount of coffee consumed.
otherTools() {
let text2;
let text1 = this.utils.typeText(
" VSCODE, GULP, BLENDER, FFMPEG,...",
"computer",
550,
500
);
let video = this.add.video(this.center_width, 100, "video2").setOrigin(0.5);
this.time.delayedCall(
5000,
() => {
this.utils.removeTyped([text1]);
text2 = this.utils.typeText(
" GAZILLIONS OF COFFEE WERE CONSUMED",
"computer",
550,
600
);
},
null,
this
);
this.time.delayedCall(10000, () => {
this.utils.removeTyped([text2]);
video.stop();
video.destroy();
this.lastVideo();
});
video.play(true);
}
Finally, another video and more credits for the music.
339
lastVideo() {
let text2;
let text1 = this.utils.typeText(
" MUSIC: SACRED WAR, BY THE RED ARMY CHOIR",
"computer",
400,
50
);
let video = this.add.video(this.center_width, 400, "video3").setOrigin(0.5);
this.time.delayedCall(
5000,
() => {
this.utils.removeTyped([text1]);
text2 = this.utils.typeText(
" EVOLUTION, BY BENSOUND",
"computer",
550,
100
);
},
null,
this
);
this.time.delayedCall(10000, () => {
this.utils.removeTyped([text2]);
video.stop();
video.destroy();
this.explanation();
});
video.play(true);
}
This is a long text that explains the story of the game. It is typed and then
removed.
explanation() {
340
this.tweens.add({
targets: this.theme,
volume: { from: 1, to: 0 },
duration: 16000,
});
const text =
" GAGARIN WAS SENT ON A SECRET MISSION\nBEYOND THE OORT CLOUD, " +
"PROPELLED BY\nNUCLEAR DETONATIONS.\n\n
HE HAS NOW PASSED THE FRONTIER OF\nOUR SOLAR SYSTEM\n" +
"HIS MISSION:\nTO SET 20 PROBES AND RECOLLECT DATA
\nFROM THE DEADLIEST STELLAR OBJECT:\n" +
"A NEUTRINO STAR!\n\nHE HAS TO AVOID INCOMING DEBRIS
\nAND GET AS CLOSE AS POSSIBLE TO THE STAR.\n" +
"THAT WILL MEAN CERTAIN DEATH, BUT ALSO\nA MASSIVE ACHIEVEMENT " +
"FOR SOVIET SCIENTISTS!\n\n" +
"THE FATAL FATE OF GAGARIN IS NOW TIED\nTO THE GLORIOUS FATE " +
"OF MOTHER RUSSIA...\n\n\nSPACE TO CONTINUE";
let text1 = this.utils.typeText(text, "computer", 450, 50);
}
}
List: story.js
Apart from doing a movie-like intro, this simple trick is great for supporting
the backstory of our hero Yuri Gagarin.
Bullet Hell
This is a utility class used to generate different bullet waves. This particular
game uses very simple patterns but it could be extended at will.
export default class BulletHell {
constructor() {
this._functions = [
this.flat,
this.tlaf,
this.horizontal,
this.multiWave,
this.cos,
341
this.tan,
this.ripple,
];
}
get functions() {
return this._functions;
}
These are different functions that we will use to generate the path of the
bullets. They’re quite simple, but you can create your own functions to
generate more complex paths. We will use the x, y, z and time parameters
to generate different patterns.
sin(x, time) {
return Math.sin(x);
}
flat(x, y, z) {
return x + z;
}
tlaf(x, y, z) {
return -x - z;
}
horizontal(x, y, z) {
return z;
}
wave(x, time) {
return Math.sin(Math.PI * (x + time));
}
multiWave(x, time) {
return Math.sin(Math.PI * (x + time));
}
cos(x, time, z) {
342
return Math.cos(x) * Phaser.Math.Between(0.1, 0.9);
}
tan(x, time, z) {
return Math.tan(x);
}
ripple(x, time, z) {
return Math.sin(time * x * (Math.PI / 360));
}
}
List: bullet_hell.js
As you can imagine, our best friend for bullet hells is math. Did you hate
trigonometry at school? No, you shouldn’t have! Maths and physics are your
best friends here!
Lightning
This class generates a lightning effect. It’s a 2D effect, where we just alternate
black and white rectangles in front of the player with a thundering noise.
Yeah, in the vacuum there are no sounds but imagine a completely silent
game.
export default class Lightning {
constructor(scene) {
this.scene = scene;
}
In this method, we create a timeline to show the lightning effect. We use
the lightningEffect rectangle to show the lightning and the lightsOut
rectangle to darken the screen.
lightning() {
if (Phaser.Math.Between(1, 11) < 10) return;
const timeline = this.scene.add.timeline();
timeline.add({
targets: this.scene.lightningEffect,
alpha: { from: 0, to: 1 },
343
duration: 100,
repeat: 3,
});
if (this.scene.lights.out) {
timeline.add({
targets: this.scene.lightsOut,
alpha: { from: 1, to: 0.5 },
duration: 500,
});
}
timeline.add({
targets: this.scene.lightningEffect,
alpha: { from: 1, to: 0 },
duration: 2000,
});
if (this.scene.lights.out) {
timeline.add({
targets: this.scene.lightsOut,
alpha: { from: 0.5, to: 1 },
duration: 500,
});
}
timeline.play();
this.scene.playRandom("thunder" + Phaser.Math.Between(0, 3));
}
}
List: lightning.js
Is there lightning when you get close to a neutrino star? Well, only Gagarin
knows but it’s a simple but nice effect that we could add that makes us
believe that we are in a really dangerous environment.
Utils
We reuse this typing effect again for transition scenes.
344
export default class Utils {
constructor(scene) {
this.scene = scene;
}
characters.forEach((character, i) => {
timeline.add({
345
at: 0,
tween: {
targets: character,
alpha: { from: 0, to: 0.5 },
duration: 100,
},
});
});
timeline.add({
at: 0,
tween: {
targets: ending,
alpha: { from: 0, to: 0.8 },
duration: 100,
repeat: 5,
yoyo: true,
onStart: () => {
this.typeAudio.stop();
},
}
});
this.typeAudio.play({
mute: false,
volume: 1,
rate: 1,
detune: 0,
seek: 0,
loop: true,
delay: 0,
});
timeline.play();
characters.push(ending);
return characters;
}
This simple method will destroy all the characters of the text.
removeTyped(texts) {
346
texts.flat().forEach((char) => char.destroy());
}
}
List: utils.js
Splash
347
the typing effect.
import { Scene3D } from "@enable3d/phaser-extension";
import Utils from "../gameobjects/utils";
348
});
}
This is just the “logo” of the game, which is just a text.
showLogo() {
this.logo = this.add
.image(this.center_width, 170, "logo")
.setOrigin(0.5)
.setScale(0.7)
.setAlpha(0);
this.tweens.add({
targets: this.logo,
duration: 3000,
alpha: { from: 0, to: 1 },
});
}
These are the instructions for the game. We use again the Utils class to show
the text letter by letter.
showInstructions() {
let text1, text2;
text1 = this.utils.typeText(
"ARROWS + W + S\nMOUSE FOR POV\n",
"computer",
this.center_width + 190,
this.center_height
);
this.time.delayedCall(
2000,
() => {
text2 = this.utils.typeText(
" PRESS SPACE",
"computer",
this.center_width + 190,
this.center_height + 100
);
},
null,
349
this
);
this.time.delayedCall(
4000,
() => {
let text3 = this.utils.typeText(
" A GAME BY PELLO",
"computer",
this.center_width + 140,
this.center_height + 200
);
let pelloLogo = this.add
.image(this.center_width, this.center_height + 300, "pello_logo_old")
.setScale(0.2)
.setOrigin(0.5);
},
null,
this
);
}
This is the method that will start the game.
loadNext() {
if (this.utils.typeAudio) this.utils.typeAudio.stop();
this.scene.start("game");
}
}
List: splash.js
From the Splash scene, we jump directly to the game!
Game
The game! It is defined as a 3D scene, but we can also use everything we
learned about regular 2D scenes when it comes to text, sounds, and simple
images. We will need to set up the 3D at the beginning and everything
related to 3D will be preceded with the word third.
350
Then we will generate 3D elements in the scene: the neutrino star, the ship
and the incoming bullet waves. Obviously, the collision detection will also
differ from previous games.
import { Scene3D, ExtendedObject3D, THREE } from "@enable3d/phaser-extension";
import { Euler } from "three";
import BulletHell from "../gameobjects/bullet_hell";
import Lightning from "../gameobjects/lightning";
351
this.third.load
.texture("nebulaset")
.then((sky) => (this.third.scene.background = sky));
this.particles = [];
this.waves = [];
this.remaining = 20000;
this.addWaveEvent = this.time.addEvent({
delay: 3000,
callback: this.addWave,
callbackScope: this,
loop: true,
});
this.addClockEvent = this.time.addEvent({
delay: 50,
callback: this.updateClock,
callbackScope: this,
loop: true,
});
this.setCenters();
//enable physics debugging
//this.third.physics.debug.enable()
this.setLightning();
this.setNeutrinoStar();
this.loadAudios();
this.prepareShip();
this.setScores();
this.cursor = this.input.keyboard.createCursorKeys();
this.W = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.W);
this.S = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S);
this.playAudio("voice_start");
}
This will set up the lightning effect with a rectangle that will be shown when
the lightning is triggered.
setLightning() {
352
this.lightsOut = this.add
.rectangle(0, 40, this.width, this.height + 100, 0x0)
.setOrigin(0);
this.lightsOut.setAlpha(0);
this.lightningEffect = this.add
.rectangle(0, 40, this.width, this.height + 100, 0xffffff)
.setOrigin(0);
this.lightningEffect.setAlpha(0);
this.lightning = new Lightning(this);
}
This adds the light sources to the scene. This is 3D so we have infinite
possibilities to set light sources and types wherever we want. We are using
a spotlight and a directional light.
setLights() {
this.spot = this.third.lights.spotLight({
color: "blue",
angle: Math.PI / 8,
});
const d = 4;
this.directional.shadow.camera.top = d;
this.directional.shadow.camera.bottom = -d;
this.directional.shadow.camera.left = -d;
this.directional.shadow.camera.right = d;
}
The neutrino star is the main element of the game. It is a sphere that is in
the center of the scene. It has a front and a back part. The back part is the
one that is used to detect collisions. It tries to imitate a black hole.
setNeutrinoStar() {
this.torus = this.addRing(0);
this.proximity = 0;
353
this.star = this.third.add.sphere(
{ name: "neutrinoStarBack", radius: 22, x: 0, y: 14.5, z: -150 },
{ lambert: { color: 0xffffe0, transparent: true, opacity: 0.5 } }
);
this.third.physics.add.existing(this.star);
this.star.body.setCollisionFlags(2);
this.starFront = this.third.add.sphere(
{ name: "neutrinoStarBack", radius: 17, x: 0, y: 12, z: -120 },
{ lambert: { color: "black", transparent: false } }
);
this.third.physics.add.existing(this.starFront);
this.starFront.body.setCollisionFlags(2);
}
This adds the rings that are around the neutrino star. They are just cylinders
with a texture.
addRings() {
this.rings = Array(20)
.fill(0)
.map((ring, i) => {
this.addRing(i);
});
}
addRing(i) {
let torus = this.third.add.cylinder(
{
x: 0,
y: 12,
z: -120,
height: 1,
radiusSegments: 200,
radiusBottom: 75 * (i + 1),
radiusTop: 75 * (i + 1),
},
{ lambert: { color: 0xffffe0, transparent: true, opacity: 0.8 } }
);
354
torus.rotation.set(0.1, 0, 0);
this.third.physics.add.existing(torus, { shape: "hacd" });
torus.body.setCollisionFlags(6);
return torus;
}
We use this helper method to set the “center” of the screen.
setCenters() {
this.width = this.cameras.main.width;
this.height = this.cameras.main.height;
this.center_width = this.width / 2;
this.center_height = this.height / 2;
}
We use this method to update the deviation. The deviation is the number of
times that the player has been hit by a particle.
updateClock() {
if (this.remaining < 0) {
this.remaining = 20000;
this.releaseProbe();
} else {
this.nextDropText.setText("NEXT DROP: " + this.remaining);
}
this.remaining -= 50;
}
This creates a background that is a texture that is repeated. It is a simple
way to create a background.
createBottom() {
this.third.load.texture("stars").then((grass) => {
grass.wrapS = grass.wrapT = 1000; // RepeatWrapping
grass.offset.set(0, 0);
grass.repeat.set(2, 2);
355
{ phong: { map: grass, transparent: true } }
);
});
}
The probes are like the elements we use to measure the progress of the ship.
We use a registry variable to keep track of the number of probes When the
ship reaches the star, it should release the last probe and the game will end
with victory!
releaseProbe() {
this.updateProbes(-1);
this.tweens.add({
targets: this.probesText,
duration: 400,
alpha: { from: 0.5, to: 1 },
repeat: 5,
});
}
The scores are just some text that we show on the screen. We use the registry
to keep track of the deviation and the probes.
setScores() {
this.deviationText = this.add
.bitmapText(
175,
30,
"computer",
"DEVIATION: " + this.registry.get("deviation"),
30
)
.setTint(0x03a062)
.setOrigin(0.5);
this.nextDropText = this.add
.bitmapText(
this.center_width,
30,
"computer",
"NEXT DROP: " + this.remaining,
356
30
)
.setTint(0x03a062)
.setOrigin(0.5);
this.probesText = this.add
.bitmapText(
this.width - 150,
30,
"computer",
"PROBES: " + this.registry.get("probes"),
30
)
.setTint(0x03a062)
.setOrigin(0.5);
}
Before we actually add the ship object to the scene, we need to load it. We
use the GLTF loader to load the ship model.
prepareShip() {
this.third.load.gltf("./assets/objects/ship.glb").then((gltf) => {
this.object = new ExtendedObject3D();
this.object.add(gltf.scene);
const shapes = [
"box",
"compound",
"hull",
"hacd",
"convexMesh",
"concaveMesh",
];
this.object.traverse((child) => {
if (child.isMesh && child.material.isMaterial) {
357
child.material = material;
}
});
this.ship = this.createShip("convexMesh", 0, this.object);
this.setShipColliderWithParticles();
});
}
This is the function that adds the ship object to the scene.
createShip(shape, i, object3d) {
this.left = false;
const object = new ExtendedObject3D();
object.add(object3d.clone());
object.position.set(i, -2, 10);
object.rotation.set(0, Math.PI, 0);
this.third.add.existing(object);
this.third.physics.add.existing(object, options);
object.body.needUpdate = true;
object.body.setLinearFactor(0, 0, 0);
object.body.setAngularFactor(1, 1, 0);
object.body.setFriction(20, 20, 20);
object.body.setCollisionFlags(2); // Dynamic body: 0, kinematic: 2
return object;
}
This will detect the collision between the ship and the particles. If the ship
collides with a particle it will take hits.
setShipColliderWithParticles() {
this.ship.body.on.collision((otherObject, event) => {
if (/particle/.test(otherObject.name)) {
this.updateDeviation(1);
this.cameras.main.shake(500);
this.playAudio(`hit${Phaser.Math.Between(1, 4)}`);
358
this.third.destroy(this.ship);
this.ship = this.createShip("convexMesh", 0, this.object);
this.setShipColliderWithParticles();
}
});
}
Here we load the audio files used in the game. Same as usual, we will use
playAudio and playRandom to play them.
loadAudios() {
this.audios = {
thunder0: this.sound.add("thunder0"),
thunder1: this.sound.add("thunder1"),
thunder2: this.sound.add("thunder2"),
thunder3: this.sound.add("thunder3"),
passby0: this.sound.add("passby0"),
passby1: this.sound.add("passby1"),
shot: this.sound.add("shot"),
hit1: this.sound.add("hit1"),
hit2: this.sound.add("hit2"),
hit3: this.sound.add("hit3"),
hit4: this.sound.add("hit4"),
voice_start: this.sound.add("voice_start"),
voice_drop: this.sound.add("voice_drop"),
voice_hit: this.sound.add("voice_hit"),
};
}
playAudio(key) {
this.audios[key].play();
}
playRandom(key) {
this.audios[key].play({
rate: Phaser.Math.Between(1, 1.5),
detune: Phaser.Math.Between(-1000, 1000),
delay: 0,
});
359
}
This is the game loop and it is quite simple. It will move the ship in the
direction of the keys pressed. It will also move the neutrino star and the
rings, because guess what… the ship is not really moving forward: we move
the star towards the ship.
update(time, delta) {
this.currentTime = time;
360
if (this.W.isDown && this.ship.position.z > 7) {
this.ship.position.set(x, y, z - 0.1);
z = z - 0.1;
} else if (this.S.isDown && this.ship.position.z < 15) {
this.ship.position.set(x, y, z + 0.1);
z = z + 0.1;
}
this.ship.position.set(x, y, z);
this.ship.body.needUpdate = true;
this.createTrail();
}
if (this.ground) {
this.ground.setRotationFromEuler(new Euler(0, 0, 1));
}
if (this.star) {
this.star.material.opacity = 0.5 / Math.sin(time * 3);
let offset = Math.sin(time) / 10;
this.starFront.position.set(
this.starFront.position.x + offset,
this.starFront.position.y + offset,
this.starFront.position.z + offset + this.proximity
);
this.starFront.rotation.set(0, time, 0);
this.starFront.body.needUpdate = true;
this.star.position.set(
this.star.position.x + offset,
this.star.position.y + offset,
this.star.position.z + offset + this.proximity
);
this.star.body.needUpdate = true;
this.proximity += 0.000001;
361
this.torus.rotation.set(0.1, delta, 0);
this.torus.position.set(
this.torus.position.x,
this.torus.position.y,
this.star.position.z + this.proximity
);
this.torus.body.needUpdate = true;
}
}
We will create a trail as we did in other games. Generating boxes in the ship
position that will be destroyed after a while.
createTrail() {
const color = Phaser.Math.Between(-1, 1) > 0 ? 0xadd8e6 : 0xffffff;
const trail = this.third.add.box(
{
x: this.ship.position.x,
y: this.ship.position.y + 0.3,
z: this.ship.position.z + 1,
width: 0.2,
height: 0.2,
depth: 0.2,
},
{ lambert: { color, transparent: true, opacity: 0.4 } }
);
this.third.physics.add.existing(trail);
trail.body.setVelocityZ(15);
this.tweens.add({
targets: trail.scale,
duration: 600,
scale: { from: 1, to: 0 },
repeat: 1,
onComplete: () => {
this.destroyParticle(trail);
},
});
}
362
This adds some trails to the ship wins. The trails are just boxes that will be
destroyed after a while.
createWingTrails(toTheLeft = null) {
const color = Phaser.Math.Between(-1, 1) > 0 ? 0xadd8e6 : 0xffffff;
const [m1, m2] =
toTheLeft === null ? [0, 0] : toTheLeft ? [-0.3, 0.3] : [0.3, -0.3];
this.third.physics.add.existing(trail1);
this.third.physics.add.existing(trail2);
trail1.body.setVelocityZ(15);
trail2.body.setVelocityZ(15);
this.tweens.add({
targets: [trail1.scale, trail2.scale],
duration: 600,
363
scale: { from: 1, to: 0 },
repeat: 1,
onComplete: () => {
this.destroyParticle(trail1);
this.destroyParticle(trail2);
},
});
}
This is the function that creates a wave to the scene. It will create a wave of
particles that will move from the left to the right of the screen. At the end,
it will remove the wave and play a sound.
addWave(start = -25, zed = false) {
this.lightning.lightning();
const { f1, f2, c } = this.applyFunctionsInterval();
this.third.physics.add.existing(box);
364
this.particles.push(box);
box.body.setVelocityZ(15);
return box;
});
this.playAudio("shot");
this.waves.push(wave);
this.time.delayedCall(
4000,
() => this.playRandom("passby" + Phaser.Math.Between(0, 1)),
null,
this
);
this.time.delayedCall(6000, () => this.removeWave(), null, this);
}
}
Depending on the number of probes, we will apply a different function to the
wave. This is to make the game more difficult as the player progresses.
applyFunctionsInterval() {
return {
20: { f1: 0, f2: 3, c: 3 },
19: { f1: 0, f2: 4, c: 3 },
18: { f1: 0, f2: 3, c: 4 },
17: { f1: 0, f2: 3, c: 5 },
16: { f1: 0, f2: 3, c: 6 },
15: { f1: 0, f2: 3, c: 6 },
14: { f1: 0, f2: 3, c: 6 },
13: { f1: 0, f2: 3, c: 6 },
12: { f1: 0, f2: 4, c: 4 },
11: { f1: 0, f2: 4, c: 4 },
10: { f1: 0, f2: 4, c: 4 },
9: { f1: 0, f2: 4, c: 5 },
8: { f1: 0, f2: 4, c: 5 },
7: { f1: 0, f2: 5, c: 4 },
6: { f1: 0, f2: 5, c: 5 },
5: { f1: 0, f2: 5, c: 5 },
365
4: { f1: 0, f2: 5, c: 6 },
3: { f1: 0, f2: 6, c: 5 },
2: { f1: 0, f2: 6, c: 5 },
1: { f1: 0, f2: 6, c: 6 },
0: { f1: 0, f2: 6, c: 6 },
}[this.registry.get("probes")];
}
When a wave passes, we need to remove the particles from the scene and
destroy them.
removeWave() {
const wave = this.waves.shift();
wave.forEach((particle) => this.destroyParticle(particle));
}
destroyParticle(particle) {
particle.userData.dead = true;
particle.visible = false;
this.third.physics.destroy(particle);
particle = null;
}
We use this method to finish the scene and change to another one. Depending
on the result, it can be the outro or the game over.
finishScene(name = "outro") {
this.scene.start(name);
}
We use this method to update the deviation. The deviation is the number of
times that the player has been hit by a particle.
updateDeviation(points = 0) {
const deviation = +this.registry.get("deviation") + points;
this.registry.set("deviation", deviation);
this.playAudio("voice_hit");
this.deviationText.setText(
"DEVIATION: " + Number(deviation).toLocaleString()
);
if (deviation === 10) {
366
this.finishScene("game_over");
}
}
This is the same as the previous one but for the probes. It will also play a
sound of a radio transmision.
updateProbes(points = 0) {
const probes = +this.registry.get("probes") + points;
this.registry.set("probes", probes);
this.playAudio("voice_drop");
this.probesText.setText("PROBES: " + Number(probes).toLocaleString());
if (probes === 0) {
this.finishScene("outro");
}
}
}
List: game.js
Apart from the specific 3D elements, it’s just a scene and the logic of the
game loop remains the same.
GameOver
If the player fails, we’ll show this scene with some message.
export default class GameOver extends Phaser.Scene {
constructor() {
super({ key: "game_over" });
}
This creates the elements that we will show when the player loses the game.
create() {
this.cameras.main.setBackgroundColor(0x000000);
this.width = this.sys.game.config.width;
this.height = this.sys.game.config.height;
this.center_width = this.width / 2;
this.center_height = this.height / 2;
this.introLayer = this.add.layer();
367
this.splashLayer = this.add.layer();
this.text = [
"GAME OVER",
"You failed to deliver the probes",
"you survived but the mission failed!",
"Go back to the solar system,",
"The gulag of the dark side of the moon",
"awaits for reeducation...",
];
this.showHistory();
showLine(text, y) {
let line = this.introLayer.add(
this.add
.bitmapText(this.center_width, y, "computer", text, 45)
.setTint(0x06e18a)
.setOrigin(0.5)
.setAlpha(0)
);
this.tweens.add({
targets: line,
duration: 2000,
368
alpha: 1,
});
}
This is the method that will start the Splash scene.
startSplash() {
location.reload();
this.scene.start("bootstrap");
}
}
List: game_over.js
The text is shown line by line in this case.
Outro
If our dear Gagarin completes the mission we show another message.
export default class Outro extends Phaser.Scene {
constructor() {
super({ key: "outro" });
}
This outro is shown when the player wins the game. It just shows a few lines
of text and then it starts the game again.
create() {
this.cameras.main.setBackgroundColor(0x000000);
this.width = this.sys.game.config.width;
this.height = this.sys.game.config.height;
this.center_width = this.width / 2;
this.center_height = this.height / 2;
this.introLayer = this.add.layer();
this.splashLayer = this.add.layer();
this.text = [
"This feels like falling",
"and collapsing at the same time...",
"I'm glad that I succeded",
"By the way...",
369
"I see no god inside here.",
];
this.showHistory();
this.input.keyboard.on("keydown-SPACE", this.startAgain, this);
this.input.keyboard.on("keydown-ENTER", this.startAgain, this);
}
startAgain() {
this.scene.start("bootstrap");
}
We use again this function to show the text line by line.
showHistory() {
this.text.forEach((line, i) => {
this.time.delayedCall(
(i + 1) * 2000,
() => this.showLine(line, (i + 1) * 60),
null,
this
);
});
}
showLine(text, y) {
let line = this.introLayer.add(
this.add
.bitmapText(this.center_width, y, "computer", text, 35)
.setOrigin(0.5)
.setAlpha(0)
);
this.tweens.add({
targets: line,
duration: 2000,
alpha: 1,
});
}
}
370
List: outro.js
Reference
https://catlikecoding.com/unity/tutorials/basics/mathematical-surfaces/
https://github.com/enable3d/enable3d-website/blob/master/src/examples/first-
phaser-game-3d-version.html
371
9. Deep Dive Into Phaser
Let’s get into more deeper of the Phaser ocean. If you’re curious about how
Phaser working behind the scenes, tune in!
What is Phaser?
Phaser is an HTML5 game framework designed specifically for web browsers.
It is built using, and relying on, web technologies. And the games it creates
are meant to be played in desktop or mobile browsers, or apps capable of
running web games, such as Discord, SnapChat, Facebook and more. There
are ways to convert browser games to native mobile or desktop apps using
3rd party tools, and many Phaser developers have done this successfully.
However, Phasers primary focus is, and always will be, the web.
Phaser is a 2D game framework. This means that both its features and
internal design are based entirely around creating lightning fast 2D games.
It does not include 3D rendering or 3D physics as built-in features. Again,
there are ways to integreate 3rd party libraries to provide this, but Phaser
itself is 2D and our documentation and examples reflect this.
Phaser was developed in JavaScript, because this is the language of the web
browser. As such, you will need to code your game using either JavaScript
or TypeScript. All of our examples and documentation are provided in
JavaScript, but we also provide TypeScript definitions.
Phaser is made available as a JavaScript library. This can be downloaded,
linked from a Content Delivery Network (CDN), or installed via any of the
standard JavaScript package managers, such as npm. Phaser is not a desktop
application. You do not ‘install’ it and there is no ‘Phaser IDE’. It is a
JavaScript library that you include in your own web pages, or bundle. You
then write your game code in JavaScript and run them together in a web
browser.
Phaser has been in active development for over 10 years. There is a small
but dedicated full-time team behind it, who are constantly striving to make
it the best it can be, while keeping it easy to learn. It is used by developers
around the world and has been used to create many thousands of games,
from small prototypes to full-scale commercial titles with millions of players.
372
Because of its maturity, Phaser is a stable and reliable framework. It is not
a ‘fad’. When changes are made, they’re for the benefit of the framework as
whole, not just to chase a ‘trendy’ new technology.
To this end it’s important to understand when Phaser is not a suitable choice:
• You want to make your game fully in 3D.
• You want to publish your game on a modern console, such as PS5,
XBox or Nintendo Switch.
• You don’t want to learn JavaScript or deal with JavaScript libraries
and need a visual / no-code based editor.
• You want to use cutting-edge browser features that aren’t yet widely
supported.
If any of the above apply to you, then Phaser isn’t the right choice for your
game. There are plenty of other frameworks and tools that will be a better
fit. However, if you’re looking to make a 2D game for the web, then we firmly
believe that Phaser is a great choice.
Events
Events are a way for one system to send a signal, that other systems may
listen for and then act upon. For example, if the player clicks their mouse
on your game, that will internally emit a sequence of events within Phaser.
Or if the Loader finishes downloading a file, that will emit a related event.
Events are a core part of Phaser and you’ll find them used throughout the
framework. They are used both internally, for one system to talk to another,
and externally, for your game code to listen for and respond to. There are
hundreds of such events that Phaser will emit during the course of a game.
Events are always emitted by what is known as an Event Emitter. Most
373
systems and Game Objects within Phaser are Event Emitters, meaning they
can emit events directly and you can hook event handlers to them.
We adopted this practise because events are extremely common in the web
browser. Most browser APIs are event-driven, so it made sense to follow this
pattern. It also means that you can easily extend Phaser by adding your
own events, or listening for existing ones and responding to them.
Game
If you look at any Phaser example you’ll see they all create an instance of the
Phaser.Game class. Indeed, without it, nothing will actually happen. This
one class can be considered as the heart of your game, for without it, nothing
will run.
Typically, you only ever have one instance of a Phaser game at any given
time. The Game class itself doesn’t do a great deal, and beyond creating it,
you rarely ever interact with it. Yet it’s responsible for creating and updating
all of the internal systems that your game needs while it is executing.
Even if you’re creating the type of game that consists of lots of smaller games
(think Mario Party, or Wario Ware), you’ll still only ever have one instance
of the Phaser Game class itself.
Renderer
The Renderer is the part of Phaser that is responsible for drawing everything
you see on the screen. Phaser ships with two different renderers: Canvas
and WebGL. By default, Phaser will query the browser to see if it supports
running WebGL. If it does, then it will create a WebGL renderer, otherwise it
will fall back to Canvas. You can also force Phaser to use a specific renderer,
if you wish, via the game configuration. For maximum compatibility Phaser
uses a WebGL1 based renderer. A move to a WebGPU is planned for Phaser
4.
Some features are only available in WebGL. For example, the ability to use
Shaders or special effects on Game Objects. WebGL is also optimized for
rendering extremely large amounts of Game Objects to the screen via the
GPU, something canvas struggles with.
374
However, Canvas is still a very capable renderer and can sometimes be a
better choice for games that don’t require the advanced features of WebGL,
or in hardware constrained environments.
Scenes
Phaser uses the concept of Scenes to allow you to divide your game up into
logical sections. A Scene can be as large, or as small, as you like. Typical
uses for a Scene would be a Loading Screen, a Main Menu, a Game Level, a
Boss Fight, an in-game Item Shop, a High Score Table, etc. You can have
as many Scenes in your game as you like. When you are starting out, you’ll
probably only have one or two, but as your game grows in complexity, you’ll
find yourself adding more.
It’s important to understand that you do not have to have one Scene per file.
You can, if you like, but it’s not a requirement. You can have all of your
Scenes in a single file, or you can have one file per Scene. It’s entirely up to
you.
Internally, there is a Scene Manager. This Manager is what you interact
with when you add, remove or switch between Scenes. It’s also responsible
for updating and rendering the Scenes. You have full control over the order
in which the Scenes are rendered in your game. For example, it’s a common
practise to have a Scene dedicated entirely to handling the UI for your game,
that is rendered above all other Scenes.
We will look at the life-cycle of a Scene in more detail in a later chapter, but
for now it’s worth understanding that you can pause, resume and sleep a
Scene at will, and that you can easily run multiple Scenes at the same time,
if you wish.
In versions of Phaser prior to v3, Scenes were called States. The two terms
are conceptually interchangeable, but Scene is the correct term to use and
expect to see in Phaser 3. If you find any reference to States in code or a
tutorial you’ve found online, it’s for Phaser 2 and likely won’t work in Phaser
3.
375
Game Objects
The main purpose of a Scene is to contain Game Objects. These are the
building blocks of your game and Phaser has a lot of Game Objects available
for you to use. Some of the more common ones include: Sprites, Text, Images,
Particle Emitters, Containers, Graphics, Tilemaps, and more. You can create
as many Game Objects as you like. A Game Object always belongs to the
Scene in which it was created. It is born and it dies there and cannot be
moved to another Scene.
Games Objects are typically created via the Game Object Factory. This
factory is available for direct use from within a Scene. For example, to
create a Sprite you would call this.add.sprite(). add is an alias to the
factory and sprite is a function within it. The factory is responsible for
creating the Game Object, adding it to the Scene and returning it to you.
Internally, Phaser has a base Game Object class from which all other Game
Objects inherit. This base Game Object does not display anything, or even
have a position within the game world. It simply provides the core func-
tionality that all Game Objects need. Each induvidual type of Game Object
then adds its own features. For example, the Sprite Game Object will add
components for position, scale, rotation, textures and animation.
Because Game Objects are made from a set of components, it’s possible to
extend them and create your own custom Game Objects. This is a very
powerful feature of Phaser and one that we’ll explore in detail later in this
guide.
Chainable Methods
Lots of methods in Phaser are chainable. This means that you can call
one method after another, without having to store the Game Object in a
temporary variable. For example, if you have a Sprite and want to set its
position, scale and rotation, you can chain the calls together like this:
sprite.setPosition(x, y).setScale(2).setRotation(0.5);
This works because each of the methods returns a reference to the Game
Object itself. So, when you call setPosition it returns the Sprite, which
you can then call setScale on, and so on.
376
When you see a method in the Phaser API that begins with set in the
majority of cases it means it’s chainable. This is a very common JavaScript
convention and one that we use heavily in Phaser.
Game World
The Game World is the space in which all of your Game Objects live. It’s
a virtual space that has no fixed size. You can think of it as a giant canvas
that you can draw on. Game Objects that have the Transform Component
(which is most of them) have the ability to set their x and y coordinates.
These values control their placement within the Game World.
Although the Game World is technically infinite, that doesn’t mean your
game has to be. You can still easily create ‘single screen’ games in Phaser. Or
you can create games that extend only in one direction, such as a horizontal
endless-runner. Or you can create games that scroll in all directions, such as
a top-down RPG. It’s entirely up to you.
The Game World is not an object or class within Phaser. It’s simply a concept
that you can use to help visualize where your Game Objects are in relation
to each other.
Cameras
Phaser has a built-in Camera system. A Camera is a way to control which
part of your Game World you are currently looking at. You can move the
camera around the world, and that in turn will influence which Game Objects
are displayed, based on their world position. By default, a Scene creates a
single Camera ready for you to use. It must always have at least one Camera,
otherwise nothing will render.
You can add as many extra Cameras as you like. You can also control the
position, size, rotation, scaling and viewport of each Camera. Common uses
of being able to have multiple cameras are for creating split-screen games, or
games with picture-in-picture effects.
Cameras can also be given a bounds. This is a rectangular area that the
Camera cannot scroll outside of. By default, a Camera has no bounds, so
it can freely scroll anywhere. However in practise you will likely need to
377
Figure 43: Camera effects
378
constrain the Camera to a fixed area of your Game World, and the Camera
Bounds are how you do this.
Input
Phaser maintains a unified input system that works across all browsers and
devices. By unified we mean that you don’t have to worry about whether the
user is on a desktop with a mouse, or a mobile device with touch input, or
even a touch capable desktop. All you need to do is listen for, and respond
to, the input events that Phaser provides. You can also respond to input
events from both keyboards and gamepads.
Internally there is a global Input Manager and every Scene has an instance of
the Input Plugin. The Input Manager is responsible for listening for native
DOM events, such as mouse movement, touch gestures and keyboard presses.
It then passes these events on to the Input Plugins, which in turn processes
them.
By default, Game Objects in Phaser do not process input. This is because
not all Game Objects need to respond to input. For example, a background
image or game logo likely doesn’t need to respond to input, but a button
does. Therefore, you must enable input processing on the Game Objects
that you specifically want to respond to input.
Once enabled for input, a Game Object will then listen for input events from
the Input Plugin and check to see if it has been ‘clicked’, or not. There are
lots of events that can be emitted, such as pointer up and down events, drag
events, scroll wheel events, etc. We’ll explore these in more detail later in
this guide, along with how the input system works internally. For now, it’s
enough to know that you can enable input on almost any Game Object and
then respond to the events it emits as your game requires.
Loader
The Loader, as the name implies, is responsible for loading any external assets
that your game may require. Common asset types include images, texture
atlases, sprite sheets, fonts, audio files and JSON data, but there are many
more that Phaser can handle.
By default, every Scene has access to its own Loader instance. The Loader
379
works on a queue-basis, which means you can add as many ‘load requests’
to it as you like and they all get added to an internal queue. You then tell
the Loader to start and it will work through the queue, loading each asset in
turn.
Scenes have a special method available to you called ‘preload’. This method
is called automatically by Phaser when the Scene starts. It’s a good place to
add all of your game assets to the Loader and you’ll see this convention used
heavily in our examples and third-party tutorials. However, you can also
add assets to the Loader at any point in your game, not just from within the
preload method.
When you add a file to the loader, you have to give it a string-based key.
This is a unique identifier for that file and its related resource. For example,
if you load an image and give it the key ‘player’, then you identify that image
by the key ‘player’ from that point on. The keys are case-sensitive and their
uniqueness is applied per file type. I.e. a sound file could have the key ‘player’
as well as an image file. String-based keys is a very important concept in
Phaser and you’ll see it used throughout the framework.
The files are loaded via built-in browser APIs, which often allows for many
files to be downloaded in parallel, depending on the browser and server set-
tings. The Loader is specialised in loading files based on network requests
and across a network. It is not for loading files from the local file system,
something that all modern web browsers prohibit for security reasons.
As with most systems in Phaser, there are lots of events you can listen for
that come from the Loader. These events are naturally centered around the
loading progress: such as which files have completed, or maybe failed, and
how far along the process is. You can use these events to create loading bars
and progress displays for your game.
Cache
When a file is downloaded by the Loader it nearly always ends up stored in
an internal Phaser Cache. There are different caches for different file types.
For example, JSON files are stored in the JSON cache, Binary files in the
Binary Cache, and so on. These caches are created automatically when the
Phaser Game instance first starts up. Files are stored in them using unique
string-based keys. If the file has come from the Loader, it will use the same
380
key you used there, to store it in the Cache with.
The Phaser Cache is different to any cache that the browser itself may main-
tain, in that it only persists for the duration of your game. Once your game
has been destroyed, either directly by you, or via a page navigation, the
Phaser Cache is cleared. Unlike the browser cache, you are free to add to,
and remove items from, any of the Phaser caches at will.
Items stored in a Phaser Cache are global, which means they can be accessed
from any Scene in your game. Scene’s do not maintain their own set of caches.
Instead, they all share the same global set. This is important to understand,
because it means that if you load a file in one Scene, it will be available in
all other Scenes too.
Mostly, you don’t need to worry about interacting with the Phaser Cache.
It’s primarily an internal system that is used by other systems, such as the
Loader, to store and retrieve data. However, there are times when you may
want to interact with it directly, and it has a public API for doing exactly
this.
Texture Manager
Phaser is a Texture based game framework. This means that it uses textures
as the basis for rendering almost everything to the screen. From Sprites
to Text, from Tilemaps to Particle Emitters, they all use textures. And
the Texture Manager is where they all reside. As with Caches, the Texture
Manager is global. The Phaser Game maintains a single instance of it that
is shared across all Scenes in your game. Therefore, adding a Texture from
one Scene makes it automatically and instantly available in all other Scenes.
The Texture Manager works in tandem with the Loader. When you load an
image file via the Loader, it is automatically passed to the Texture Manager.
Phaser uses string-based keys for all of its textures. For example, when
loading an image if you give it the key ‘player’ then it will be stored in the
Texture Manager under the same key. When you then need to use this, say
for a Sprite, you tell the Sprite to use the texture key ‘player’. This string
based approach is used through-out Phaser.
Although using the Loader is the most common way to populate the Texture
Manager, it has lots of available methods to allow you to create and add
381
textures directly to it. For example, if they need to come from a different
source, perhaps as the result of an API call, or from a Canvas element.
The Texture Manager includes what are known as Parsers. A Parser is a
function that converts external data in to a format that Phaser can use. The
end goal of these functions are to take images and related data and create
Texture and Frame instances from them. For example, there are Parsers for
loading Texture Atlases, Unity Atlases, Sprite Sheets and more. Although
commonly used as part of the loading progress, you can also call any of the
Parsers directly, as needed.
382
themselves to the screen.
Physics
Phaser has two physics systems built in. The first is called Arcade Physics
and the second is Matter JS.
Arcade Physics is, as its name implies, meant for more ‘arcade’ or ‘retro’
style games, although is not limited just to those. It’s a lightweight physics
system that can only handle two different types of physics shapes: rectangles
and circles. It’s not meant for complex physics simulations, but rather for
simple things like platformers, top-down games, or puzzle games. It’s very
fast and easy to use, with lots of helper functions, but due to its nature it
does have its limitations.
Matter JS is an open-source third party physics library and Phaser has its
own custom version of it bundled. The reason for including Matter is that it
provides a more advanced ‘full body’ physics system. If you need to move be-
yond rectangles and circles, with more complex physics shapes, and features
such as constraints, joints and behaviours, then Matter is the system to use.
Both physics systems need to be enabled before they can be used. This can
be done via the Game Configuration or on a per-Scene basis. Once enabled,
you can then add physics-enabled Game Objects to your game. For example,
if you enable Arcade Physics, you can then add a Sprite and enable physics
on it. This will allow you to control the Sprite using the built-in physics
functions, such as velocity, acceleration, gravity, etc.
By default a Game Object is not enabled for physics. This is because not
all Game Objects need to be. For example, a background image or game
logo likely doesn’t need to be affected by physics, but a player character
does. Therefore, you must enable physics on the Game Objects that you
specifically want to be affected by it. We will cover this in detail in later
chapters.
The two systems are entirely separate. An Arcade Physics sprite, for example,
cannot collide with a Matter Physics sprite. You cannot add the same Sprite
to both systems, you need to pick one or the other. However, although it’s
unusual to do so, both systems can actually run in parallel in the same Scene.
This means that you can have a Sprite that uses Arcade Physics and another
383
that uses Matter Physics, and they will both work at the same time, although
they will not interact together.
384
Game Objects also have a scale property. This allows you to scale the Game
Object horizontally and vertically. By default, a Game Object has a scale
of 1, which is 100% of its original size. A value of 2 would be 200% of its
original size, and 0.5 would be 50% of its original size.
You can set the scale to any value you like, including negative values. Neg-
ative scaling will cause the Game Object to flip on its axis. For example, if
you set the scale to -1 on the x-axis, the Game Object will flip horizontally.
If you set it to -1 on the y-axis, it will flip vertically. If you set it to -1 on
both axis, it will flip both horizontally and vertically.
Scaling in Phaser always takes place from the center of the Game Object and
this scale point cannot be changed.
Display List
Phaser uses a Display List to manage the order of Game Objects within the
World. Every Scene has its own Display List. When you add a Game Object
to a Scene, it is automatically added to the Display List. The Display List is
a special type of container that allows you to control the order in which the
Game Objects are rendered. By default, Game Objects are rendered in the
order in which they were added to the Display List.
The Display List is also responsible for managing the position of Game Ob-
jects within the World. When you add a Game Object to the Display List,
it is automatically positioned at the bottom of the list. This means that it
will be rendered first, and therefore appear behind all other Game Objects.
You can change the position of a Game Object within the Display List by
using its helper functions such as sendToBack, sendBackwards, bringToTop
and bringForward. These allow you to move a Game Object around the
Display List, thus influencing the order which things are rendered or checked
for input events, as objects “on the top” get input priority.
In Phaser 2 it was possible to add a Game Object as a child of another Game
Object. They were, in effect, self-contained Display Lists. This is no longer
the case in Phaser 3. The only Game Object in Phaser 3 that can have
children is the Container Game Object.
385
Depth and Visibility
Game Objects have two other properties that impact how and if they are
rendered. The first of these is called visible. By default, all Game Objects
are visible. This means that they will be rendered. However, if you set the
visible property to false, then the Game Object will be skipped by the
renderer. It will still exist in the World, and still be updated, but it will not
be rendered. This is useful for Game Objects that you may want to hide at
certain points in your game, then bring back later.
The second property is known as the Game Objects ‘depth’. This is a value
that controls the order in which the Game Objects are rendered. By default,
Game Objects have a depth of zero. This means that they are rendered in
the order in which they were added to the Display List. However, you can
change the depth of a Game Object via its setDepth method. This allows
you to explicitly set the order in which Game Objects are rendered, without
having to move them around the Display List. Typically, you would use
this if you want to programmatically control the rendering order of Game
Objects, such as a sprite moving infront or behind other objects in a busy
Scene, where moving them all around the Display List would be impractical.
386
it is rendered. You can use this property to create effects such as a Game
Object ‘flashing’ a color, or to make a Game Object appear to be tinted a
certain color.
You can optionally set a different tint color per corner. This is known as
a vertex tint and allows you to create color blended effects across a Game
Object. Tinting is an “all or nothing” effect, where the entire Game Object is
tinted the same blend of colors. It’s not possible to tint just part of a Game
Object, or only impact a specific color used by the Game Object texture.
Both vertex alpha and tinting are only available when using the WebGL
renderer, they do not have a Canvas equivalent.
Update List
The Update List is an internal system that every Scene has. It works in a
similar way to the Display List, except instead of controlling the order of
rendering, it controls which Game Objects have their ‘preUpdate’ methods
called. An “update” happens in a Phaser Game every time the browser
updates the screen and uses browser based APIs to control it. This is typically
60 times per second, but can vary depending on the device and browser
settings.
When an update happens, the Update List is iterated through and each Game
Object in it has its preUpdate method called. This is where the Game Object
can perform any internal logic it needs to, such as updating its animation
system. You can also override this method yourself, adding your own logic
to it as needed.
Once all Game Objects in the Update List have been processed, the Scene
then has its own update method called. This is where you can perform any
Scene-level logic, such as checking for collisions, keyboard input, or updating
the position of a Camera.
Not all Game Objects are added to the Update List. Those that require it,
are added automatically when created via the Game Object Factory. For
example, a Sprite is added to the Update List, where-as a Graphics Game
Object is not. But as with most systems in Phaser, you can manually add
and remove entries from the Update List, as needed.
387
Geometry
Phaser has an extensive set of Geometry classes. These are used internally
by the physics and input systems, but are also available for you to use in
your own games. The geometry classes on offer include: Circle, Ellipse, Line,
Point, Polygon, Rectangle, Triangle and the Mesh class.
Each of these classes has a set of methods and support functions that allow
you to perform geometric operations on them. For example, you can check
if a point is contained within a circle, get the bounds of an ellipse, or the
nearest point from a line, as well as many other features.
There are also a wide range of intersection functions. You can test for condi-
tions such as a Circle intersecting with a Rectangle, or getting the rays from
a point to a polygon.
The Geometry classes are not Game Objects. You cannot add them on to
the Display List. Instead, think of them as data structures that you can use
to perform geometric operations on, of which most games tend to have quite
a few.
Tweens
Tweens are an important part of most games, although it’s entirely possible
you have never come across the term before. Phaser has a built-in Tween
Manager that allows you to create smooth, time-based changes to object
properties. For example, you can tween the position of a Sprite from one
coordinate to another, over a given duration of time. Or you can tween the
alpha value of a Game Object from 1 to 0, making it appear to fade out.
The Tween Manager is a Scene-based system, and each Scene has its own
instance of it.
Although most often used on Game Objects, tweens can actually adjust any
object at all. For example, you can tween the volume of a sound, or the
position of a Camera. You can even tween the properties of a JavaScript
object, such as an object containing a players score, or health points.
Tweens have a whole raft of features built into them. They can be set to
repeat, yoyo, tween multiple objects at once, set multiple properties at once,
each with their own custom values, have delays, interpolation, a variety of
different smoothing effects and much, much more. They can even be chained
388
together, so that one starts as soon as another finishes. As a result, they are
a very powerful system and one that you’ll find yourself using a lot in your
games.
We will cover tweens in much more depth in a later chapter, but the take-
away here is that a ‘tween’ is when the value of an object is changed over a
period of time.
Math
JavaScript itself has a pretty comprehensive Math API, which is of course
optimized to run quickly in browser. Phaser extends this with its own set of
Math functions, that are primarily geared around common use-cases in games.
For example, there are Math functions for working with angles, distances,
random numbers, interpolation, and more. Lots of these exist because they
are required internally, so we expose them for you to use too. The rest are
just functions we’ve found that we have come to require over the years.
All Phaser Math functions are contained in their own namespace. We do
not, and never will, modify or pollute the native JavaScript Math namespace.
This means that you can use both Phaser Math functions and native Math
functions in your game, without any conflicts.
Animations
The primary means of animation in Phaser is by using ‘frame’ based ani-
mations. As mentioned previously, Phaser maintains a Texture class, which
contains as many Frames as may exist on that Texture. The Animation sys-
ten allows you to play a sequence of these Frames, one after the other, at a
given frame rate. This is how you create the illusion of animation on Sprites
in your game. To achieve this, you often see texture image files divided into
a ‘grid’ of frames, where each frame is a different animation frame. This is
known as a Sprite Sheet or Texture Atlas.
Animations are created via the Animation Manager. Each Scene has its own
instance of the Animation Manager. You can create as many animations as
you like, and each animation can have as many frames as you like. You can
also create multiple animations that all use the same frames, if you wish.
For example, you could have a ‘walk’ animation that uses frames 1 to 4,
389
and a ‘run’ animation that uses frames 1 to 8. Both animations would use
overlapping frames, but play them at different speeds.
Not all Game Objects can be animated. The main one you’ll use is the
Sprite Game Object. This carries its own Animation State component with
it, allowing you to create and play animations directly on a Sprite instance.
Animations can be either global or local. A global animation is one that
is created via the Animation Manager and is available to all Game Objects
in your game. A local animation is one that is created directly on a Game
Object, such as a Sprite. Local animations are only available to that Game
Object and cannot be used by any other Game Object.
It’s worth mentioning that animation can also be achieved by tweening ob-
jects, if you just need a blend of motion + subtle changes (like scale or alpha),
and that Phaser 3 also has a plugin available for Spine animations, which is
a bone-based animation software package published by Esoteric Software.
Renderer
When a Phaser Game first boots, it will create a renderer. Based on the
browser and your game configuration, this will be an instance of either the
Canvas Renderer or the WebGL Renderer. Canvas is an API that the web
browser makes available. In essence, it’s a rectangular element that we can
draw to. It allows Phaser to create a ‘context’ upon it and this context is
what is used to draw things to the canvas.
WebGL is an API based on top of OpenGL ES that allows us to draw things
to the screen via the GPU. Objects being drawn can be batched together,
which means that the GPU can often process them extremely quickly. It’s
a much more powerful system than Canvas. However, it’s not supported by
all browsers, or all devices. This is why Phaser maintains two renderers.
Phaser will always try to use WebGL if it can, but will fall back to Canvas if
it can’t. Because Canvas is a much simpler renderer, you don’t have access to
certain features that Phaser can provide, such as Shaders, special effects, or
the ability to use color tints. However, it’s still a very capable renderer and
can sometimes be a better choice for games that don’t require the advanced
features of WebGL, or need to operate in hardware constrained environments.
390
Sound
Web Browsers offer the ability to play audio in two different ways. The first
is known as the Audio Tag. This is an HTML tag you can put on a web page
that offers UI controls to play and pause/resume audio files. The second
is known as the Web Audio API. This is a JavaScript API that allows you
to create and control audio files directly from your code. It’s a much more
powerful system than the Audio Tag, but is not supported by all browsers.
Phaser has a built-in Sound Manager that allows you to play audio files in
your game. It will automatically detect if the browser supports the Web
Audio API and if it does, it will use that. If not, it will fall back to the Audio
Tag. This means that you can use the same API to play audio files in all
browsers, regardless of their support for the Web Audio API.
As with WebGL vs. Canvas, there are things that only Web Audio can do.
Such as positional audio, i.e. having sounds ‘follow’ a player across the game
world. It’s also much better suited to playing lots of short duration sound
effects in quick succession, i.e. “gunshots” or “explosions”.
You can pick which audio system you’d like to use via the Game Configura-
tion, or even disable audio entirely. Not all games need audio, after all.
The Phaser Sound Manager is a global system. This means that it belongs
to the Game instance, and if you start to play a sound in one Scene, it won’t
automatically stop just because you change to another Scene. This gives you
a lot of control, but also means you need to be careful to stop looping sounds
when you’re done with them, or they’ll keep playing.
Tilemaps
A tilemap is a way of storing a level or game world. It’s a data structure
that contains information about the tiles that make up the level, such as
their position, size and type. It’s a very common way of creating levels in
2D games, as it allows you to visually design the level in a tile-based editor
such as Tiled, then export it as a data file.
Tilemaps are typically orthogonal, which means that the tiles are laid out
in a grid, with each tile being the same size. However, Phaser also sup-
ports isometric tilemaps, where the tiles are laid out in a staggered grid, and
hexagonal tilemaps, where the tiles are laid out in a hexagonal grid.
391
Phaser has a built-in Tilemap class that allows you to load and render
tilemaps in your game. It supports a wide range of tilemap formats, including
CSV, Tiled JSON, Tiled XML, and the ability to generate data dynamically
at runtime. Because Tiled works on a ‘layer’ basis, Phaser uses the same
principals. You can create a ‘TilemapLayer’ instance and this will render as
a ‘layer’ in your game. You can have multiple tilemap layers and mix and
match regular Game Objects inbetween those.
The Arcade Physics system that is built into Phaser has support for tilemap
collision as part of it. You can also convert a tilemap for use in Matter
Physics, which gives you more control over things such as sloped tiles.
Plugins
Phaser has a built-in Plugin System that allows you to extend it in an in-
finite number of ways. Indeed, the majority of the internal systems are
implemented as plugins. A plugin can either be global, or Scene-based. A
Global plugin, as the name implies, is available across the whole of Phaser
and when you access a global plugin from a Scene, you’re accessing one single
instance of it.
A Scene-based plugin is created by, and belongs to, the Scene in which it was
added. The difference is that you can have several unique instances of Scene
Plugins running in multiple Scenes in parallel.
Scenes can be added to your game via the Loader, or you can import them as
JavaScript modules. There are a large number of 3rd party Phaser plugins in
existence, which add lots of extra functionality that we just cannot include
in the core framework.
Curves
A Curve is a mathematical function that allows you to plot a series of points
in such a way that they form a line or a curve. Phaser includes support for
Cubic Bezier curves, Ellipse Curves, Line Curves, Quadratic Bezier curves
and Spline curves.
Phaser also has support for Paths. A Path is a collection of multiple curves,
joined into one continuous compound curve.
392
You can use the curves and paths in a variety of ways. The most common
would be to have a Game Object follow the path. For example, a ‘space
invader’ style sprite could follow a path that made it move left and right
across the screen, then drop down a bit, then move left and right again, and
so on.
Curves are not Game Objects. You do not add them to the display list and
they do not render. They’re purely a mathematical function with lots of
helper utilities available, such as the ability to get the bounds, or tangent,
or a specific point from the curve. Phaser does provide the ability to draw a
curve to a Graphics Game Object, but this is mostly for debugging purposes.
Scale Manager
The Scale Manager is a global system that handles the scaling, resizing and
alignment of the canvas element into which Phaser is rendering your game.
When a Phaser game boots it will create a canvas element by default, of the
given dimensions set in the game configuration. It’s the Scale Managers job,
if instructed to do so, to then scale this canvas via CSS to make it fill the
available space on the web page.
There are various scaling modes available. The most common is known as
‘Scale To Fit’. This will scale the canvas to fill the available space, whilst
maintaining the aspect ratio of the game. However, there are other scale
modes, including ‘resize’ which will resize the underlying canvas to fill the
available space. There are also modes for ‘zooming’ a canvas, centering it, or
allowing it to enter into fullscreen mode.
Scaling a game is a complex topic. There are lots of different devices, with
different screen sizes, pixel densities and aspect ratios. The Scale Manager
is designed to help you handle all of this, but it’s not a magic bullet. You
still need to understand the basics of how it works, and how to configure it
to suit your game.
More importantly, the underlying size of the canvas element directly impacts
the performance of your game. The larger the canvas, the more pixels it has
to render to. This means that a game running at 800x600 will render much
faster than one running at 1920x1080. You need to find a balance between
the size of the canvas and the performance of your game across the devices
that matter most to you.
393
Game Object Masks
Phaser has the ability to ‘mask’ Game Objects as they are rendered. A mask
allows you to ‘hide’ areas of the Game Object from rendering. There are two
types of mask available: Geometry Masks and Bitmap Masks. The Geometry
Mask works by using geometry data in order to create the mask. For example
rectangles, circles, ellipses, polygons and more. This data is used to create
a path that forms the mask. Internally, it uses what is known as the stencil
buffer in WebGL and the clip path in Canvas.
The Bitmap Mask works by using a texture as the mask. This texture can
be any size and shape you like, and can be animated, or even a video. The
alpha values of the pixels in the texture control what the mask looks like
on-screen. For example, a pixel with an alpha value of 0 will hide the Game
Object, where-as a pixel with an alpha value of 1 will show it. This allows you
to create detailed effects, such as feathering, not possible with a Geometry
Mask. Bitmap Masks are a WebGL only feature.
Masks in Phaser are slightly unique in that they are drawn and positioned
in world space. A Game Object can only have one mask applied to it at any
one time. However, you can apply the same mask to multiple Game Objects,
if you wish. They are not Game Object specific and if you then move the
Game Object, the mask will not ‘follow’ it. This means they require some
careful planning to use effectively.
FX
As of Phaser v3.60, the framework includes a new FX Pipeline system with
lots of built-in effects. This is a powerful and flexible way to apply both pre
and post-processing effects to your game. It’s a WebGL only feature and is
not available in Canvas mode as it relies on shaders.
The built-in FX include: Barrel, Bloom, Blur, Bokeh, Circle, ColorMatrix,
Displacement, Glow, Gradient, Pixelate, Shadow, Shine, Vignette and Wipe.
The FX can be enabled on all of the common types of Game Objects and
you can stack effects and control the stacking order. For example, you can
apply both a glow and vignette effect to a Sprite. Cameras can also have FX
applied to them, which impacts everything they render and lets you create
effects such as a ‘zoom blur’ or pixelate.
394
Figure 44: fx
395
Time
Every Scene has an instance of the Clock class. It’s responsible for keeping
track of the elapsed time, delta time, and other time related values. It also
allows you to create Timer Events, which are events that fire after a given
amount of time has passed. For example, you can create a Timer Event that
fires after 5 seconds, or 10 seconds, or 1 minute. You can also create Timer
Events that repeat, such as every 5 seconds, or every 10 seconds.
The Clock that belongs to a Scene is used by all Scene systems, such as tweens
and sound. It’s also used by Game Objects, such as the Sprite animation
system. This means that all of these systems are synchronized to the same
clock. You have the ability to ‘scale’ the time of an individual clock, thus
slowing down, or speeding-up the systems running within a single Scene.
396
10. Detailed Look Into Game Objects
In this chapter, we delve deep into the core of Phaser game development: the
game objects. Game objects are the building blocks of any Phaser game, from
simple sprites to complex groups that manage multiple sub-objects. Under-
standing how to effectively create, manipulate, and control these elements
is fundamental for any developer looking to create engaging and dynamic
games. Let’s dive in!
397
Game Objects
Alpha Component
398
Or, you can set the alpha property directly:
player.alpha = alpha;
By default, Game Objects will have an alpha value of 1. This means they will
be fully visible. You can reset the alpha of a Game Object either by setting
its alpha property to 1, or by calling the chainable clearAlpha method:
player.clearAlpha();
You can use this property to create effects such as a Game Object ‘fading
out’ over time, or to make a Game Object appear to be semi-transparent.
As an internal optimization, Game Objects with an alpha value of 0 will be
skipped by the renderer.
399
an alpha value of 0.5, then the child will be rendered at 0.25 alpha as it’s
multiplied with the parent’s alpha:
container.setAlpha(0.5);
child.setAlpha(0.5);
400
Game Objects
Blend Mode Component
401
Or, you can set the blendMode property directly:
sprite.blendMode = mode;
The mode value can be one of the BlendModes constants, such as
Phaser.BlendModes.SCREEN. It can also be a string, such as SCREEN, or an
integer, such as 3. If you give a string, it must be all upper-case and match
exactly those available in the BlendModes constants list. If you give an
integer, it must be a valid Blend Mode constant ID from the list below.
The default value is zero, which is the NORMAL blend mode.
402
Game Objects
Bounds Component
The Bounds Component is responsible for providing methods you can call
that will return various bounds related values from a Game Object.
The ‘bounds’ of a Game Object can be summed-up as a rectangle that fully
encapsulates the visual bounds of the Game Object, taking into account its
scale and rotation.
Not all Game Objects have a bounds. For example, the Graphics Game
Object does not have an instrinsic bounds because of the way in which it
works. However, most texture-based Game Objects, such as Sprites, Text
and TileSprites can return their bounds.
If the Game Object has a parent container, then its bounds will be factored
based on its influence from the Container.
The bounds of a Game Object can be obtained by calling its getBounds
method:
const bounds = sprite.getBounds();
This will return a Rectangle Shape object, where the x and y values are the
top-left of the bounds, and the width and height values are the width and
height of the bounds.
You can also pass in a Rectangle object to the getBounds method, and it
will set the values based on the bounds of the Game Object:
const rect = new Phaser.Geom.Rectangle();
sprite.getBounds(rect);
If you don’t pass in a Rectangle then a new instance will be created and
returned to you. So, if you need to call this method frequently, pass in a
Rectangle instance to help ease object creation.
Every time you call this method the bounds are calculated fresh. They are
not cached internally, or updated automatically. So be aware of this if you
are using bounds in any kind of update loop, or at scale.
403
Bounds Related Points
As well as the getBounds method, there are also a number of other methods
available that return specific points from the bounds of the Game Object. If
you don’t require the full bounds then getting just the point you do need is
more efficient.
These methods are:
• getTopLeft
• getTopCenter
• getTopRight
• getLeftCenter
• getCenter
• getRightCenter
• getBottomLeft
• getBottomCenter
• getBottomRight
They all operate in the same way. You can optionally pass them a Vector2
instance in which to store the resulting point, or they can create one for you.
They all also have the includeParent boolean, which allows them to involve
a parent container, if the Game Object has one, in the calculations, or not.
For example, here is how to use the getTopLeft method without factoring
in a parent:
const point = sprite.getTopLeft();
And here is how to use it, but factor in a parent:
const point = sprite.getTopLeft(null, true);
And here is how to use it, but factor in a parent, and store the result in a
pre-created Vector2:
const point = new Phaser.Math.Vector2();
sprite.getTopLeft(point, true);
All of the listed methods can be used in this way.
None of the bounds methods allow you to set the bounds. They are all ‘read
404
only’ methods.
405
Game Objects
Crop Component
The Crop Component allows texture-based Game Objects to ‘crop’ them-
selves. A crop is a rectangle that limits the area of the texture frame that is
visible during rendering.
Cropping a Game Object does not change its size, dimensions, physics body
or hit area, it just visually changes what you can see of it during the render-
pass.
The current crop state of a Game Object is stored in its isCropped boolean:
const isCropped = player.isCropped;
To crop a Game Object you can use the chainable setCrop method:
player.setCrop(x, y, width, height);
It takes four arguments that represent the x/y coordinate to start the crop
from, and the width and height of the crop. A crop is always a rectangle and
cannot be any other shape.
The coordinates are relative to the Game Object, so 0 x 0 is the top-left of
the Game Object texture frame.
Instead of passing in numeric values directly, or you can provide a single
Rectangle Geometry object instance as the first and only parameter:
const rect = new Phaser.Geom.Rectangle(x, y, width, height);
player.setCrop(rect);
Note that this is a Geometry object, not a Rectangle Shape object.
One set, to adjust the crop you can call the setCrop method again with new
values, or pass in an updated Rectangle instance.
If you wish to remove the crop from a Game Object, resetting it to show the
entire texture again, call the setCrop method with no arguments:
player.setCrop();
406
Crop Limitations
Internally, the crop works by adjusting the textures UV coordinates prior to
rendering. Therefore the crop can only ever be a rectangle that fits inside
the existing texture area.
You cannot crop a Game Object to show more of the texture than originally
allowed, or use any other shape than a rectangle.
Because it works by just adjusting the UV coordinates it does provide a way
to do super-fast masking, if you need a rectangular mask.
407
Data Manager
The Data Manager is a component that allows you to store, query and get
key/value paired information. This information is isolated to the parent of
the Data Manager.
By default in Phaser 3 there is one instance of the Data Manager that belongs
to the Game, which is known as the ‘registry’. In addition to this, every Scene
has a Data Manager instance. And finally, all Game Objects are able to have
a Data Manager instance as well. Plus, should you need to, you can create
your own instances and manage those.
408
Data Manager Parents
A Data Manager needs to be bound to a parent. There are four types of
parent that a Data Manager can belong to: The Game, a Scene, a Game
Object or a custom object.
sprite.setDataEnabled();
Alternatively, if any of the following data related methods: setData,
incData, toggleData or getData are called, they will also trigger the
creation of a Data Manager belonging to the Game Object. Those methods
are covered in more detail further in this section.
Once the Data Manager exists it can act as a store for any data you would
like to bind to that specific Game Object.
create() {
this.data.set("lives", 3);
}
}
The Data Manager Plugin is exactly the same as the Data Manager, including
all of the same features and methods, but is constructed to function as a Scene
409
Plugin.
create() {
this.registry.set("lives", 3);
}
}
Unlike the Scene’s Data Manager, this one is owned by the Game instance
itself. It is created automatically by during the boot process and is then
available in all Scenes via the registry property.
This means that any data set into the registry in one Scene is instantly
available in all other Scenes in your game. It also means you can use it as a
place to store global data, such as highscores, level data, settings and more.
410
The first parameter is the parent of the Data Manager, in this case the class
itself. The second parameter is the Event Emitter instance it will use. You
can use any Event Emitter you like, but it must have an instance of one.
Set Data
The first thing you’ll want to do is set some data. You can do this using the
set method:
// In the Registry
this.registry.set("playerName", "Vasquez");
411
Setting Multiple Values You can set multiple values in one call by pass-
ing an object to the set method:
// In the Registry
this.registry.set({
playerName: "Hicks",
weapon: "M41A Pulse Rifle",
score: 0,
});
Merge an existing Object into the Data Manager You can populate
the Data Manager with key/value pairs from an existing object by using the
merge method:
412
const weapon = { name: "M41A Pulse Rifle", ammo: 10 };
// In the Registry
this.registry.merge(weapon, true);
Using Objects as Values While you can use objects as values, you should
be careful when doing so. For example:
const weapon = { name: "M41A Pulse Rifle", ammo: 10 };
413
Get Data
Once you’ve stored some data you can retrieve it again using the get method:
// In the Registry
this.registry.get("playerName");
414
Data Values
When you get data, what you’re getting in most cases is a copy of that data.
For example, if the data is a string, number or boolean, then calling get will
return that value. If you then manipulate the value, the Data Manager will
not be aware of this change. For example:
const score = this.data.get("score");
score += 10;
In this case the score value is a copy of the value stored in the Data Manager.
Although you modified it by adding 10 to it, the Data Manager will not be
aware of this change. If you then call get again, the value returned will be
the original value, not the updated one.
To avoid this situation, use the values property of the Data Manager:
const score = this.data.values.score;
score += 10;
Any value set in the Data Manager is available via the values property.
Here, score is a reference to a special value stored in the Data Manager.
This time, if you add 10 to it, the Data Manager will be aware of this change
and will emit the CHANGE_DATA event, too. If you call get again, the value
returned will be the updated one.
You can also modify the values directly, such as:
this.data.values.score += 10;
Again, this is a ‘safe’ way to modify the values in the Data Manager, as it
will emit the CHANGE_DATA event and the value will be updated.
Increment Data
The inc method will increment a value by the given amount. If the value
doesn’t already exist in the Data Manager, it will be created and given the
value of the amount:
415
// In the Registry
this.registry.inc("score", 10);
Toggle Data
The toggle method will toggle a boolean value between true and false:
// In the Registry
this.registry.toggle("musicEnabled");
416
this.data.toggle("musicEnabled");
Freezing Data
The Data Manager has the ability to be ‘frozen’. If you enable this, then no
further data can be added or removed from the Data Manager, and values
already stored within it cannot be modified. This is useful if you wish to
lock-down a Data Manager and make it read-only.
To freeze, or un-freeze a Data Manager, call the chainable setFreeze method:
// In the Registry
this.registry.setFreeze(true);
417
Changing the frozen state of the Data Manager is immediate. For example, if
you are adding an object containing several new values, and in the SET_DATA
event listener you call setFreeze(true), then the remaining values will never
be added.
Removing Data
You can remove a single item of data from the Data Manager using the
remove method:
// In the Registry
this.registry.remove("playerName");
418
const playerName = this.data.pop("playerName");
has The first, and most simple, is the has method. This checks to see if
the Data Manager has a key matching the given string:
// In the Registry
this.registry.has("playerName");
419
// From within a Scene
this.data.has("playerName");
count To return the total number of entries currently being stored in the
Data Manager, use the count property:
// In the Registry
this.registry.count;
getAll To return everything within the Data Manager, use the getAll
method:
// In the Registry
this.registry.getAll();
420
{
playerName: 'Hicks',
weapon: 'M41A Pulse Rifle',
score: 0
}
If the Data Manager is empty, an empty object is returned.
When using getAll you should treat the returned object as read-only. If you
modify it directly, the Data Manager will not be aware of the changes.
each The each method allows you to pass all entries in the Data Manager
to a given callback. You pass the callback as the first argument, an optional
context as the second and then any further arguments:
// In the Registry
this.registry.each((parent, key, value) => {
console.log(key, value);
});
query The query method allows you to search the Data Manager for keys
that match the given Regular Expression. It will then return an object
421
containing any matching key/value pairs.
For example, let’s assume we have populated the Data Manager with a num-
ber of different weapons:
this.data.set({
"M41A Pulse Rifle": 10,
"M56 Smartgun": 20,
"M240 Flamethrower": 30,
"M42A Scope Rifle": 40,
"M83 SADAR": 50,
"M92 Grenade Launcher": 60,
});
We can then use the query method to return all weapons that contain the
word ‘Rifle’:
const rifles = this.data.query(/Rifle/);
The returned object will contain all matching key/value pairs:
{
'M41A Pulse Rifle': 10,
'M42A Scope Rifle': 40
}
If no matches are found, an empty object is returned.
The list and values properties The list property is an array contain-
ing all of the keys in the Data Manager:
// In the Registry
this.registry.list;
422
// In the Registry
this.registry.values;
423
Finally, if you have created your own instance of the Data Manager, then you
would have provided an Event Emitter when you did this. It’s this emitter
you should listen tgo events from.
create() {
this.data.set("lives", 3);
shutdown() {
this.data.reset();
}
}
424
Game Objects
Depth Component
425
The current depth of a Game Object is stored in its depth numeric property:
const depth = sprite.depth;
You can set the depth of a Game Object using the chainable setDepth
method:
sprite.setDepth(value);
Or, you can modify the depth property directly:
sprite.depth = value;
The value can be any number, either an integer or a float. The default value
is zero.
There is no upper or lower bounds on what the value can be and the numbers
do not have to be assigned consecutively. If it’s easier for you to give a Game
Object a depth of 1000, and another a depth of 500, then you’re free to do
so.
You can also bind the depth property to a Game Objects position. For
example, it’s quite common to bind the depth of a Game Object to its y
position, so that the higher it is in the Scene, the higher its depth value:
update();
{
sprite.setDepth(sprite.y);
}
If one or more Game Objects share the same depth value, then they are
sorted based on their index within the Display List. The first one in the list
is rendered first, and so on.
Depth Updates
When the depth property of any Game Object is modified, the Depth Com-
ponent tells the Scene that it needs to run a depth sort on the Display List.
This is done by the component calling the DisplayList.queueDepthSort
method and it happens automatically, you don’t need to do anything else.
Because sorting the rendering list can be a costly operation if there are a lot
of Game Objects, Phaser will queue the depth sort and only execute it at
426
render time. If no Game Objects have had their depth changed since the last
frame, the depth sort is skipped entirely.
Creating new Game Objects, or removing existing ones, will also cause the
depth sort to be queued.
427