为画布游戏获得流畅的动画效果

时间:2015-01-31 20:30:26

标签: javascript html5 canvas requestanimationframe

对于具有不同硬件容量的不同设备,如何在浏览器繁忙或空闲的情况下获得更好的动画效果。

我尝试了很多方法,仍然无法找到让游戏显示更好动画的正确方法。

这是我试过的:

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则游戏动画效果不错,但浏览器繁忙时却不行,我不知道如何改变它的恐怖。

3 个答案:

答案 0 :(得分:3)

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

使用requestAnimationFrame(rAF)完成与屏幕的同步 当你写:

requestAnimationFrame( myCalbBack ) ;  

您正在注册myCalbBack以便一次,下一次可以使用该画面。
(如果你知道双缓冲(画布总是双缓冲),这次是下一次GPU将绘图缓冲区与显示缓冲区交换。)

另一方面,如果您不使用rAF但是使用间隔/超时来安排抽奖,那么将会发生的抽奖实际上不会显示直到下一次显示刷新。从现在到16.6毫秒之后的任何时间都可能发生(在60Hz显示屏上)。

下面有一个20毫秒的间隔和一个16毫秒的屏幕,你可以看到实际显示的图像可能是16毫秒距离或2 * 16毫秒距离 - 绝对不是20毫秒 - 肯定 - 。你不能从区间回调中知道实际抽签何时显示。由于rAF和Intervals都不是100%准确,你甚至可以有3帧增量。

enter image description here

现在,您与屏幕同步,这是一个坏消息:requestAnimationFrame不会完全定期打勾。由于各种原因,两帧之间的经过时间可能会改变1ms或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/

答案 1 :(得分:0)

您应该使用requestAnimationFrame代替setInterval

阅读this articlethis 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 ,因为它还处理我尚未进入的跨浏览器问题。

答案 2 :(得分:-1)

您应该使用requestAnimationFrame。它将排队回调以在下次浏览器呈现帧时运行。要实现持续更新,请递归调用更新函数。

var update = function(){

//做东西

requestAnimationFrame(更新)

}