使用 requestAnimationFrame 的 javascript 中一个简单的 30fps 游戏循环?

Posted

技术标签:

【中文标题】使用 requestAnimationFrame 的 javascript 中一个简单的 30fps 游戏循环?【英文标题】:A simple 30fps game loop in javascript using requestAnimationFrame? 【发布时间】:2017-02-03 07:49:21 【问题描述】:

我正在编写一个纸牌游戏,所以我需要使用 window.requestAnimationFrame 设置一个基本的游戏循环。我需要将 fps 设置为 30fps,因为它是一款慢节奏的纸牌游戏,我只需要一个简单可行的游戏循环。我的游戏循环如下。

function loop(timestamp) 
          var progress = timestamp - lastRender

          update(progress)
          draw()

          lastRender = timestamp
          window.requestAnimationFrame(loop)
        
        var lastRender = 0
        window.requestAnimationFrame(loop)

如何将 fps 设置为 30fps?我很感激任何帮助!谢谢!

【问题讨论】:

请看这个帖子:***.com/questions/19764018/… 为什么需要 30 fps? requestAnimationFrame 的诀窍在于,它总是为任何给定的显示器提供可能的最佳帧速率,因此限制为 30 fps 似乎很奇怪。 @EmilS.Jørgensen 好的,谢谢!事实证明,上面的代码很好。它在我的系统中提供 60 fps,我认为这对游戏来说很好。 【参考方案1】:

您应该将simulatingdrawing 分开。

下面是我每毫秒创建一个simulation 的示例(每秒 1000 次)。每次模拟都会取消上一次对动画帧的请求。

如果浏览器刷新已“勾选”,它将有 drawn 我们的更新(增加我们的计数器)。

1 秒后我停止,我们应该会看到您的显示器刷新率大致为我们的count

//counter
var count = 0;
//draw function
function draw() 
    count++;
  
  //Animation frame handle
var animationFramHandle;
//Run every millisecond
var interval = setInterval(function() 
  //Cancel requestAnimationFrame
  cancelAnimationFrame(animationFramHandle);
  //request new requestAnimationFrame
  animationFramHandle = requestAnimationFrame(draw);
, 1);
//Wait 1 second
setTimeout(function() 
  //Stop simulation
  clearInterval(interval);
  cancelAnimationFrame(animationFramHandle);
  console.log("ticks in a second:", count);
, 1000);

编辑 - 为什么要分离关注点

在画布上绘图是一项昂贵的操作,如果可以避免,您通常不会希望在每一帧上都运行它。

基本上只有在发生变化时才要求重绘。

在下面的示例中,我创建了大量的盒子,模拟它们并绘制它们:

//DOM elements
var canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 400;
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
ctx.fillStyle = "rgba(0,0,0,0.1)";
var drawNode = document.createElement("p");
document.body.appendChild(drawNode);
//Variables
var simulations = 0;
var simulationTime = 0;
var requestAnimationFrameHandle;
//Boxes to simulate and draw
var boxes = [];
while (boxes.length < 10000) 
    boxes.push(
        x: Math.random() * canvas.width,
        y: Math.random() * canvas.height,
        s: 5
    );

//Draw function
function draw() 
    var t = Date.now();
    //Clear
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    //Draw
    for (var bIndex = 0; bIndex < boxes.length; bIndex++) 
        var box = boxes[bIndex];
        ctx.fillRect(box.x, box.y, box.s, box.s);
    
    //Log
    drawNode.innerhtml = ("New draw after " + simulations + " simulations<br>Drawing took " + (Date.now() - t) + " ms<br>Simulation time is " + simulationTime + " ms");
    simulations = 0;

//Simulate function
function simulate(force) 
    if (force === void 0)  force = false; 
    simulations++;
    if (Math.random() * 1000 > 800) 
        var t = Date.now();
        for (var bIndex = 0; bIndex < boxes.length; bIndex++) 
            var box = boxes[bIndex];
            box.x = Math.abs(box.x + (Math.random() * 3 - 1)) % canvas.width;
            box.y = Math.abs(box.y + (Math.random() * 3 - 1)) % canvas.height;
        
        simulationTime = Date.now() - t;
        cancelAnimationFrame(requestAnimationFrameHandle);
        requestAnimationFrameHandle = requestAnimationFrame(draw);
    

setInterval(simulate, 1000 / 120);

请注意模拟它们比绘制它们要快得多。

根据经验,每帧之间只有 1000/60 ~ 16 毫秒,所以如果我们可以节省抽签所需的毫秒时间,那么我们很乐意这样做,并将这样的帧计算时间集中在更大的操作上(比如寻路或碰撞检测或任何在你的游戏中很重要的东西)。

通过这种方式,我们还可以以绘制速率以外的其他速率运行模拟,这在处理异步任务时非常方便。

【讨论】:

你能详细说明为什么模拟和绘图应该分开而不是像我上面的例子那样放在一起吗?谢谢!【参考方案2】:

FPS 代表每秒帧数。

1000 / 30 = 30 帧

试试这个基本功能:

function animate() 
  setTimeout(function() 
    requestAnimationFrame(animate);
  , 1000 / 30);

【讨论】:

以上是关于使用 requestAnimationFrame 的 javascript 中一个简单的 30fps 游戏循环?的主要内容,如果未能解决你的问题,请参考以下文章

requestAnimationFrame 使用小记

requestAnimationFrame简介

秀才提笔忘了字:javascript使用requestAnimationFrame实现动画

使用requestAnimationFrame做动画效果一

移动端滑屏全应用requestAnimationFrame的兼容与使用

在 debounce 函数中使用 requestAnimationFrame 是个好主意吗?