画布动画:分离更新和渲染循环的好处?

时间:2015-10-01 13:58:36

标签: javascript canvas

我正在使用JavaScript和canvas元素创建一些简单的用户控制模拟。

目前我有一个单独的更新循环(使用setTimeout)和渲染循环(使用requestAnimationFrame)。

使用时间增量来缩放更新,因此在这种意义上,一致性并不重要。原因是我不希望渲染循环中的任何hick-up吞下用户输入或者使模拟响应性降低。

更新循环可能会以较低(但希望是固定的)帧速率运行。

这是JavaScript中的一个好习惯,还是有任何明显的陷阱?我希望update循环将获得优先权,但我对事件循环的理解可能有点过分了。 (在最坏的情况下,VM实现之间的行为不同。)

示例代码:

function update() {
  // Update simulation and process input
  setTimeout(update, 1000 / UPDATE_RATE);
}

function render() {
  // Render simulation onto canvas
  requestAnimationFrame(render);
}

function init() {
  update();
  render();
}

1 个答案:

答案 0 :(得分:-1)

Isaac Sukin在Game Development with Three.js中解决了这些问题。它涵盖了低渲染帧率的情况,这是该问题的主要关注点:

  

[...]在低帧率和高速度下,你的物体每帧都会移动很长的距离,这会导致它做一些奇怪的事情,比如穿过墙壁。

它还涵盖了相反的情况,具有高渲染帧速率和相对慢的物理计算:

  

在高帧速率下,计算物理可能需要比帧之间的时间长,这将导致应用程序冻结或崩溃。

此外,它还涉及决定论的概念,这在多人游戏中变得很重要,而游戏则依赖于它来重放或反作弊机制:

  

此外,我们希望完美再现。也就是说,每次我们使用相同的输入运行应用程序时,我们都希望输出完全相同。如果我们有可变的帧增量,那么即使在正常的帧速率下,由于累积的舍入误差,我们的输出也会分散程序运行的时间。

建议不要运行多个循环,因为这会严重且难以调试性能影响。取而代之的是采用在渲染循环中累积时间增量的方法,直到达到固定的预设大小,此时将其传递到物理循环进行处理:

  

更好的解决方案是将物理更新时间步骤与帧刷新时间步骤分开。物理引擎应该接收固定大小的时间增量,而渲染引擎应该确定每帧应该发生多少次物理更新。

以下是一些示例代码,显示了JavaScript中的最低实现:

var INVERSE_MAX_FPS = 1 / 60;
var frameDelta = 0;
var lastUpdate = Date.now();

function render() {
  // Update and render simulation onto canvas
  requestAnimationFrame(render);

  var now = Date.now();

  frameDelta += now - lastUpdate;
  lastUpdate = now;

  // Run as many physics updates as we have missed
  while(frameDelta >= INVERSE_MAX_FPS) {
    update();
    frameDelta -= INVERSE_MAX_FPS;
  }
}

function init() {
  render();
}

使用以下代码,无论自上次渲染帧后多长时间,都将处理所需的物理更新。任何剩余时间增量将被转移到下一帧。

请注意,目标最大FPS可能需要根据模拟运行的速度进行调整。