How to fix stuttering in scrolling animations

As I started working on our next game, I noticed a very strange and unpleasant behaviour: when scrolling the viewport, the animation was stuttering. Here's how I fixed it.

The first impression that this behaviour gave me was that my computer wasn't powerful enough to run the game, but having a constant 50fps (The refresh rate of some laptop monitors is 50Hz), and a maximum 30% CPU usage demonstrated that my computer can handle the game just fine, so there must be an elegant solution to this.

Now, anyone willing to implement a game engine should read the excellent article Fix Your Timestep! by Glenn Fiedler. In fact, if you follow (and understand) that article until the end, you'll see that this problem has been addressed there.

Another thing to mention is that I'm also the author of the friGame library, that is used by our next game.

The problem is: I did not fully understand Glenn Fiedler's article, so, until recently, the update/draw loop in friGame looked something like this:

friGame.draw = function (timestamp) {
    var
        newTime = timestamp,
        frameTime = newTime - currentTime
    ;

    requestAnimationFrame(friGame.draw);

    currentTime = newTime;
    accumulator += frameTime;

    if (accumulator >= dt) {
        while (accumulator >= dt) {
            playground.update();
            accumulator -= dt;
        }
    }

    playground.draw();
};

Which is based on the second to last code block from Glenn Fiedler's article.

For years I thought that this code was good enough, it had a fixed time step of 16.67ms, and on 60Hz monitors it looked good.

Moreover I was confused by the last part of Glenn Fiedler's article when he talks about interpolating between the previous and current physics state.

What does it mean that I have to store the previous physics state? Does it mean that I have to keep track of accelerations, speeds, and so on for every object in my game?

Moreover, if I have to keep track of the physics of the game, I can no longer use the same API in friGame, or be free to choose a different physics engine on a game by game basis.

It turns out that none of this is true, I was simply confused by the choice of words.

You see, in the last part of Glenn Fiedler's article, the interpolation is done in order to render an intermediate state, so, in theory, the only states to keep track of are the ones that affect rendering, and these are:

  • Position
  • Size
  • Rotating angle
  • Scaling factor
  • Opacity

In pratice, there is a computational cost for keeping track and interpolating all these values, so smooth scrolling can be achieved simply by keeping track and interpolating only the position of all the sprites.

So, in the latest development version of friGame the update/draw loop looks something like this:

friGame.draw = function (timestamp) {
    var
        newTime = timestamp,
        frameTime = newTime - currentTime
    ;

    requestAnimationFrame(friGame.draw);

    currentTime = newTime;
    accumulator += frameTime;

    if (accumulator >= dt) {
        while (accumulator >= dt) {
            // Keep track of all the sprite positions
            for (sprite in friGame.sprites) {
                sprite.prevLeft = sprite.left;
                sprite.prevTop = sprite.top;
            }

            playground.update();
            accumulator -= dt;
        }
    }

    playground.draw(accumulator / dt);
};

sprite.draw = function (interp) {
    var
        left = (this.left * interp) + (this.prevLeft * (1 - interp)),
        top = (this.top * interp) + (this.prevTop * (1 - interp))
    ;

    ctx.drawImage(this.img, left, top);
};

After implementing the interpolation of only the sprite positions, I was impressed by how smooth the animations have become.

I'm planning to release an update to friGame in the next few weeks, so stay tuned.

Comments !