4
\$\begingroup\$

It's a common occurrence nowadays to have different refresh rates across different devices. That's why it's very important to keep track of the device's frame rate to keep the game time consistent across different refresh rates.

Is this an efficient way to go about this?

var timeBetween;
var timeLast;

function gameLoop() {
  var timeNow = window.performance.now();
  timeBetween = timeNow - timeLast;
  timeLast = timeNow;

  gameLogic(timeBetween); // Send time to game logic to calculate e.g. game speed etc.
  gameDraw();

  requestAnimationFrame(gameLoop);
}
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

One thing I recommend is to use a "scale factor" (I'm making names, there might be an official name for this approach) - a unit-less value that you multiply to time-sensitive values to compensate for frame rate differences. One perk when doing it this way is you don't have to write time sorcery in your equations (which is everywhere usually). You write them as is and just multiply this value to the result.

The formula is scale = delta / 16.6 where 16.6 is the frame time for 60fps. But this may differ per device, so you might need to find out the device refresh rate first and adjust this accordingly. According to MDN:

The number of callbacks is usually 60 times per second, but will generally match the display refresh rate in most web browsers as per W3C recommendation. https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame

Here's an example calculation with a box that should move 60 units at 60fps. At 60fps, it moves an intended 60 units. But if the frame rate drops to 40fps, it should not move 40 units. Scale factor compensates for that by scaling up the calculated value.

// At 60fps, a frame takes 16.6ms
const delta = 16.6;
const scale = delta / 16.6; // 1
const boxMoved = 60; // your logic at 60fps calculates 60 units
const officialBoxMoved = boxMoved * scale; // 60

// At 40fps, a frame takes 25ms
const delta = 25;
const scale = delta / 16.6; // 1.5
const boxMoved = 40; // your logic at 40fps calculates 40 units
const officialBoxMoved = boxMoved * scale; // 60

Your code would look like:

let lastTime = performance.now();

(function gameLoop(currentTime){
  requestAnimationFrame(gameLoop);

  const scale = (currentTime - lastTime) / (100 / 6);
  lastTime = currentTime;

  gameLogic(scale);
  gameDraw();

}());

In terms of the code you just posted, there isn't much of a difference. But where the difference will be is in how you write your game logic using this value.

\$\endgroup\$
0
1
\$\begingroup\$

You're basically asking if there's a faster way to do subtraction as there isn't much else going on here.

Well, no. But you can skip calling performance.now() as that's already been done for you: The callback you pass to requestAnimationFrame() will receive precisely that value as its argument.

Also, timeBetween can be a local variable, and everything should be wrapped in a IIFE (immediately-invoked function expression) to avoid cluttering up the global scope.

(function () {
  var lastFrameTime = performance.now();

  function gameLoop(now) {
    var frameTime = now - lastFrameTime;
    lastFrameTime = now;

    update(frameTime);
    draw();

    requestAnimationFrame(gameLoop);
  }

  // kick it off
  requestAnimationFrame(gameLoop);
}());

Rather than prefixing things with game or time, I've opted for slightly simpler names. update and draw in particular are the typical names for the two steps a game performs each frame.

You'll also want to initialize your timeLast/lastFrameTime to something. Otherwise the first frame's duration will be NaN.

\$\endgroup\$
4
  • \$\begingroup\$ I think he's talking about time based animations. A new time has to be initialized every time the function runs. \$\endgroup\$
    – superlaks
    Commented Apr 11, 2017 at 4:48
  • \$\begingroup\$ @superlaks Huh? Not sure I understand your comment. \$\endgroup\$
    – Flambino
    Commented Apr 11, 2017 at 8:40
  • \$\begingroup\$ He needs a time (performance.now) to compare frame time. In your example, that line is only executed once. However, I can't see more improvements to his code, other than making the timeBetween variable local in your example. \$\endgroup\$
    – superlaks
    Commented Apr 11, 2017 at 12:22
  • \$\begingroup\$ @superlaks No, it's calculated each time is called, because requestAnimationFrame calls its callback (gameLoop) with the value of performance.now() as an argument (see the first few lines of me answer). The whole reason requestAnimationFrame does this is precisely to make it a little bit easier to calculate frame time without having to call now() yourself \$\endgroup\$
    – Flambino
    Commented Apr 11, 2017 at 12:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.