paint-brush
Embedding Phaser3 Games into React 18 Function Components with useEffectsby@timbushell
2,628 reads
2,628 reads

Embedding Phaser3 Games into React 18 Function Components with useEffects

by Tim BushellMarch 4th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

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.
featured image - Embedding Phaser3 Games into  React 18 Function Components with useEffects
Tim Bushell HackerNoon profile picture

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.

Prerequisites

  • Familiar with React.
  • Familiar with Phaser3.
  • Familiar with JavaScript.
  • A tendency to click “I like this post” buttons.

The core pattern

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.

Scoring

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.

Modal

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.