Using Phaser together with React - reactjs

I am trying to understand how I could use React for rendering all the UI elements and Phaser for rendering a game (using Canvas or WebGL) within the React application.
One way would be to use React's componentDidMount, so after I have my HTML ready I can use a div id to render the Phaser content. In this case who does the rendering? React of Phaser?
If the mentioned way is not the right one, could you suggest another?

There is a module for this on Github called react-phaser. Here is a CodePen that does not use any separate module.
var PhaserContainer = React.createClass({
game: null,
...
componentDidMount: function(){
this.game = new Phaser.Game(800, 400, Phaser.AUTO, "phaser-container",
{
create: this.create,
update: this.update
});
},
...
render: function(){
return (
<div className="phaserContainer" id="phaser-container">
</div>
);
}
...
}
This is not my CodePen. Credit goes to Matthias.R

Check this new WebComponent to integrate Phaser with any other framework 👍 https://github.com/proyecto26/ion-phaser
Example:
import React, { Component } from 'react'
import Phaser from 'phaser'
import { IonPhaser } from '#ion-phaser/react'
class App extends Component {
state = {
initialize: false,
game: {
width: "100%",
height: "100%",
type: Phaser.AUTO,
scene: {}
}
}
render() {
const { initialize, game } = this.state
return (
<IonPhaser game={game} initialize={initialize} />
)
}
}

Related

#react-google-maps/api - Loading map is blocking the main thread

I am currently working on a website, where I use google maps to display some markers with places location.
I am using this npm package: https://www.npmjs.com/package/#react-google-maps/api.
The issue is that when I do site audit with Google Insights (https://developers.google.com/speed/pagespeed/insights/) I am getting very low mobile performnace. It is about 50.
This is considered a issue, because I am using Next.js (version 10.0.6). Audit score on desktop is great.
I think, that the main issue is with loading a map. This proccess is blocking my website for about 500 ms. It is a lot.
Screen shot of an issue
Here is how I am getting the map at the moment:
I am using functional component
I am importing GoogleMap and useLoadScript from #react-google-maps/api
Loading the map
Rendering the map (onLoad is used to set ref)
I have already tried usingEffect (with an empty array), async/await syntax, nothing helped.
I will be very grateful for any help.
Kind regards,
Bartek
Although I am not that familiar on how Google Insight works, loading the script directly instead of relying on other 3rd party libraries (#react-google-maps/api) could prove beneficial and should reduce the latency.
App.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import Map from './components/map';
import "./style.css";
class App extends Component {
render() {
return (
<Map
id="myMap"
options={{
center: { lat: -33.8569, lng: 151.2152 },
zoom: 8
}}
/>
);
}
}
export default App;
map.js
import React, { Component } from "react";
import { render } from "react-dom";
class Map extends Component {
constructor(props) {
super(props);
this.state = {
map: ""
};
}
onScriptLoad() {
this.state.map = new window.google.maps.Map(
document.getElementById(this.props.id),
this.props.options
);
var marker = new window.google.maps.Marker({
position: { lat: -33.8569, lng: 151.2152 },
map: this.state.map,
title: "Hello Sydney!"
});
//adding markers by click
this.state.map.addListener("click", e => {
//Determine the location where the user has clicked.
this.addMarker(e.latLng);
});
}
componentDidMount() {
if (!window.google) {
var s = document.createElement("script");
s.type = "text/javascript";
s.src = `https://maps.google.com/maps/api/js?key=YOUR_API_KEY`;
var x = document.getElementsByTagName("script")[0];
x.parentNode.insertBefore(s, x);
s.addEventListener("load", e => {
this.onScriptLoad();
});
} else {
this.onScriptLoad();
}
}
addMarker(latLng) {
var marker = new google.maps.Marker({
position: latLng,
map: this.state.map
});
}
render() {
return <div className="map" id={this.props.id} />;
}
}
export default Map;

How to integrate Phaser into React

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
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
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)
}
}
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.
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;
}
You need to put images inside the folder public!
For me, I see the best practice to use both of them properly is to create phaser project separately and host it separately using firebase or whatever hosting service you prefer, and then take the link and put it in an iframe tag inside react.
in this way you can manage them efficiently and you can manipulate react website in more comfortable way especially the mobile width compatibility.

How to use VideoJS plugins with ReactJS?

I am trying to use videojs-offset plugin, with ReactJS.
But, it is showing me follwoing error -
TypeError: this.player.offset is not a function
import React, { Component } from 'react';
import './App.css';
import PropTypes from 'prop-types';
import "video.js/dist/video-js.css";
import videojs from 'video.js'
import videoJsContribHls from 'videojs-contrib-hls'
import offset from 'videojs-offset';
export default class VideoPlayer extends React.Component {
componentDidMount() {
// instantiate Video.js
this.player = videojs(this.videoNode, this.props, function onPlayerReady() {
console.log('onPlayerReady', this);
this.player.offset({
start: 10,
end: 300,
restart_beginning: false //Should the video go to the beginning when it ends
});
});
}
// destroy player on unmount
componentWillUnmount() {
if (this.player) {
this.player.dispose()
}
}
render() {
return (
<div>
<div data-vjs-player>
<video ref={ node => this.videoNode = node } className="video-js"></video>
</div>
</div>
)
}
}
This is a common issue while integrating ANY videojs plugin with reactJS.
This problem is solved with following change -
First, In VideoJS Options, the plugins should be initialized, like,
plugins: offset: {start: 10, end: 300, restart_beginning: false} }
if your plugin does not have any parameters, you can initialize it like plugin_name: {}
Then, In componentDidMount() method, register the plugin like this -
componentDidMount() {
videojs.registerPlugin('offset',offset);
this.player = videojs(this.videoNode, this.props, function
onPlayerReady() {
console.log('onPlayerReady', this);
});}
This solution works for integrating ANY videojs plugin with react.

use videojs plugins in react Video JS

As doc mentioned I ve initialized video js in react by doing something like this ...
import React from 'react';
import videojs from 'video.js'
export default class VideoPlayer extends React.Component {
componentDidMount() {
// instantiate video.js
this.player = videojs(this.videoNode, this.props, function onPlayerReady() {
console.log('onPlayerReady', this)
});
}
// destroy player on unmount
componentWillUnmount() {
if (this.player) {
this.player.dispose()
}
}
// wrap the player in a div with a `data-vjs-player` attribute
// so videojs won't create additional wrapper in the DOM
// see https://github.com/videojs/video.js/pull/3856
render() {
return (
<div data-vjs-player>
<video ref={ node => this.videoNode = node } className="video-js"></video>
</div>
)
}
}
I want to integrate VideoJs Overlay plugin in this...
so i ve done something like this...
import React from 'react';
import videojs from 'video.js'
export default class VideoPlayer extends React.Component {
componentDidMount() {
// instantiate video.js
this.player = videojs(this.videoNode, this.props, function onPlayerReady() {
player.overlay({
content: 'Default overlay content',
debug: true,
overlays: [{
content: 'The video is playing!',
start: 'play',
end: 'pause'
}, {
start: 0,
end: 15,
align: 'bottom-left'
}, {
start: 15,
end: 30,
align: 'bottom'
}, {
start: 30,
end: 45,
align: 'bottom-right'
}, {
start: 20,
end: 'pause'
}]
});
});
}
// destroy player on unmount
componentWillUnmount() {
if (this.player) {
this.player.dispose()
}
}
render() {
return (
<div data-vjs-player>
<video ref={ node => this.videoNode = node } className="video-js" id="videojs-overlay-player"></video>
</div>
)
}
}
While doing this it give me error like player.overlay is not function...
and if i do videojs.registerPlugin('overlay', overlay);
and call overlay function it gives me error like component Overlay is undefined
How to workout videojs plugins in react way????
You need to import videojs-overlay package before you use it.
Follow these steps :
Install the plugin: npm i videojs-overlay --save
Import the package in Player class : import overlay from 'videojs-overlay';
Register the plugin before instantiating the player: videojs.registerPlugin('overlay', overlay);
Then, player.overlay({... will work as intended.

react-transition-group lifecycle method componentWillLeave not being called

I'm trying to get componentWillLeave to get called and I'm having no luck. componentWillAppear gets called, but I'm unable to get the former to call. Most responses I've seen call for making sure the callbacks are being called, which I am doing. I'm also using React router if that makes a difference. Any help would be greatly appreciated.
import React, { Component } from 'react';
import { TweenMax } from 'gsap';
import PropTypes from 'prop-types';
import { Animated } from '../../components/common/Animated';
import NavButton from '../../components/navButton/NavButton';
import './Team.scss';
class Team extends Component {
componentDidMount() {
this.el = this.refs.container;
}
componentWillAppear(callback) {
TweenMax.set(this.el, { x: '100%' });
TweenMax.to(this.el, 1, { x: '0%' });
callback();
}
componentWillLeave(callback) {
TweenMax.to(this.el, 1, { x: '-100%', onComplete: callback });
}
render() {
const { match } = this.props;
return (
<div ref="container" className="teamContainer">
<NavButton
title={'To Interactive'}
route={`/${match.params.monster}/interactive`}
/>
</div>
);
}
}
Team.propTypes = {
match: PropTypes.shape({
pathname: PropTypes.shape({
monster: PropTypes.number.isRequired,
}),
}),
};
Team.defaultProps = {
match: {
pathname: {
monster: -1,
},
},
};
export default Animated(Team);
I haven't tried this with react router, but with a similar router approach. I got it to work using TransitionGroup v1.x and made sure my active child component was
a react element
had it's own unique key
https://github.com/reactjs/react-transition-group/tree/v1-stable
Note:
When using CSSTransitionGroup, there's no way for your components to be notified when a transition has ended or to perform any more complex logic around animation. If you want more fine-grained control, you can use the lower-level TransitionGroup API which provides the hooks you need to do custom transitions.

Resources