Javascript 我应该如何并行使用requestAnimationFrame和setTimeout来创建更好的游戏循环?

Javascript 我应该如何并行使用requestAnimationFrame和setTimeout来创建更好的游戏循环?,javascript,html,settimeout,requestanimationframe,game-loop,Javascript,Html,Settimeout,Requestanimationframe,Game Loop,我的目标是创建一个高效的游戏循环,使用requestAnimationFrame更新显示画布,使用setTimeout更新游戏逻辑。我的问题是我应该把所有的绘图操作放在requestAnimationFrame循环中,还是只放在更新html画布的主绘图操作中 我所说的“所有绘图操作”是指所有的缓冲。例如,我会将所有精灵绘制到缓冲区,然后将缓冲区绘制到主画布。一方面,如果我将所有缓冲放入requestAnimationFrame中,我不会在每次逻辑更新时浪费cpu绘图,另一方面,绘图占用大量cpu

我的目标是创建一个高效的游戏循环,使用
requestAnimationFrame
更新显示画布,使用
setTimeout
更新游戏逻辑。我的问题是我应该把所有的绘图操作放在
requestAnimationFrame
循环中,还是只放在更新html画布的主绘图操作中

我所说的“所有绘图操作”是指所有的缓冲。例如,我会将所有精灵绘制到缓冲区,然后将缓冲区绘制到主画布。一方面,如果我将所有缓冲放入
requestAnimationFrame
中,我不会在每次逻辑更新时浪费cpu绘图,另一方面,绘图占用大量cpu,可能会导致
requestAniomationFrame
等待所有这些操作完成。。。将逻辑更新与绘图分离的目的是使
requestAnimationFrame
不会因非绘图处理而陷入困境

有没有人有过这种创建游戏循环的经验?不要说“只需将其全部放入
requestAnimationFrame
”,因为这会降低渲染速度。我相信将逻辑与绘图分离是一条可行的道路。下面是我所说的一个例子:

/* The drawing loop. */
function render(time_stamp_){//First parameter of RAF callback is timestamp.
    window.requestAnimationFrame(render);

    /* Draw all my sprites in the render function? */
    /* Or should I move this to the logic loop? */
    for (var i=sprites.length-1;i>-1;i--){
        sprites[i].drawTo(buffer);
    }

    /* Update the on screen canvas. */
    display.drawImage(buffer.canvas,0,0,100,100,0,0,100,100);
}

/* The logic loop. */
function update(){
    window.setTimeout(update,20);

    /* Update all my sprites. */
    for (var i=sprites.length-1;i>-1;i--){
        sprites[i].update();
    }
}
谢谢

编辑:


我决定与web workers一起将游戏逻辑与绘图完全分离,据我所知,必须在DOM加载的主脚本中进行

  • 我想尽可能经常刷新屏幕
  • 有一些昂贵的操作,我不想在每次屏幕刷新时都这样做。当然,这意味着还有其他东西需要刷新,如果没有,前面的一点是无用的
  • 我没有,也不能有一个标志,指示需要以前的操作。请注意,这是一种明智的方法,其他选项只是在不可能的情况下的替代选择
  • 在代码中,您决定每秒执行20次此操作

    在本例中,我将设置一个时间戳,指示此操作何时完成


    在requestAnimationFrame代码中,测试此时间戳是否已过期超过1/20秒,然后执行代码。

    因此,我从未找到一种分离逻辑和绘图的好方法,因为JavaScript使用单个线程。无论我做什么,draw函数的执行可能会妨碍逻辑,反之亦然。我所做的是找到一种方法,尽可能及时地执行它们,同时确保使用requestAnimation Frame对逻辑和优化绘图进行恒定时间更新。该系统设置为在设备速度太慢而无法以所需帧速率绘制时插入动画以弥补跳过的帧。不管怎样,这是我的代码

    var engine = {
            /* FUNCTIONS. */
            /* Starts the engine. */
            /* interval_ is the number of milliseconds to wait between updating the logic. */
            start : function(interval_) {
                /* The accumulated_time is how much time has passed between the last logic update and the most recent call to render. */
                var accumulated_time = interval_;
                /* The current time is the current time of the most recent call to render. */
                var current_time = undefined;
                /* The amount of time between the second most recent call to render and the most recent call to render. */
                var elapsed_time = undefined;
                /* You need a reference to this in order to keep track of timeout and requestAnimationFrame ids inside the loop. */
                var handle = this;
                /* The last time render was called, as in the time that the second most recent call to render was made. */
                var last_time = Date.now();
    
                /* Here are the functions to be looped. */
                /* They loop by setting up callbacks to themselves inside their own execution, thus creating a string of endless callbacks unless intentionally stopped. */
                /* Each function is defined and called immediately using those fancy parenthesis. This keeps the functions totally private. Any scope above them won't know they exist! */
                /* You want to call the logic function first so the drawing function will have something to work with. */
                (function logic() {
                    /* Set up the next callback to logic to perpetuate the loop! */
                    handle.timeout = window.setTimeout(logic, interval_);
    
                    /* This is all pretty much just used to add onto the accumulated time since the last update. */
                    current_time = Date.now();
                    /* Really, I don't even need an elapsed time variable. I could just add the computation right onto accumulated time and save some allocation. */
                    elapsed_time = current_time - last_time;
                    last_time = current_time;
    
                    accumulated_time += elapsed_time;
    
                    /* Now you want to update once for every time interval_ can fit into accumulated_time. */
                    while (accumulated_time >= interval_) {
                        /* Update the logic!!!!!!!!!!!!!!!! */
                        red_square.update();
    
                        accumulated_time -= interval_;
                    }
                })();
    
                /* The reason for keeping the logic and drawing loops separate even though they're executing in the same thread asynchronously is because of the nature of timer based updates in an asynchronously updating environment. */
                /* You don't want to waste any time when it comes to updating; any "naps" taken by the processor should be at the very end of a cycle after everything has already been processed. */
                /* So, say your logic is wrapped in your RAF loop: it's only going to run whenever RAF says it's ready to draw. */
                /* If you want your logic to run as consistently as possible on a set interval, it's best to keep it separate, because even if it has to wait for the RAF or input events to be processed, it still might naturally happen before or after those events, and we don't want to force it to occur at an earlier or later time if we don't have to. */
                /* Ultimately, keeping these separate will allow them to execute in a more efficient manner rather than waiting when they don't have to. */
                /* And since logic is way faster to update than drawing, drawing won't have to wait that long for updates to finish, should they happen before RAF. */
    
                /* time_stamp_ is an argument accepted by the callback function of RAF. It records a high resolution time stamp of when the function was first executed. */
                (function render(time_stamp_) {
                    /* Set up the next callback to RAF to perpetuate the loop! */
                    handle.animation_frame = window.requestAnimationFrame(render);
    
                    /* You don't want to render if your accumulated time is greater than interval_. */
                    /* This is dropping a frame when your refresh rate is faster than your logic can update. */
                    /* But it's dropped for a good reason. If interval > accumulated_time, then no new updates have occurred recently, so you'd just be redrawing the same old scene, anyway. */
                    if (accumulated_time < interval_) {
                        buffer.clearRect(0, 0, buffer.canvas.width, buffer.canvas.height);
    
                        /* accumulated_time/interval_ is the time step. */
                        /* It should always be less than 1. */
                        red_square.draw(accumulated_time / interval_);
    
                        html.output.innerHTML = "Number of warps: " + red_square.number_of_warps;
    
                        /* Always do this last. */
                        /* This updates the actual display canvas. */
                        display.clearRect(0, 0, display.canvas.width, display.canvas.height);
                        display.drawImage(buffer.canvas, 0, 0, buffer.canvas.width, buffer.canvas.height, 0, 0, display.canvas.width, display.canvas.height);
                    }
                })();
            },
            /* Stops the engine by killing the timeout and the RAF. */
            stop : function() {
                window.cancelAnimationFrame(this.animation_frame);
                window.clearTimeout(this.timeout);
                this.animation_frame = this.timeout = undefined;
            },
            /* VARIABLES. */
            animation_frame : undefined,
            timeout : undefined
        };
    
    var引擎={
    /*功能*/
    /*启动引擎*/
    /*interval_u是更新逻辑之间等待的毫秒数*/
    启动:功能(间隔时间){
    /*累计的_时间是从上次逻辑更新到最近调用render之间经过的时间*/
    var累计时间=间隔时间;
    /*current time是最近一次调用渲染的当前时间*/
    var电流_时间=未定义;
    /*最近第二次调用渲染与最近一次调用渲染之间的时间量*/
    var运行时间=未定义;
    /*为了跟踪循环中的超时和requestAnimationFrame ID,您需要对此进行引用*/
    var handle=this;
    /*上次调用render时,如最近第二次调用render时*/
    var last_time=Date.now();
    /*下面是要循环的函数*/
    /*它们通过在自己的执行中设置对自己的回调来循环,从而创建一个无休止的回调字符串,除非有意停止*/
    /*每一个函数都是用这些别致的括号定义和调用的。这使得函数完全是私有的。它们上面的任何作用域都不会知道它们的存在*/
    /*您希望首先调用逻辑函数,以便绘图函数可以使用*/
    (函数逻辑){
    /*设置下一个对logic的回调以使循环永久化*/
    handle.timeout=window.setTimeout(逻辑,间隔u1;);
    /*这几乎只是用来增加自上次更新以来的累计时间*/
    当前时间=日期。现在();
    /*实际上,我甚至不需要经过时间变量。我可以将计算直接添加到累积时间上,并节省一些分配*/
    已用时间=当前时间-上次时间;
    上次时间=当前时间;
    累计_时间+=经过的_时间;
    /*现在,您希望为每个时间间隔更新一次,该时间间隔可以适应累计时间*/
    而(累计时间>=间隔时间){
    /*更新逻辑*/
    red_square.update();
    累计时间-=间隔时间;
    }
    })();
    /*即使逻辑循环和绘图循环在同一线程中异步执行,它们也保持分开的原因在于异步更新环境中基于计时器的更新的性质*/
    /*在进行更新时,您不想浪费任何时间;处理器采取的任何“小睡”都应该是在处理完所有内容后的一个周期的最后*/
    /*所以,假设你的逻辑被包裹在你的RAF循环中:它只会在RAF说它是正确的时候运行