为 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/
如何动态地为具有不同硬件容量的不同设备获得更好的动画,即使浏览器繁忙或空闲时也是如此。
我试了很多方法,还是找不到合适的方法让游戏显示出更好的动画。
这是我试过的:
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/