在 requestAnimationFrame 游戏循环中跟踪 time/frames 的最佳方法是什么?

What is the best way to track time/frames in a requestAnimationFrame game loop?

我编写了非常简单的游戏教程,这些教程使用了一个简单的 requestAnimationFrame 游戏循环。他们不需要跟踪经过的时间或帧速率:

var canvas = document.querySelector("#canvas");
var ctx = canvas.getContext("2d");

function gameLoop() {
  ctx.clearRect(0,0,canvas.width,canvas.height);
  //Drawing, etc.
  var myRAF = requestAnimationFrame(gameLoop);
}
gameLoop();

现在我想学习如何为 spritesheet 中的步行循环等内容制作动画,而不仅仅是为静态对象的运动制作动画。我认为这需要首先学习如何跟踪渲染帧所花费的时间,或者您所在的帧。我的印象是,如果您使用 RAF 而不是 setInterval 或 setTimeout(糟糕!),则没有必要这样做。我见过人们使用 Date 对象、requestAnimationFrame 时间戳和 performance.now,但我还不理解代码。如果我的目标是从 spritesheet 制作动画,并确保游戏中的运动速度相同,无论任何特定玩家达到多少 fps,那么使用 requestAnimationFrame 进行游戏开发的最佳选择是什么?我读到你必须将游戏中的所有速度乘以时间因子,但不知道如何。否则,一台速度只有 30fps 的慢速计算机正在以一半的速度在游戏中行走,而速度较快的计算机速度只有 60fps 左右,对吗?

请告诉我如何在游戏循环中实现 time/frame 跟踪代码,以及实现此目的的不同方法的优缺点。

冒着听起来像我想要教程的风险,请忽略原始问题中的以下部分

也很高兴看到您如何使用该代码从 spritesheet 中制作诸如行走或拍打翅膀之类的动画,以及如何将移动速度乘以时间因子以便每个人都获得相同的游戏体验。

requestAnimationFrame 接受回调。该回调传递自页面启动以来的时间(以毫秒为单位)。因此,您可以使用该时间从上一个 requestAnimationFrame 回调的时间中减去它来计算您的帧速率并用于 "deltaTime" 以类似速度乘以其他值。

通常你会根据 deltaTime 来移动东西。如果您的 deltaTime 以秒为单位,那么很容易根据每秒增量制作任何东西。例如,每帧每秒移动 10 个单位,您可以执行类似

的操作
const unitsPerSecond = 10;
x = x + unitsPerSecond * deltaTimeInSeconds

至于帧数,您只需保留自己的计数器即可

const ctx = document.querySelector('canvas').getContext('2d');

let x = 0;
let y = 0;
const speed = 120;  // 120 units per second

let frameNumber = 0;
let previousTime = 0;
function render(currentTime) {
  // keep track of frames
  ++frameNumber;
  
  // convert time to seconds
  currentTime *= 0.001;
  
  // compute how much time passed since the last frame
  const deltaTime = currentTime - previousTime;
  
  // remember the current time for next frame
  previousTime = currentTime;
  
  // move some object frame rate independently
  x += speed * deltaTime;
  y += speed * deltaTime;
  
  // keep x and y on screen
  x = x % ctx.canvas.width;
  y = y % ctx.canvas.height;
  
  // clear the canvas
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  
  // draw something
  ctx.fillStyle = (frameNumber & 0x1) ? 'red' : 'blue'; // change color based on frame
  ctx.fillRect(x - 5, y - 5, 11, 11);
  
  // draw something else based on time
  ctx.fillStyle = 'green';
  ctx.fillRect(
     145 + Math.cos(currentTime) * 50, 
     75 + Math.sin(currentTime) * 50,
     10, 10);
  
  requestAnimationFrame(render);
}
requestAnimationFrame(render);
canvas { border: 1px solid black; }
<canvas></canvas>