为 canvas 游戏制作流畅的动画

get a smooth animation for a canvas game

如何动态地为具有不同硬件容量的不同设备获得更好的动画,即使浏览器繁忙或空闲时也是如此。

我试了很多方法,还是找不到合适的方法让游戏显示出更好的动画。

这是我试过的:

var now;
var then = Date.now();
var delta;

window.gamedraw = function(){

   now = Date.now();
   delta = now - then;

   if(delta > 18){
        then = now - (delta % 18);
        game_update();
   }

}

window.gameloop = setInterval(window.gamedraw,1);

18是更新游戏的间隔值,但是当浏览器繁忙时这个间隔不好,需要降低。如何动态地获得更好的动画,即使在浏览器空闲或忙碌时?

我想是间隔值的问题,因为如果间隔值较低则游戏动画非常快,如果该值是 18 则游戏动画很好但当浏览器繁忙时则不是,我不知道如何动态改变它。

您应该使用 requestAnimationFrame 而不是 setInterval

阅读this article or this one

后一个其实就是在讨论你的方法(有增量时间),然后介绍动画帧作为更可靠的替代方案。

第一篇文章真是一个很好的资源。对于初学者,您的间隔为 18 毫秒,显然您的目标是接近 60 fps。这实际上是 requestAnimationFrame 的默认设置,因此您无需编写任何特殊内容:

function gamedraw() {
  requestAnimationFrame(gamedraw); //self-reference
  game_update(); //your update logic, propably needs to handle time intervals internally
}

gamedraw(); //this starts the animation

如果您想明确设置更新间隔,可以通过将 requestAnimationFrame 包装在 setInterval 中来实现,如下所示:

var interval = 18;
function gamedraw() {
  setTimeout(function() {
    requestAnimationFrame(gamedraw);
    game_update(); //must handle time difference internally
  }, interval);
}
gamedraw();

请注意,game_update() 函数必须跟踪上次调用它的时间,例如,将所有内容都移动两倍,以防必须跳过一帧。

实际上,这意味着您可以(并且可能应该)重构您的 game_update() 函数,以占用 实际上 作为参数传递的时间,而不是在内部确定. (没有功能上的区别,它只是更好、更清晰的代码 IMO,因为它没有隐藏计时魔法。)

var time;
function gamedraw() {
  requestAnimationFrame(gamedraw);
  var now = new Date().getTime(),
      dt = now - (time || now);
  time = now; //reset the timer
  game_update(dt); //update with explicit and variable time step
}
gamedraw();

(这里我又丢掉了显式帧。)

不过,我还是建议你 read the first article 因为它还处理了我在这里没有涉及的跨浏览器问题。

你应该使用 requestAnimationFrame。它将在浏览器下次呈现帧时排队到 运行 的回调。实现不断更新,递归调用update函数

var update = function(){

//做事

requestAnimationFrame(更新)

}

要获得流畅的动画,您必须:
• 在屏幕上同步。
• 计算游戏中经过的时间。
• 仅使用此游戏时间制作动画。

使用 requestAnimationFrame (rAF) 与屏幕同步。
当你写:

requestAnimationFrame( myCalbBack ) ;  

您正在注册myCalbBack被调用一次,下次可以在屏幕上绘制时。
(如果你知道双缓冲(canvas 总是 double-buffered),这次是 GPU 下一次将绘图缓冲区与显示缓冲区交换。)

另一方面,如果您不使用 rAF 而是使用 interval/timeout 来安排抽奖,将会发生的情况是抽奖不会真正显示 直到下一次显示刷新。从现在到 16.6 毫秒后的任何时间都可能发生(在 60Hz 显示器上)。

下面有一个 20 毫秒的间隔和一个 16 毫秒的屏幕,您可以看到实际显示的图像将在 16 毫秒或 2 * 16 毫秒之外 - 绝对不是 20 -。您只是无法从间隔回调中知道实际抽奖何时显示。由于 rAF 和间隔都不是 100% 准确,您甚至可以有 3 帧增量。

现在您已经与屏幕同步了,这里有一个坏消息:requestAnimationFrame 没有完全规律地跳动。由于各种原因,两帧之间经过的时间可能会变化 1 毫秒或 2 毫秒,甚至更多。因此,如果您每帧使用固定移动,您将在不同的时间移动相同的距离:速度总是在变化。

(解释:每个 rAF +10 px,
16.6 显示 -->> rAF 时间为 14、15、16 或 17 毫秒
--> 表观速度从 0.58 到 0.71 px/ms。 )

答案是测量时间...并使用它!
希望 requestAnimationFrame 为您提供当前时间,这样您甚至不必使用 Date.now()。第二个好处是,这个时间在具有准确计时器(Chrome 桌面)的浏览器上会非常准确。
下面的代码显示了如何知道自上一帧以来经过的时间,并计算应用程序时间:

function animate(time) {
  // register to be called again on next frame.
  requestAnimationFrame(animate);
  // compute time elapsed since last frame.
  var dt = time-lastTime;
  if (dt<10) return;
  if (dt >100) dt=16;  // consider only 1 frame elapsed if unfocused.
  lastTime=time;
  applicationTime+=dt;
  //
  update(dt);  
  draw();
}

var lastTime = 0;
var applicationTime = 0;
requestAnimationFrame(animate);

现在最后一步是始终在所有公式中使用时间。所以与其做

x += xSpeed ;

你必须做:

x += dt * xSpeed ;

现在不仅会考虑帧之间的微小变化,而且无论设备的屏幕(20Hz、50Hz、60Hz,... ).

我做了一个小演示,您可以在其中选择同步,如果使用固定时间,您将能够判断差异:

http://jsbin.com/wesaremune/1/