Search code examples
javascripthtmlanimationcanvassprite-sheet

What is the best way to track time/frames in a requestAnimationFrame game loop?


I've coded along with very simple game tutorials that use a simple requestAnimationFrame game loop. They have no need of tracking time elapsed or frame rate:

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();

Now I want to learn how to animate things like walk cycles from a spritesheet, rather than only animating movement of a static object. I believe that requires first learning how to track how much time a frame took to render, or what frame you are on. I was under the impression that this wasn't necessary if you use RAF instead of setInterval or setTimeout (oops!). I've seen people use the Date Object, the requestAnimationFrame Timestamp, and performance.now, though I don't understand the code yet. Which is the best choice for game development with requestAnimationFrame if my goals are to animate from spritesheets, and to make sure movement in the game is the same speed no matter how many fps any particular player is achieving? I've read that you have to multiply all speeds in the game by a time factor, but don't know how. Otherwise a slow computer that's only getting 30fps is walking through the game and shooting bullets at half speed compared to fast machines gettine around 60fps, right?

Please show me how I would implement the time/frame tracking code in a game loop, in addition to pros and cons of different methods of accomplishing this.

At risk of sounding like I want a tutorial, please ignore the following part from the original question

It would also be nice to see how you would use that code to animate something like walking or flapping wings from a spritesheet, and how to multiply movements speeds by a time factor so everyone gets the same game experience.


Solution

  • requestAnimationFrame takes a callback. That callback is passed the time in milliseconds since the page started. So you can use that time subtract it from the time if the previous requestAnimationFrame callback to figure out your frame rate and to use for a "deltaTime" to mutiply other values by like velocity.

    Typically you move things based on deltaTime. If your deltaTime is in seconds then it's easy to make anything based on delta-per-second. For example to move 10 units per second each frame you do something like

    const unitsPerSecond = 10;
    x = x + unitsPerSecond * deltaTimeInSeconds
    

    As for frame count you just keep your own counter

    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>