在 requestAnimationFrame 游戏循环中跟踪时间/帧的最佳方法是啥?

Posted

技术标签:

【中文标题】在 requestAnimationFrame 游戏循环中跟踪时间/帧的最佳方法是啥?【英文标题】:What is the best way to track time/frames in a requestAnimationFrame game loop?在 requestAnimationFrame 游戏循环中跟踪时间/帧的最佳方法是什么? 【发布时间】:2020-02-25 22:34:14 【问题描述】:

我编写了非常简单的游戏教程,这些教程使用了一个简单的 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 Timestamp 和 performance.now,尽管我还不了解代码。如果我的目标是从 spritesheets 制作动画,并确保无论任何特定玩家达到多少 fps,游戏中的移动速度都相同,那么使用 requestAnimationFrame 进行游戏开发的最佳选择是哪一个?我读过你必须将游戏中的所有速度乘以时间因子,但不知道如何。否则,只有 30 fps 的慢速计算机在游戏中行走并以一半的速度射击子弹,而快速机器的速度约为 60 fps,对吧?

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

可能听起来我想要一个教程,请忽略原始问题中的以下部分

也很高兴看到您将如何使用该代码为精灵表中的行走或拍动翅膀等动画制作动画,以及如何将运动速度乘以时间因子,以便每个人都能获得相同的游戏体验。

【问题讨论】:

@gman 至少链接一个教程,该教程教授我所询问的时间/框架部分。我会更新我的问题。 【参考方案1】:

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

通常您会根据 deltaTime 移动事物。如果您的 deltaTime 以秒为单位,那么很容易根据 delta-per-second 进行任何操作。例如,每帧每秒移动 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>

【讨论】:

以上是关于在 requestAnimationFrame 游戏循环中跟踪时间/帧的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在循环开始时调用 requestAnimationFrame 不会导致无限递归?

requestAnimationFrame 方法你真的用对了吗?

IE 在 requestAnimationFrame 之前执行渲染

在 requestAnimationFrame 游戏循环中跟踪时间/帧的最佳方法是啥?

详解Web API requestAnimationFrame

如何在不同浏览器上解决requestAnimationFrame中不同的FPS?