5
votes

Comment implémenter une gameloop avec requestAnimationFrame sur plusieurs composants React Redux?

J'ai du mal à trouver la meilleure façon de procéder. Je peux utiliser un appel récursif avec requestAnimationFrame pour avoir une boucle de jeu:

export interface Props {
    name: string;
    points: number;
    onIncrement?: () => void;
    onDecrement?: () => void;
}

class Hello extends React.Component<Props, object> {

    constructor(props: Props) {
        super(props);
    }

    render() {
        const { name, points, onIncrement, onDecrement } = this.props;

        return (
            <div className="hello">
                <div className="greeting">
                    Hello {name + points}
                </div>
                <button onClick={onDecrement}>-</button>
                <button onClick={onIncrement}>+</button>
            </div>
        );
    }

    componentDidMount() {
        this.tick();
    }

    tick = () => {
        this.props.onIncrement();
        requestAnimationFrame(this.tick)
    }

}

Mais que faire si je veux sur chaque image:

  • Composant1 à faire X
  • Composant2 à faire Y
  • Composant3 à faire Z

Je pourrais simplement avoir une autre boucle dans chaque composant, mais je crois comprendre que c'est une mauvaise pratique d'avoir plusieurs boucles requestAnimationFrame en cours et c'est un impact significatif sur les performances.

Je suis donc perdu ici. Comment puis-je faire en sorte qu'un autre composant utilise la même boucle? (Si c'est même la meilleure façon de procéder!)


0 commentaires

3 Réponses :


0
votes

Vous devez créer un composant parent qui exécute la boucle, puis le transmet aux autres composants, il devrait ressembler à ceci:

<Loop>
    <ComponentX loop={loop} />
    <ComponentY loop={loop} />
    <ComponentZ loop={loop} />
</Loop>


0 commentaires

1
votes

Une solution serait de définir un tableau tel que les callbacks dans le cadre de votre état. Au début du cycle de vie de chaque composant, ajoutez une fonction à ce tableau qui fait ce que vous voulez pour chaque boucle. Ensuite, appelez chaque fonction de votre boucle rAF comme ceci:

update( performance.now())

// Update loop
function update( timestamp ) {
    // Execute callback
    state.callbacks.forEach( cb => cb( ...args )) // Pass frame delta, etc.

    requestAnimationFrame( update )
  }

Avec un peu de travail, vous pouvez ajuster cet exemple simple pour fournir un moyen de supprimer des fonctions de callbacks pour autoriser la dynamique ajouter / soustraire des routines de la boucle de jeu par nom ou par signature.

Vous pouvez également passer un objet enveloppant la fonction qui contient également un entier que vous pouvez utiliser pour trier les rappels par priorité.


0 commentaires

6
votes

Vous avez besoin d'un composant parent qui appelle requestAnimationFrame et itère sur un tableau de refs des composants enfants qui doivent être mis à jour à chaque cycle, en appelant sa méthode update (ou comme vous voulez l'appeler) :

<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="app"></div>
body {
  margin: 0;
  padding: 16px;
}

.progressBarWrapper {
  position: relative;
  width: 100%;
  border: 3px solid black;
  height: 32px;
  box-sizing: border-box;
  margin-bottom: 16px;
}

.progressBarProgress {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
}
class ProgressBar extends React.Component {

  constructor(props) {
    super(props);
    
    this.state = {
      progress: 0,
    };
  }
  
  update() {
    this.setState((state) => ({
      progress: (state.progress + 0.5) % 100,
    }));
  }  

  render() {
    const { color } = this.props;
    const { progress } = this.state;
    
    const style = {
      background: color,
      width: `${ progress }%`,
    };
    
    return(
      <div className="progressBarWrapper">
        <div className="progressBarProgress" style={ style }></div>
      </div>
    );  
  }
}

class Main extends React.Component {

  constructor(props) {
    super(props);
    
    const progress1 = this.progress1 = React.createRef();
    const progress2 = this.progress2 = React.createRef();
    const progress3 = this.progress3 = React.createRef();
    
    this.componentsToUpdate = [progress1, progress2, progress3];
    this.animationID = null;    
  }
  
  componentDidMount() {  
    this.animationID = window.requestAnimationFrame(() => this.update());  
  }
  
  componentWillUnmount() {
    window.cancelAnimationFrame(this.animationID);
  }
  
  update() {
    this.componentsToUpdate.map(component => component.current.update());
  
    this.animationID = window.requestAnimationFrame(() => this.update());  
  }
  
  render() {
    return(
      <div>
        <ProgressBar ref={ this.progress1 } color="magenta" />
        <ProgressBar ref={ this.progress2 } color="blue" />     
        <ProgressBar ref={ this.progress3 } color="yellow" />       
      </div>
    );
  }
}

ReactDOM.render(<Main />, document.getElementById('app'));

Gardez à l'esprit, cependant, que si vous essayez de faire quelque chose de trop complexe et que vous voulez atteindre 60 ips , React n'est peut-être pas le bon outil à utiliser. p >

De plus, setState est asynchrone , donc lorsque vous l'appelez, vous ne faites que pousser une mise à jour dans une file d'attente que React traitera à un moment donné, ce qui pourrait en fait se produire dans l'image suivante ou plus tard.

J'ai profilé cet exemple simple à voir si c'était le cas et ce n'est pas le cas, en fait. Les mises à jour sont ajoutées à la file d'attente ( enqueueSetState ), mais le travail se fait tout de suite:

 Exemple de résultat de profilage d'application

Cependant, je soupçonne que dans une application réelle où React a plus de mises à jour à gérer ou dans les futures versions de React avec des fonctionnalités telles que le time slicing, les mises à jour asynchrones avec des priorités ... le rendu peut en fait se produire dans un cadre différent.


2 commentaires

Je suis d'accord, react ne convient pas au développement de jeux pour diverses raisons. La seule façon pour moi d'utiliser React dans le développement de jeux est d'envelopper mon jeu dans un seul composant qui ne rend qu'une seule annonce avec une seule requestAnimationFrame. React dans ce cas n'est utilisé que pour naviguer dans d'autres vues sans boucle de jeu.


@Danziger stackoverflow.com/questions/62653091/ … Pouvez-vous vérifier cette question?