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);
}
asked Apr 10, 2017 at 17:13
\$\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.

answered Apr 11, 2017 at 12:46
\$\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.

answered Apr 10, 2017 at 22:00
\$\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\$ Commented Apr 11, 2017 at 4:48
  • \$\begingroup\$ @superlaks Huh? Not sure I understand your comment. \$\endgroup\$ 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\$ 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\$ Commented Apr 11, 2017 at 12:25

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.