bberak / react-game-engine

A lightweight Game Engine for the web (React) 🕹⚡🎮
MIT License
413 stars 24 forks source link

payload Undefined #2

Closed michaelcuneo closed 4 years ago

michaelcuneo commented 4 years ago

If I throw the example quickstart into a blank boilerplate, and run, I get

Systems.js?86e8:7 Uncaught TypeError: Cannot read property 'payload' of undefined at MoveFinger (Systems.js?86e8:7)

michaelcuneo commented 4 years ago

I've managed to fix this by checking to see if input exists before it creates the payload...

  let payload;

  if (input) {
    payload = input.find(x => x.name === 'onMouseDown');
  }
bberak commented 4 years ago

Hi @michaelcuneo,

Thanks for finding and debugging this. I'm a bit stumped, because input should always be truthy (an array, an empty array) - never undefined or null..

michaelcuneo commented 4 years ago

I am implementing into Max Stoibers react-boilerplate. I just deleted the HomePage, made a new index.js and threw it all inside there...

Logging the GameEngine shows that it's actually all configured and running.

On further investigation, the input is now undefined from scratch, before and after clicking.

michaelcuneo commented 4 years ago

index.js

import React from 'react';
import { Helmet } from 'react-helmet';

import { GameEngine } from 'react-game-engine';
import { Finger } from './Renderers';
import { MoveFinger } from './Systems';

class HomePage extends React.PureComponent {
  constructor() {
    super();

    this.GameEngineRef = React.createRef();
  }

  componentDidMount() {
    this.GameEngineRef.current.start();
  }

  componentWillUnmount() {
    this.GameEngineRef.current.stop();
  }

  render() {
    return [
      <Helmet key="Helmet">
        <title>Home Page</title>
        <meta
          name="description"
          content="A React.js Boilerplate application homepage"
        />
      </Helmet>,
      <GameEngine
        key="GameEngine"
        ref={this.GameEngineRef}
        systems={[MoveFinger]}
        entities={{
          finger1: { position: [20, 200], renderer: <Finger /> },
          /* finger2: { position: [40, 200], renderer: <Finger /> }, */
          /* finger3: { position: [60, 200], renderer: <Finger /> }, */
        }}
        running
      />,
    ];
  }
}

export default HomePage;

Renderers.js

import React from 'react';
import PropTypes from 'prop-types';

class Finger extends React.PureComponent {
  render() {
    console.log(this.props.position[0], this.props.position[1]);

    const x = this.props.position[0] + window.innerWidth / 2;
    const y = this.props.position[1] + window.innerHeight / 2;

    console.log(x, y);

    return (
      <div
        style={{
          position: 'absolute',
          width: 50,
          height: 50,
          backgroundColor: 'red',
          left: x,
          top: y,
          transformOrigin: '50% 50%',
        }}
      />
    );
  }
}

Finger.propTypes = {
  position: PropTypes.array,
};

export { Finger };

Systems.js

const MoveFinger = (entities, { input }) => {
  // -- I'm choosing to update the game state (entities) directly for the sake of brevity and simplicity.
  // -- There's nothing stopping you from treating the game state as immutable and returning a copy..
  // -- Example: return { ...entities, t.id: { UPDATED COMPONENTS }};
  // -- That said, it's probably worth considering performance implications in either case.

  let payload;

  if (input) {
    payload = input.find(x => x.name === 'onMouseDown');
  }

  if (payload) {
    const finger = entities.finger1;

    finger.position[0] = payload.pageX;
    finger.position[1] = payload.pageY;
  }

  return entities;
};

export { MoveFinger };
michaelcuneo commented 4 years ago

Actually I have modified the example a little, because I was unsure of what RADIUS was, because it's undefined in the example... I used the width and height of the screen halved instead... what's RADIUS meant to be?

bberak commented 4 years ago

RADIUS is a typo left over from the React Native examples. I still can't see why input would be undefined.

Can you try and remove the following and see if input is still undefined?

  constructor() {
    super();

    this.GameEngineRef = React.createRef();
  }

  componentDidMount() {
    this.GameEngineRef.current.start();
  }

  componentWillUnmount() {
    this.GameEngineRef.current.stop();
  }
michaelcuneo commented 4 years ago

I think I've somehow fixed it while troubleshooting, but didn't see that it had actually been fixed until I now log in System, and see that input is giving me data.

It appears to be working fine, but the objects don't move...

I believe I know what may have occurred, the original index.js was a stateless function() {} instead of a class extending React.PureComponent. I think the switch there may have fixed it partially.

Now to get the object moving. :) The data appears to be getting to the System, entities are returned,

As soon as I click anywhere, the Finger1 position becomes (undefined, undefined)

michaelcuneo commented 4 years ago

Found the issue... not 100% sure why, but payload has a tree inside called payload before getting to pageX... so I had to change the following two lines

    finger.position[0] = payload.payload.pageX;
    finger.position[1] = payload.payload.pageY;

I now get data back from entities, with coordinates for Finger1.

But still no movement of the actual entity.

michaelcuneo commented 4 years ago

O.k. turns out, finger is not allowed to be a PureComponent. Changing to Component got it working fine.

michaelcuneo commented 4 years ago

Here are my final solutions for making it work.

index.js

import React from 'react';
import { Helmet } from 'react-helmet';

import { GameEngine } from 'react-game-engine';
import Finger from './Renderers';
import { MoveFinger } from './Systems';

class HomePage extends React.PureComponent {
  render() {
    return [
      <Helmet key="Helmet">
        <title>Home Page</title>
        <meta
          name="description"
          content="A React.js Boilerplate application homepage"
        />
      </Helmet>,
      <GameEngine
        key="GameEngine"
        systems={[MoveFinger]}
        entities={{
          finger1: { position: [20, 200], renderer: <Finger /> },
          finger2: { position: [40, 200], renderer: <Finger /> },
          finger3: { position: [60, 200], renderer: <Finger /> },
        }}
      />,
    ];
  }
}

export default HomePage;

Renderers.js

import React from 'react';
import PropTypes from 'prop-types';

class Finger extends React.Component {
  render() {
    const x = this.props.position[0];
    const y = this.props.position[1];

    console.log(x, y);

    return (
      <div
        style={{
          position: 'absolute',
          width: 50,
          height: 50,
          backgroundColor: 'red',
          left: x,
          top: y,
        }}
      />
    );
  }
}

Finger.propTypes = {
  position: PropTypes.array,
};

export default Finger;

Systems.js

const MoveFinger = (entities, { input }) => {
  // -- I'm choosing to update the game state (entities) directly for the sake of brevity and simplicity.
  // -- There's nothing stopping you from treating the game state as immutable and returning a copy..
  // -- Example: return { ...entities, t.id: { UPDATED COMPONENTS }};
  // -- That said, it's probably worth considering performance implications in either case.

  const payload = input.find(x => x.name === 'onMouseDown');

  if (payload) {
    const finger = entities.finger1;

    finger.position[0] = payload.payload.pageX;
    finger.position[1] = payload.payload.pageY;
  }

  return entities;
};

export { MoveFinger };

Thanks for the help, can't wait to see what I can build with this.

bberak commented 4 years ago

Thanks for providing your updated code @michaelcuneo!

I'll see if I can update the docs ASAP - but I'll focus on your other issue first, that seems like a show stopper. Cheers.