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);
}
2 Answers 2
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.
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
.
-
\$\begingroup\$ I think he's talking about time based animations. A new time has to be initialized every time the function runs. \$\endgroup\$superlaks– superlaks2017年04月11日 04:48:44 +00:00Commented Apr 11, 2017 at 4:48
-
\$\begingroup\$ @superlaks Huh? Not sure I understand your comment. \$\endgroup\$Flambino– Flambino2017年04月11日 08:40:52 +00:00Commented 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– superlaks2017年04月11日 12:22:29 +00:00Commented 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 ofperformance.now()
as an argument (see the first few lines of me answer). The whole reasonrequestAnimationFrame
does this is precisely to make it a little bit easier to calculate frame time without having to callnow()
yourself \$\endgroup\$Flambino– Flambino2017年04月11日 12:25:19 +00:00Commented Apr 11, 2017 at 12:25