Here’s a pattern for embedding Phaser3 Games inside React Components.
At its simplest implementation, you can use this pattern to add Phaser3 Games and treat them like a GIF animation on steroids.
At its most complex, I’ll show you how to launch Phaser3 games inside (and from other) React components, catch events happening inside the game to trigger functions in React. These events could update status notifications in the website’s banner or fire data to your website’s back-end API to keep a permanent record of player progress.
As with all my posts, I won’t drip feed the solutions with too many “now add this” and “then add this” snippets, but simply show you entire code patterns which have worked for me.
As promised, here is the basic pattern that we will be using:
import Phaser from "phaser"
import React, { useEffect, useState } from "react"
import happyFace from "./happy-face.png"
class HappyFaceScene extends Phaser.Scene {
preload() {
// A convention of mine to avoid `this` confusion with sub classes is to
// give it a better reference name at the top of every function.
let scene = this
scene.load.image("happyFace", happyFace)
}
create(data) {
let scene = this
// Add your game objects and all that other stuff. It's just a simple image.
scene.add.image(0, 0, "happyFace").setOrigin(0, 0).setDisplaySize(480, 142)
// Finally trigger an event so that the scene is now visible. This is optional
// but useful if you want to transition your game's appearance.
scene.game.events.emit("putOnAHappyFace", true)
}
update() {
let scene = this
let { lives, progress } = scene
// For example, monitor the number of lives and exit when
if (!lives) {
// Save the progress.
game.registry.merge(progress)
// Trigger the game end.
scene.game.events.emit("putOnAHappyFace", false)
}
}
}
export const Phaser3GameComponent = () => {
const [isReady, setReady] = useState(false);
useEffect(() => {
// Nothing really special here... Your phaser3 config should work just fine.
let config = {
height: 320,
width: 480,
physics: { default: "arcade" },
scale: {
// Except this should match the ID of your component host element.
parent: "phaser-game",
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_HORIZONTALLY,
},
transparent: true,
type: Phaser.AUTO,
}
let game = new Phaser.Game(config)
// Triggered when game is fully visible.
game.events.on('putOnAHappyFace', setReady)
// Add your scene/s here (or in `scene` key of `config`).
game.scene.add("HappyFaceScene", HappyFaceScene, true)
// If you don't do this, you get duplicates of the canvas piling up
// everytime this component renders.
return () => {
setReady(false)
game.destroy(true)
}
// You must have an empty array here otherwise the game restarts every time
// the component renders.
}, [])
// Return the host element where Phaser3 will append the canvas.
return <div id="phaser-game" className={isReady ? "visible" : "invisible"} />
}progress
You can see how simple this is. React does what’s made for: it reacts to things which happen in the game.
As well as generally catching events raised by the game, React can also be wired into Phaser’s callbacks
setting.
In this additional snippet, which you would add to the Phaser Game config part of the pattern above, we will load the previous user progress into the game’s Phaser3 “registry” using the merge
function. We can also catch changes to the
...
physics: { default: "arcade" },
callbacks: {
preBoot: (game) => {
game.registry.merge(userProgress)
game.registry.events.on('changedata', function(parent, key, value, previousValue) => {
updateUserProgress(key, value)
})
},
},
...
Where userProgress
come out of the store and updateUserProgress
mutates changes back to the store.
This shows how easy it is to embed Phaser games (either to display a non-interactive animation or a playable one) in React, and pass data in and out.
Lastly, using a div as the container is perfect if you want to embed the game inline on a page. In the implementation I’m working on for a client, the game is shown full screen using a Bootstrap-React modal like this:
return (
<Modal show={isReady} backdrop="static" fullscreen={true} keyboard={false}>
<Modal.Body>{isReady && <div id="PhaserGame" />}</Modal.Body>
</Modal>
)
With this simply change, the bootstrap modal will pop up when the game loads and vanish when its over.