How to integrate Phaser into React - reactjs

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.

Related

NextJS issue with server side rendering with react-d3-tree

To start off, I have looked at issue number 40 and 95 on the react-d3-tree github
I don't see anything on StackOverflow that would help me. I am trying to implement the parseJSON method so I can take my own JSON file from the project folder and then generate a tree diagram.
Let's start from what I did in the beginning. I copy pasted the example code which worked for 2 seconds before crashing. Reason? Server Side Rendering. Great, so then I find this from NextJS which allows me to disable SSR for some components. Hey, now the example code is working. Let's try the example code where they use external data! Nope, it can't find the parseJSON method. I have no idea what to do, can't find anything to fix this. I am trying to import this function that has some issue with SSR, but because it isn't a component I am not able to import it using dynamic, and I can't import normally because it causes a "window is not defined" error because of SSR.
The following are my main two files.
DynamicComponent.js [Version 1]
import dynamic from 'next/dynamic';
const Tree = dynamic(
() => import('react-d3-tree'),
{ ssr: false },
);
export default Tree;
DynamicComponent.js [Version 2]
import dynamic from 'next/dynamic';
export const Tree = dynamic(
() => import('react-d3-tree'),
{ ssr: false },
);
export const treeUtil = dynamic(
() => import('react-d3-tree/src/util'),
{ ssr: false },
);
Diagram/index.js
import React from 'react';
import { Tree, treeUtil } from '../DynamicComponent';
const myTreeData = require('../fakeData.json');
class Diagram extends React.PureComponent {
constructor() {
super();
this.state = {
data: undefined,
};
}
componentWillMount() {
treeUtil.parseJSON(myTreeData)
.then((data) => {
this.setState({ data });
});
}
render() {
return (
<div
id="treeWrapper"
style={{ width: '50em', height: '20em' }}
>
<Tree data={this.state.data} />
</div>
);
}
}
export default Diagram;
Error I Get with Version 1
ReferenceError: treeUtil is not defined
Error I Get with Version 2
TypeError: _DynamicComponent__WEBPACK_IMPORTED_MODULE_1__.treeUtil.parseJSON is not a function
Please StackOverflow, you're my only hope.
I ran into the same problem with Cytoscape, a similar library (but specifically for graph-network visualization). It took lots of trial and error, but the solution was:
import the component dynamically
remove the import JSON and inline it into a js file. For some stupid reason, that worked for me and was the magic fix. (how big was your JSON file?) Worse-case try copying & pasting into the component itself.
For your component try this:
// convert fakeData.json to fakeData.js
export default {...fake data here };
import React from 'react';
import dynamic from 'next/dynamic'
import myTreeData from 'fakeData';
const Tree = dynamic(
() => import('./dynamicComponent'),
{ ssr: false }
);
// you can also delineate a loading component;
// converted to hooks for '21
const Diagram = () => {
const [data,setData] = useState({});
useEffect(() => {
treeUtil.parseJSON(myTreeData)
.then((data) => {
setData(data);
})
},[treeUtil,myTreeData,setData]);
return (
<div
id="treeWrapper"
style={{ width: '50em', height: '20em' }}
>
<Tree data={data} />
</div>
);
}
export default Diagram;
I guess treeUtil is not a react component, so you can't use dynamic to import it. Just import it normally may be work.
import dynamic from 'next/dynamic';
export const Tree = dynamic(
() => import('react-d3-tree'),
{ ssr: false },
);
export { default as treeUtil } from 'react-d3-tree/src/util';

Can't seem to load custom fonts with Expo's Font.loadAsync

I'm using React Native with Expo, and it is all going well except for this one issue with custom fonts. I have my font Lobster-Regular.ttfin ./assets/fonts, and I have been trying to load it as seen in the official docs:
componentDidMount() {
Font.loadAsync({
'Lobster': require('./assets/fonts/Lobster-Regular.ttf'),
});
}
I then style my header as such:
headerText: {
color: 'white',
fontSize: 30,
fontFamily: 'Lobster'
},
All I get is
fontFamily 'Lobster' is not a system font and has not been loaded
through Font.loadAsync.
If you intended to use a system font, make sure you typed the name correctly and that it is supported by your device operating system.
If this is a custom font, be sure to load it with Font.loadAsync.
Am I missing something?
Yes. You are missing that the call is Font.loadAsync(). This means that it loads asynchronously. As in: It takes a while. You can't render the UI until the font has loaded. You need to do something along these lines:
import { AppLoading, Font } from 'expo'
state = {
fontsLoaded: false,
...
}
componentWillMount() {
Font.loadAsync( {
'Lobster': require('./assets/fonts/Lobster-Regular.ttf')
}
).then( () => this.setState( { fontsLoaded: true } ) )
}
render() {
if( !this.state.fontsLoaded ) {
return <AppLoading/>
}
return (
...
)
}
Font.loadAsync is old and proved to have unpredictable problems. now expo have rolled out new solutions here
so the correct code is now:
import {useFonts} from 'expo-font';
import AppLoading from "expo-app-loading";
let [fontsLoaded] = useFonts({
'Lobster': require('./assets/fonts/Lobster-Regular.ttf'),
});
if (!fontsLoaded) {
return <AppLoading/>;
}
...
install expo-font package from expo CLI because sometime expo-font version is not compatible with your expo version so,
step 1:
expo install expo-font
step 2:
class App extends React.Component {
state = {
fontLoaded: false,
};
componentDidMount() {
this.loadAssetsAsync();
}
async loadAssetsAsync() {
await Font.loadAsync({
// Load a font `Montserrat` from a static resource
MuseoSans500: require("./assets/fonts/museosans_500-webfont.ttf"),
MuseoSans700: require("./assets/fonts/museosans_700-webfont.ttf"),
});
this.setState({ fontLoaded: true });
}
render() {
if (!this.state.fontLoaded) {
return null; // render some progress indicator
}
return <AnyComponent />;
}
}
** react-native font in stateless function **
**step:1 import font from expo **
import * as Font from 'expo-font';
import { AppLoading } from 'expo';
*step2: require font from files *
// fetchFonts from local files type ttf
const fetchFonts = () => {
return Font.loadAsync({
'PacificoRegular': require('../assets/Pacifico/Pacifico-Regular.ttf'),
});
};
*****step3:use state *****
// state font fetch control
const [fontloaded,setfontloaded]=useState(false);
**step4: use app loaded **
if(!fontloaded){
return(
<AppLoading
startAsync={fetchFonts}
onFinish={()=>{setfontloaded(true)}}
onError={console.warn}/>
)
}
**step5:style font **
txt:{
padding:5,
fontSize:18,
fontFamily: 'PacificoRegular',
}
useEffect(()=>{
async function loadFonts(){
await Font.loadAsync({
'Montserrat': require("./assets/fonts/Montserrat/Montserrat-Regular.ttf"),
'Montserrat-SemiBold': require('./assets/fonts/Montserrat/Montserrat-SemiBold.ttf'),
'Montserrat-Bold': require('./assets/fonts/Montserrat/Montserrat-Bold.ttf'),
'Fascinate': require('./assets/fonts/Fascinate/Fascinate-Regular.ttf')
}).then(res=>{
console.log("FONTS LOADED!");
setLoaded(true)
}).catch(Err=>{
setLoaded(true);
console.log(Err);
});
}
loadFonts();
},[])
Load using this useEffect in your App.js file, once loaded the fonts can be used anywhere in your expo or react-native project
const Heading = (color) => {
return({fontSize:45*fontScale, fontFamily: "Montserrat-SemiBold", color, marginTop: -10, marginLeft: InitialMargin, letterSpacing: 4})
}
Make sure you are not using the style fontWeight, as it will override the fontStyle and will not apply the fontFamily to your Text.
Because 'startAsync' is depreacted from the component AppLoading,
we can use coustom fonts as bellow,
This code works fine.
I think <Apploading> causes no further instructions to be
performed until the font is loaded
===============================================
import * as Font from 'expo-font';
import AppLoading from 'expo-app-loading';
import React ,{useState}from 'react';
export default function App() {
const [fontLoading,setFontLoading]=useState(false);
Font.loadAsync( {
yekan:require("./myapp/fonts/byekan.ttf"),
ih:require("./myapp/fonts/ih.ttf")
}
).then( () => setFontLoading('true') )
if (!fontLoading){
return(
<AppLoading/>);
} else{
return(
do someting.....
............
)
}

React jest testing. Cannot read property 'maps' of undefined with google js api

Hi guys I've setup the Google Maps JavaScript API and its working all fine, but my tests all fail now with the error
TypeError: Cannot read property 'maps' of undefined.
Here is what my component looks like
import React, { Component } from 'react'
import { connect } from 'react-redux'
import List from './List'
import { fetchPlaces } from '../../store/actions/places'
const google = window.google
export class Places extends Component {
componentDidMount() {
const pyrmont = { lat: -33.866, lng: 151.196 };
const service = new google.maps.places.PlacesService(document.getElementById('map'))
// this.props.fetchPlaces('fitzroy')
const request = {
location: pyrmont,
radius: 500, type:
['restaurant'],
placeId: 'ChIJN1t_tDeuEmsRUsoyG83frY4',
fields: ['name', 'rating', 'formatted_phone_number', 'geometry']
};
service.nearbySearch(request, callback);
function callback(place, status) {
if (status == google.maps.places.PlacesServiceStatus.OK) {
console.log(place)
}
}
}
....rest of component
Here's what my test looks like
import React from 'react'
import { render, renderIntoDocument } from 'react-testing-library'
import 'jest-dom/extend-expect'
import { Places } from '../../Places/Places'
const baseProps = {
fetchPlaces: jest.fn(),
};
test('it shows a loading message when places are being loaded on mount', () => {
const { container } = render(<Places loading={true} places={[]} {...baseProps} />)
expect(container).toHaveTextContent('Loading')
});
First line of the error stack is
"at Places.componentDidMount (src/components/Places/Places.js:13:34)"
EDIT: I've setup a mock of the google api in my test file and if I console.log the google object its no longer undefined but I still get the same error in my test.
const setupGoogleMock = () => {
const google = {
maps: {
places: {
AutocompleteService: () => { },
PlacesServiceStatus: {
INVALID_REQUEST: 'INVALID_REQUEST',
NOT_FOUND: 'NOT_FOUND',
OK: 'OK',
OVER_QUERY_LIMIT: 'OVER_QUERY_LIMIT',
REQUEST_DENIED: 'REQUEST_DENIED',
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
ZERO_RESULTS: 'ZERO_RESULTS',
},
},
Geocoder: () => { },
GeocoderStatus: {
ERROR: 'ERROR',
INVALID_REQUEST: 'INVALID_REQUEST',
OK: 'OK',
OVER_QUERY_LIMIT: 'OVER_QUERY_LIMIT',
REQUEST_DENIED: 'REQUEST_DENIED',
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
ZERO_RESULTS: 'ZERO_RESULTS',
},
},
};
global.window.google = google;
};
beforeAll(() => {
setupGoogleMock();
});
I ran ran into a similar issue today, with an error of: TypeError: window.google.maps.places.AutocompleteService is not a constructor
I found a fix from CRA issue #955 - here
Basically you change the AutoCompleteService line to Autocomplete: class {}. Keep the rest of that mock file the same.
Although, the test that I have (which is now passing) is just the basic CRA initial test of 'it renders without crashing' but it should work out for you as well
Some modules might not work well inside a testing environment, or may not be as essential to the test itself. Mocking out these modules with dummy replacements can make it easier to write tests for your own code. Click Here For Detail
import MockedMap from "./map";
jest.mock("./map", () => {
return function DummyMap(props) {
return (
<div data-testid="map">
{props.center.lat}:{props.center.long}
</div>
);
};
});

Using Phaser together with React

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} />
)
}
}

Webpack theme loader

I'd like to accomplish the following structure:
button.core.jsx
button.theme-a.jsx
button.theme-b.jsx
To take React as an example, I'd like to do the following in button.core.jsx:
import React from 'react';
import Themed from './button.[theme]';
export default class Button extends React.Component {
render() {
if (Themed) {
return <Themed />;
}
return <button>default button</button>;
}
}
In other words, I want to define a theme in my webpack.config.js and load that file if it exists. If it does't, render the default behaviour. I think this would be a very powerful setup!
I've been searching around for making a custom loader, but no success yet. Can anyone point me in the right direction?
I've got this working with writing a custom "resolver":
const ThemeResolver = {
apply: function(resolver) {
resolver.plugin('file', function(req, cb) {
if (req.request.indexOf('[theme]') == -1) {
return cb();
}
const defaultFile = req.request.replace('[theme]', 'Default');
const themedFile = req.request.replace('[theme]', process.env.THEME);
req.request = themedFile;
this.doResolve(['file'], req, (err) => {
if (!err) {
return cb();
}
req.request = defaultFile;
this.doResolve(['file'], req, cb);
})
});
}
};
module.exports = {
// ...
plugins: [
new webpack.ResolverPlugin([ThemeResolver]),
]
// ...
};
It tries to resolve a file with [theme] in its path into a path with the theme defined as a environment variable. If it fails, it'll fallback to a default file instead. This way I can require a themed file like so:
import Presentation from './button-[theme]'
The main component turned out to be a bit different than in my question, but I'm actually pretty content with it:
import React from 'react';
import Presentation from './button-[theme]';
export default class Button extends React.Component {
onClick = (e) => console.log('some logic');
render() {
return <Presentation onClick={ this.onClick } />;
}
}
The logic of this button-component can live inside of button.core.jsx, while the presentation will be handled by one of these components:
THEME=theme-a npm start // button-[theme] resolves to button-theme-a.jsx
THEME=theme-c npm start // button-[theme] resolves to button-default.jsx
Disclaimer: I didn't use this in a large scale or production environment yet, but it seems to work in a small POC. Please let me know if I'm doing something unwise!

Resources