Before We Start
When I was researching how to do this for my game, I noticed a lot of blogs about it didn't specify their setup or versions, which meant that their solutions might not work but I didn't know until I tried. So: I'm using React version 18.2.0, Phaser 3.70.0, and Ruby 2.7.4. We'll go through setup, EventEmitters, and some other small things. This method does not use Redux, although using Redux is a useful way for managing state that is shared by Phaser and React.
Still Here
So, you got through the fun setup for creating a webapp with a React frontend and Ruby on Rails backend- congrats! I like this combination for managing websites as well.
Now you want to add a game made with Phaser.js to your site. I had to do that myself, so I thought I'd share my process in case it can help others get their games on their websites. Fortunately, it's not too difficult.
I'll also go over setting up a Phaser EventEmitter that can be used to communicate between your React components and your Phaser game, so you can save data like user scores and other things in your backend. But first things first!
Installing Phaser.js Into Your React/Rails Webapp
Your setup may be slightly different than mine, so I'll show what mine looks like to help clarify where things go.
Root
-app
-channels
-controllers
-jobs
-mailers
-models
-serializers
-views
-client
-node_modules
-public
-src
-package-lock.json
-package.json
-config
-environments
...
-routes
-db
-lib
-node_modules
-storage
-tmp
-config.ru, Gemfile, package-lock.json, package.json,
Procfile, Rakefile, webpack.config.js are all on this
level too, among others.
Note that all Rails API related folders are contained within the app
folder, and all frontend-related folders are in client
. The only backend things that are separate from app
are the db (database) and config folders; db is where you would have your migrations and schema and seeds.rb file, and config contains your routes.rb file.
In order to use phaser, you need to install it using npm install phaser
in your terminal. Make sure to do this within the client
folder, as Phaser will run in the frontend, or alternatively you can run the command while in your root folder by doing npm install --prefix client
. Next, navigate to your index.html file (you can also add this next bit to your App.js if you'd prefer). Once there, create a div. This will be the container for your Phaser game, so give it an id of "phaser-container". You'll need to reference this element by it's id later to tell Phaser which element is its parent element.
Next we can set up our Phaser game config. I chose to make my game config a React component, so I imported React and useEffect
. You'll also need to import Phaser, and any game scenes that you need. This is what my Phaser game config looks like in my project:
const PhaserGameConfig = ({gameType, onGamePlayed}) => {
useEffect(() => {
const config = {
type: Phaser.AUTO,
width: 700,
height: 500,
backgroundColor: '#ffffff',
physics: {
default: 'arcade',
arcade: {
gravity: {y: 0},
debug: true,
},
},
parent: 'phaser-game',
scene: 'TicTacToe' //or, in my case, getScene()
};
new Phaser.Game(config);
}, []);
return <div id="phaser-game"></div>;
}
export default PhaserGameConfig;
Now, you may have noticed that I passed the prop gameType
to my component (there's also another prop, onGamePlayed
, which I'll get to shortly). In my case, my site has multiple games on it, so I created a getScene
method within my component, which returned a different Phaser scene depending on the value of gameType.
Now, wherever you want your Phaser game or games to be displayed, you can simply return a PhaserGameConfig component, and in my case, pass the game's name as a prop to the component.
To have access to any assets, such as images or audio, in your Phaser scene, simply put the assets into the public folder that's within the client folder.
The communication between Phaser and React that I used involves the use of Phaser's EventEmitter
. The example I'm going to show worked for my game, but it may take some tinkering to get it working in yours depending on what your needs are for communicating between the two. I hope showing how I did it is helpful. If anyone has an idea for a better way of solving the problem below, I'd love to hear about it in the comments!
For me, I simply needed a way to detect when a user had played an entire game of TicTacToe so that I could add to a count of the number of games the user has played so far.
In my project, I added a few lines to my PhaserGameConfig
component that allowed me to achieve this:
//PhaserGameConfig component as shown above...
//after the useEffect
const handler = () => {
onGamePlayed();
eventEmitter.on("gameEnd", handler);
return <div id="phaser-game"></div>
} //bracket marking the end of the PhaserGameConfig component
export const eventEmitter = new Phaser.Events.EventEmitter();
export default PhaserGameConfig;
}
Now, in my Phaser scene for TicTacToe, I import that eventEmitter like this: import {eventEmitter} from '../PhaserGameConfig'
(in my case, my phaser scenes are not in the same folder as my components). Then, at the start of create()
, I added this.eventEmitter = eventEmitter
. Voila! Now, after a game finishes in my TicTacToe scene, I can simply emit my event like this:
this.eventEmitter.emit("gameEnd);
And my PhaserGameConfig
component calls the callback function passed as a prop to the component, adding to the count of the number of games played by the user.
Now, for more complicated event handling where you have more than one or two events, or events that need to be handled differently depending on the game, I would create my eventEmitter
as a context to be used with useContext
. This helps to avoid lots of prop drilling, and decouples the event handling from the PhaserGameConfig
component itself. This would probably require the use of useMemo
, useCallback
and React.memo
where appropriate to optimize a bit and avoid a lot of unnecessary re-renders.
Have a better idea for how to handle communication between React and Phaser? Let me know in the comments!
Top comments (0)