Javascript Game Foundations - The Game Loop

Wed, Dec 4, 2013

Ten Essential Foundations of Javascript Game Development

  1. A Web Server and a Module Strategy
  2. Loading Assets
  3. The Game Loop
  4. Player Input
  5. Math
  6. DOM
  7. Rendering
  8. Sound
  9. State Management
  10. Juiciness

The Game Loop

In board games, card games, and text-adventure games, the game updates only in response to player input, but in most video games the state updates continuously over time. Entities move, scores increase, health decreases, and objects fall, speed up, and slow down.

Therefore we need a game loop that can update() and render() our game world.

When running in the browser, all javascript code is executed in a single thread, the UI thread (not counting WebWorkers). Which means we can’t run a naive infinite game loop:

  while(true) {  // UH OH! - blocks UI thread
    update();
    render();
  }

This would block the UI thread, making the browser unresponsive. Not good.

RequestAnimationFrame

Instead, the browser provides asynchronous mechanisms for us to “do a little work”, then let the browser UI do it’s job, then have it callback to us at a later time to “do a little more work”.

In older browsers, we might have used setInterval or setTimeout to call our update method a set number of frames per second, but in modern browsers we should be using requestAnimationFrame to hook into the browser’s native refresh loop:

function frame() {
  update();
  render();
  requestAnimationFrame(frame); // request the next frame
}

requestAnimationFrame(frame); // start the first frame

Timestamps

While requestAnimationFrame gives us our asynchronous loop, it doesn’t guarantee exactly how frequently it will execute. In most modern GPU accelerated browsers it will be close to 60fps, but it might be a little more, might be a little less. If our update or render methods are slow we might cause it to drop drastically below 60fps.

We cannot assume that 1/60th of a second passes in each frame. Therefore we need to measure exactly how much time has passed between subsequent iterations of the loop. In modern browsers we can use the high resolution timer and in older browsers fallback to using a Date object:

function timestamp() {
  return window.performance && window.performance.now ? window.performance.now() : new Date().getTime();
},

Now that we have a utility method for getting a timestamp (in milliseconds) we can extend our game loop to provide that information to the update and render methods.

var now, dt,
    last = timestamp();

function frame() {
  now   = timestamp();
  dt    = (now - last) / 1000;    // duration in seconds
  update(dt);
  render(dt);
  last = now;
  requestAnimationFrame(frame);
}

requestAnimationFrame(frame);

One additional note is that requestAnimationFrame might pause if our browser loses focus, resulting in a very, very large dt after it resumes. We can workaround this by limiting the delta to one second:

  ...

  dt = Math.min(1, (now - last) / 1000);   // duration capped at 1.0 seconds

Fixing Our Timestep

Now that we know exactly how long it has been since the last frame, we can use that information for any math calculations within our update method.

Having a variable timestep can work for many simple games. However we gain additional benefits if we can guarantee that the update method is called at a fixed, known, interval.

Replayable - if the timestep is variable (and unpredictable) then we cannot predictably replay the level. If we want to be able to replay what happened we need the timestep to be fixed and predictable

Predictable Physics - if we have a physics engine in our game, then variations in the timestep would make it unpredictable, which might make it hard to create predictable level design

Mitigate bullet-through-paper - depending on our collision detection scheme, we might find that fast moving objects can pass through small objects, this can be mitigated if our fixed timestep is set relative to our entities maximum speed and minimum size.

The idea behind a fixed timestep gameloop has been written about thoroughly by gafferongames.com

The basic idea is to accumulate our dt until it is greater than our desired fixed timestep, call update with the fixed timestep, and carry over the remainder to accumulate for the next time.

We should also pass any remainder dt to our render function so it can perform smoothing LERP calculations (if required)

var now,
    dt   = 0,
    last = timestamp(),
    step = 1/60;

function frame() {
  now = timestamp();
  dt = dt + Math.min(1, (now - last) / 1000);
  while(dt > step) {
    dt = dt - step;
    update(step);
  }
  render(dt);
  last = now;
  requestAnimationFrame(frame);
}

requestAnimationFrame(frame);

For a detailed explanation of fixed timestep game loops, I highly recommend you read the articles by gafferongames.com, gamesfromwithin.com and koonsolo.com

Adding an FPS Meter

One last nice to have is to integrate an FPS counter into the loop. These days I favor FPSMeter which can be integrated in easily:

var fpsmeter = new FPSMeter({ decimals: 0, graph: true, theme: 'dark', left: '5px' });

function frame() {
  fpsmeter.tickStart();
  ...
  fpsmeter.tick();
}

BONUS: Slow Motion

As a bonus, we can (optionally) adjust the step variable and play our game in slow motion. This can be very helpful when debugging. If we multiply step by 5, the game will play 5 times slower (12.5 fps instead of 60fps).

Putting this all together into a final version that can be re-used by different games, we can build a general purpose Game.run method that allows the caller to override various options:

Game = {

  ...

  run: function(options) {

    var now,
        dt       = 0,
        last     = timestamp(),
        slow     = options.slow || 1, // slow motion scaling factor
        step     = 1/options.fps,
        slowStep = slow * step,
        update   = options.update,
        render   = options.render,
        fpsmeter = new FPSMeter(options.fpsmeter || { decimals: 0, graph: true, theme: 'dark', left: '5px' });

    function frame() {
      fpsmeter.tickStart();
      now = timestamp();
      dt = dt + Math.min(1, (now - last) / 1000);
      while(dt > slowStep) {
        dt = dt - slowStep;
        update(step);
      }
      render(dt/slow);
      last = now;
      fpsmeter.tick();
      requestAnimationFrame(frame, options.canvas);
    }

    requestAnimationFrame(frame);
  },

  ...
}

I’ve used a variation of this game loop for all my javascript games so far. It’s worked very well.