bberak / react-game-engine

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

Multiple entities won't take a prop. #4

Closed michaelcuneo closed 4 years ago

michaelcuneo commented 4 years ago

This is more of a Usage Question, than a bug with the code or anything... but I've set up a simple system where I'd like three balls on the screen. I've styled them up as radial-gradients to look just like a sphere. one Red, one Green, and one Blue. I think I'm doing something backwards here, but I'm not sure...

This is my setup.

Entities.js

import Sphere from './Components/Sphere';
import HUD from './Components/Hud';

export default async () => {
  const redSphere = await Sphere({ colour: 'red' });
  const blueSphere = await Sphere({ colour: 'blue' });
  const greenSphere = await Sphere({ colour: 'green' });
  const hud = HUD();

  const entities = {
    redSphere,
    blueSphere,
    greenSphere,
    hud,
  };

  return entities;
};

SphereComponent

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

import Ball from './Ball';

class SphereRenderer extends React.Component {
  shouldComponentUpdate(nextProps) {
    const m1 = this.props.mouseController || {};
    const m2 = nextProps.mouseController || {};

    return (
      m1.left !== m2.left ||
      m1.position.x !== m2.position.x ||
      m1.position.y !== m2.position.y
    );
  }

  render() {
    console.log(this.props);
    const { left, position = { x: 0, y: 0 } } =
      this.props.mouseController || {};

    const { colour } = this.props;

    return <Ball position={position} colour={colour} />;
  }
}

SphereRenderer.propTypes = {
  mouseController: PropTypes.object,
  colour: PropTypes.string,
};

export default () => ({ renderer: <SphereRenderer /> });

Ball.js

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

const Ball = ({ position, colour }) => {
  if (colour === 'blue') {
    // Change all the gradient colours to suit a Blue Ball.
  }

  if (colour === 'red') {
    // Change all the gradient colours to suit a Red Ball.
  }

  if (colour === 'green') {
    // Change all the gradient colours to suit a Green Ball.
  }

  const StyledBall = styled.div`
    position: absolute;
    display: block;
    border-radius: 50%;
    height: 100px;
    width: 100px;
    margin: 0;
    background: radial-gradient(circle at 25% 25%, #5cabff, #000);
    left: ${position.x}
    right: ${position.y}
    &::before: {
      content: "";
      position: absolute;
      background: radial-gradient(circle at 50% 120%, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0) 70%);
      border-radius: 50%;
      bottom: 2.5%;
      left: 5%;
      opacity: 0.6;
      height: 100%;
      width: 90%;
      filter: blur(5px);
      z-index: 2;    
    }
    &::after {
      content: "";
      width: 100%;
      height: 100%;
      position: absolute;
      top: 5%;
      left: 10%;
      border-radius: 50%;
      background: radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.8) 14%, rgba(255, 255, 255, 0) 24%);
      transform: translateX(-80px) translateY(-90px) skewX(-20deg);
      filter: blur(10px);
    }
  `;

  return <StyledBall />;
};

Ball.propTypes = {
  position: PropTypes.object,
  colour: PropTypes.string,
};

export default Ball;

So theoretically, I have made 3 balls. The 3 balls do appear on the screen as I expected, but for some reason, Colour is undefined in the log at Components/Sphere.js

Based on the entities system and components from the react-game-engine-template, this should provide the colour assigned in entities, to each component?

bberak commented 4 years ago

I think your SphereComponent isn't handling the color argument. Try this:

export default ({ colour }) => ({ colour, renderer: <SphereRenderer /> });

//-- OR

export default (args) => ({ ...args, renderer: <SphereRenderer /> });

Also, I noticed that you are consuming the mouseController from the renderer. I'm assuming this is working because you have written a system that injects the mouseController into your sphere entities.. Just a heads up that the mouseController object will be recreated on each frame, which means that your entities will be rendered on each frame (hence your use of shouldComponentUpdate as an optimization).

If you make SphereRenderer a PureComponent, they will only be re-rendered when a prop changes (eg, position or colour). This saves you having to do the optimizations yourself. This does mean that will need to encapsulate your movement code into a new system - but I think this is preferrable once you get into the habit of re-using systems for other entities. Just my two cents :)

michaelcuneo commented 4 years ago

Ahh yes, I forgot to add my Sphere.js system to the previous post... this is where it's pushing mouseController and colour into the entities... but not colour...

const Sphere = (entities, args) => {
  const { redSphere, greenSphere, blueSphere } = entities;

  if (redSphere) {
    redSphere.mouseController = args.mouseController;
    redSphere.colour = args.colour;
  }

  if (greenSphere) {
    greenSphere.mouseController = args.mouseController;
    greenSphere.colour = args.colour;
  }

  if (blueSphere) {
    blueSphere.mouseController = args.mouseController;
    blueSphere.colour = args.colour;
  }

  return entities;
};

export default Sphere;
michaelcuneo commented 4 years ago

Solved it... I was looking at it all wrong. The args.colour does not exist, and won't exist inside the Sphere System.

Replaced args.colour with an actual string and it's working fine now.

Ball is receiving colour then changing the colour of the .CSS based on the string. :)

For the movement side of things, I was placing the movement checks in shouldComponentUpdate, so that I can change position based on a click, then measurement around the object... unless there's a better way to go about it within the engine itself... my plan was 'If left mouse button true, perform function to determine if the left mouse button was clicked inside the ball's circumference, if it was, allow it to be dragged around.

bberak commented 4 years ago

Oh I see..

This can be performed within the scope of the engine because I think the mouseController arg should have a previous property on it. Therefore you won't need to do a comparison in shouldComponentUpdate to determine if there was a click. Eg:

const DragSystem = (entities, { mouseController }) => {

   const mouseDown = mouseController.left && !mouseController.previous.left;

   if (mouseDown) {
      const sphere = getSphearBaseOnPosition(entities, mouseController.position);

      //-- Perform drag logic here

      sphere.position = mouseController.position;
   }  

   return entities;
};

export default DragSystem;

But don't let me distract you - sounds like you've got it working :)

michaelcuneo commented 4 years ago

It appears to work fine now, but the 2D movement chunks along quite slowly giving me many 'requestAnimationFrame' handler violations... it seems to be a bit too chunky moving around highly stylised CSS DIV's in this... I think I'll need to move it all over to Three.JS based on the original template example.

michaelcuneo commented 4 years ago

I forgot that mouseController was a whole system on it's own that had functions that I could bring in a drag system to, seperate from the spheres themselves... that would make it way more functional. All of my lag, might just be the shouldComponentUpdate bloat that I have.

bberak commented 4 years ago

Hi @michaelcuneo, yeah i would dig into this a bit further before moving to ThreeJS - your use case doesn't sound overly intensive (but perhaps I'm wrong). Let me know if I can help - it would be could to get a batter understanding of the inherent limitations of the engine when the entities are rendered to the DOM.

A quick way I debug these problems is to time how long it takes each system to complete - that allows me to see if the logic in my systems is causing the lag.

michaelcuneo commented 4 years ago

Yeah, the whole idea for this is basically to have 3 balls on the screen, and if I click on a ball, I have the option to either move it or enlarge it... it's not much more complicated than that... it's for a research project...

I've narrowed down the lag issues, it wasn't the react-particle-js, layer, or the ball layer itself... but the ::before and ::after with filters on it, For some reason css filter property really slows everything down badly... as soon as I commented out those two ::before and ::afters, everything went back to being nice and smooth.

So the option I have here I guess, is to just remove the StyledBall, with it's 38 Lines of Style... and just use an illustrated vector. Problem solved I think. That'll speed things up.

michaelcuneo commented 4 years ago

For future reference... Vectors made it twice as slow... really really slow. Gone with exported .PNG's

michaelcuneo commented 4 years ago

So far, so good. using .PNG's gave me all the speed back. Now to hook it all up to drag and resize Systems. https://haldor.michaelcuneo.com.au/ThreeCircles.png

bberak commented 4 years ago

Good to know regarding Vectors - I wouldn't have seen that one coming to be honest.

michaelcuneo commented 4 years ago

I think the .SVG's were so slow because I revolved a 2D plane using the 3D tool in Illustrator to make it look like a real sphere. It had way too many point. I think if I had simplified the points, to just the 4 needed to show a circle, it might have been way faster. Detailed vectors are the issue.