【问题标题】:How to implement a gameloop with requestAnimationFrame across multiple React Redux components?如何跨多个 React Redux 组件使用 requestAnimationFrame 实现游戏循环?
【发布时间】:2019-06-01 16:05:13
【问题描述】:

努力想出最好的解决方法。我可以使用带有requestAnimationFrame 的递归调用来进行游戏循环:

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

}

但是如果我想要在每一帧上:

  • Component1 做 X
  • Component2要做Y
  • Component3 做 Z

我可以在每个组件中设置另一个循环,但我的理解是,让多个 requestAnimationFrame 循环运行是一种不好的做法,而且会严重影响性能。

所以我在这里迷路了。如何让另一个组件使用相同的循环? (如果这甚至是最好的方法!)

【问题讨论】:

    标签: javascript reactjs typescript redux react-redux


    【解决方案1】:

    您需要一个调用requestAnimationFrame 的父组件,并遍历需要在每个周期更新的子组件的refs 数组,调用它的update(或者你想调用它)方法:

    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'));
    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%;
    }
    <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>

    但请记住,如果您尝试做一些过于复杂的事情并想点击60 fps,那么 React 可能不是正确使用的工具。

    另外,setState is asynchronous,所以调用它时,您只是将更新推送到 React 将在某个时间点处理的队列,这实际上可能发生在下一帧或更晚。

    我分析了这个简单的示例,看看是否是这种情况,实际上并非如此。更新已添加到队列 (enqueueSetState),但工作立即完成:

    但是,我怀疑在 React 需要处理更多更新的实际应用程序中,或者在具有时间切片、具有优先级的异步更新等功能的 React 的未来版本中......渲染实际上可能发生在不同的帧中。

    【讨论】:

    • 我同意,由于各种原因,React 不适合游戏开发。我可以在游戏开发中使用 React 的唯一方法是将我的游戏包装在单个组件中,该组件仅使用单个 requestAnimationFrame 呈现一次广告。在这种情况下,React 仅用于在没有游戏循环的其他视图中导航。
    • @Danziger stackoverflow.com/questions/62653091/…你能检查一下这个问题吗?
    【解决方案2】:

    一种解决方案是定义一个数组,例如 callbacks 作为您的状态的一部分。在每个组件的生命周期开始时,向此数组添加一个函数,该函数在每个循环中执行您想要的操作。然后像这样调用 rAF 循环中的每个函数:

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

    通过一些工作,您可以调整这个简单的示例,以提供一种从callbacks 中删除函数的方法,从而允许通过名称或签名从游戏循环中动态添加/减去例程。

    您还可以传递一个包装函数的对象,该函数还包含一个整数,您可以使用该整数按优先级对回调进行排序。

    【讨论】:

      【解决方案3】:

      您应该创建一个正在运行循环的父组件,然后将其传递给其他组件,它应该如下所示:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-05
        • 2016-08-06
        • 2019-07-25
        相关资源
        最近更新 更多