13

I've got a React application created with create-react-app and I'm trying to integrate Phaser 3 as well. I followed this guide to get started. I've got the canvas rendering the text but loading images in the preload does not seem to be working. I get the default failed to load texture image displayed.

import ExampleScene from "./scenes/ExampleScene";

import * as React from "react";

export default class Game extends React.Component {
  componentDidMount() {
    const config = {
      type: Phaser.AUTO,
      parent: "phaser-example",
      width: 800,
      height: 600,
      scene: [ExampleScene]
    };

    new Phaser.Game(config);
  }

  shouldComponentUpdate() {
    return false;
  }

  render() {
    return <div id="phaser-game" />;
  }
}

ExampleScene:

import Phaser from "phaser";

export default class ExampleScene extends Phaser.Scene {
  preload() {
    this.load.image("logo", "assets/logo.png");
  }
  create() {
    const text = this.add.text(250, 250, "Phaser", {
      backgroundColor: "white",
      color: "blue",
      fontSize: 48
    });

    text.setInteractive({ useHandCursor: true });
    this.add.image(400, 300, "logo");

    text.on("pointerup", () => {
      console.log("Hello!");
      //store.dispatch({ type: ACTION_TYPE });
    });
  }
}

The idea is to create a visualization with flowers growing based on a simple gene engine. So Phaser would get instructions from the Store about the current state.

I'm guess this has something to do with the way Phaser loads and there's a conflict with how React updates. I'm preventing the component from updating as I only need the game to receive instructions by listening to the store

I've already looked at this SO answer and the accompanying wrapper, but it is outdated.

How can I get Phaser to load images when in a Create-React-App?

CodeSandbox: https://codesandbox.io/s/github/nodes777/react-punnett/tree/phaser-game Repo: https://github.com/nodes777/react-punnett/tree/phaser-game

Taylor N
  • 500
  • 1
  • 3
  • 15
  • 2
    Try using a hook with function components instead :) https://stackblitz.com/edit/react-phaser?file=hooks%2FuseGame.ts – jdnichollsc Sep 09 '21 at 21:13

6 Answers6

12

Other option is using WebComponents to be able to integrate Phaser with any other framework (React, Angular, VueJS, etc), check this npm package: https://www.npmjs.com/package/@ion-phaser/core

Also, you can use the React wrapper of that library to use Phaser with React components easily, so you don't need to manipulate WebComponents directly, example:

import React from 'react'
import Phaser from 'phaser'
import { IonPhaser } from '@ion-phaser/react'

const game = {
  width: "100%",
  height: "100%",
  type: Phaser.AUTO,
  scene: {
    init: function() {
      this.cameras.main.setBackgroundColor('#24252A')
    },
    create: function() {
      this.helloWorld = this.add.text(
        this.cameras.main.centerX, 
        this.cameras.main.centerY, 
        "Hello World", { 
          font: "40px Arial", 
          fill: "#ffffff" 
        }
      );
      this.helloWorld.setOrigin(0.5);
    },
    update: function() {
      this.helloWorld.angle += 1;
    }
  }
}

const App = () => {
  return (
    <IonPhaser game={game} />
  )
}

export default App;

Fore more details check the repo: https://github.com/proyecto26/ion-phaser/tree/master/react

jdnichollsc
  • 1,520
  • 1
  • 24
  • 44
  • 1
    Are there ways to pass props into the IonPhaser component that can be used in the game? – Debo Akeredolu Mar 21 '22 at 23:24
  • Hello @DeboAkeredolu, you can use a custom hook instead, e.g: https://stackblitz.com/edit/react-phaser But I recommend you to use Events, or some other means of communication between React and Phaser (event emitters, etc) – jdnichollsc Mar 22 '22 at 23:11
5

A year ago I was here looking for the answer myself. Here's pattern which should work.

import Phaser from "phaser"
import React, { useEffect, useState } from "react"

/** @tutorial I made this! This answers how you get your image. */
import logoImage from "./path-to-logo.png"

/** @tutorial I made this! Use a functional React component and `useEffect` hook.*/
export const Phaser3GameComponent = ({ someState }) => {
  // Optional: useful to delay appearance and avoid canvas flicker.
  const [isReady, setReady] = useState(false)
  // Just an example... do what you do here. 
  const dataService = (changedState) => {
    // I'm not sure how to use stores, but you'll know better what to do here.
    store.dispatch(
      {
        ...someState,
        ...changedState,
      },
      { type: ACTION_TYPE }
    )
  }
  // This is where the fun starts. 
  useEffect(() => {
    const config = {
      callbacks: {
        preBoot: game => {
          // A good way to get data state into the game.
          game.registry.merge(someState)
          // This is a good way to catch when that data changes.
          game.registry.events.on("changedata", (par, key, val, prevVal) => {
            // Simply call whatever functions you want outside.
            dataService({ [key]: val })
          })
        },
      },
      type: Phaser.AUTO,
      parent: "phaser-example",
      width: 800,
      height: 600,
      scene: [ExampleScene],
    }
    let game = new Phaser.Game(config)
    // Triggered when game is fully READY.
    game.events.on("READY", setReady)
    // If you don't do this, you get duplicates of the canvas piling up.
    return () => {
      setReady(false)
      game.destroy(true)
    }
  }, []) // Keep the empty array otherwise the game will restart on every render.
  return (
    <div id="phaser-example" className={isReady ? "visible" : "invisible"} />
  )
}

export default class ExampleScene extends Phaser.Scene {
  preload() {
    this.load.image("logo", logoImage)
  }
  create() {
    // You made this!
    const text = this.add.text(250, 250, "Phaser")
    text.setInteractive({ useHandCursor: true })
    this.add.image(400, 300, "logo")
    /** @tutorial I made this! */
    // Get all that lovely dataState into your scene,
    let { clickCount } = this.registry.getAll()
    text.on("pointerup", () => {
      // This will trigger the "changedata" event handled by the component.
      this.registry.merge({ clickCount: clickCount++ })
    })
    // This will trigger the scene as now being ready.
    this.game.events.emit("READY", true)
  }
}
Timothy Bushell
  • 170
  • 1
  • 7
4

In my case I use the following component and it works fine:

import Phaser from 'phaser';
import * as React from 'react';

import { HTML_DIV_ID, gameConfig } from './gameConfig';

export const GameWrapper = () => {
    const [game, setGame] = React.useState<Phaser.Game>();

    React.useEffect(() => {
        const _game = new Phaser.Game(gameConfig());

        setGame(_game);

        return (): void => {
            _game.destroy(true);
            setGame(undefined);
        };
    }, []);

    return (
        <>
            <div id={HTML_DIV_ID} />
        </>
    );
};

With create-react-app and React.StrictMode:

Also I deleted React.StrictMode (default option with create-react-app) because it mounts and unmounts all components so I had unexpected behavior with phaser sometimes

You can use react hook for the code above as:

// usePhaser.js
export function userPhaser(config) {
    const [game, setGame] = React.useState();

    React.useEffect(() => {
        const _game = new Phaser.Game(config);

        setGame(_game);
    
        return (): void => {
            _game.destroy(true);
            setGame(undefined);
        };
    }, []);

    return game;
}
zemil
  • 3,235
  • 2
  • 24
  • 33
2

I started from scratch and created my own boilerplate from the phaser 3 template. I wrote about the specific steps to add React to the Phaser 3 template here.

It seems like you could eject from Create-React-App and add in Phaser 3 from there, but the warnings not to eject turned me away from that solution.

Taylor N
  • 500
  • 1
  • 3
  • 15
  • 1
    I read your meduim blog post, unfortunately, it didn't work, I got errors saying unable to find 'react', unable to find 'index.html'. – ShashankAC Jan 25 '20 at 17:51
  • 1
    Hmm, you should be able to run `git clone https://github.com/nodes777/phaser3-react-template` then `npm install`, then `npm start`. Did you try that or follow the tutorial from scratch? Let me know if you get errors from running those instructions. – Taylor N Jan 25 '20 at 23:58
0

Here's phaser3+react+websocket template, use this repository as sample. Key feature is two dimensional functionality, you can call react controls from your scene. To call react hooks outside react components:

CONTROLS.setVersion(`Phaser v${Phaser.VERSION}`)

To call phaser from react component you can use(import global variable from phaser.game.ts):

import game from "./phaser-game"
(game.scene.game as GameScene).<whatever you want>

https://github.com/tfkfan/phaser3-react-template

-2

You need to put images inside the folder public!

shapeare
  • 4,133
  • 7
  • 28
  • 39
  • This is not true. I have a React website with dependencies which are phaser games that have statements like `import img1 from "./images/img1.png"` in those modules. I import the game config in React Component modules. You can even import `img1` from the game library and reference it directly in the component as, for instance, a thumbnail for a button which will load the game. When I follow my pattern (see my answer) React is able to `build` all those libraries into one bundle - including all the hashed images. – Timothy Bushell May 27 '23 at 09:02