Skip to content

Game loops

Heiko Brumme edited this page Jan 29, 2015 · 5 revisions

In this part we will take a look into game loops.

How does a game work?

Programmatically a game can be described with a simple method:

public void startGame() {
    init();
    gameLoop();
    dispose();
}

This is everything a game have to do, init() would contain all the initialization, like creating a window or loading ressources. And dispose() contains all the code to free the ressources used by the game.
But since init() and dispose() is fairly straightforward to implement we will just take a look into the gameLoop().

A simple game loop

The key part of the game loop is to handle input, update the game logic and rendering the game.

public void gameLoop() {
    while (running) {
        input();
        update();
        render();
    }
}

A game loop like that would consume all the CPU, because it runs as fast as it can. Most of the times you don't want it like that, so you should let the loop sleep some time.

public void gameLoop() {
    long sleepTime = 1000L / 60L;

    while (running) {
        input();
        update();
        render();

        sleep(sleepTime);
    }
}

In this example we have a fixed sleep time of 16 milliseconds, so the game will update 62,5 times per second.
That's better than no sleep time, but you want to have a variable sleep time to get constant FPS.
To do this we should take a look into time calculations.

With timing calculations we have two options for creating a game loop:

  • Variable timestep game loop
  • Fixed timestep game loop

Variable timestep

With a variable timestep you don't have to bother if your game runs too slow or too fast, because the delta time is used for updating the game.

The first thing we should do in the loop is to get the delta time.

float delta = timer.getDelta();

The delta time will get used for updating your game. With that in mind the update() method has to get changed by adding the delta to it.

public void update(float delta) {
    /* do updates */
}

There is not much more to do with a variable timestep game loop, so in the end it will look like this:

public void gameLoop() {
    while (running) {
        float delta = timer.getDelta();

        input();
        update(delta);
        render();

        window.update();
    }
}

You may wonder why there is no sleep() inside that loop. This is because you can tell GLFW to enable v-sync, then you won't have to bother with sleeping.
But if we add sleeping the loop doesn't change that much.

public void gameLoop() {
    long targetTime = 1000L / targetFPS;

    while (running) {
        /* Note that you have to multiply by 1000 to get milliseconds */
        long startTime = timer.getTime() * 1000;
        float delta = timer.getDelta();

        input();
        update(delta);
        render();

        window.update();

        /* Same as above, multiply time in seconds by 1000 */
        long endTime = timer.getTime() * 1000;
        sleep(startTime + targetTime - endTime);
    }
}

But this loop has a draw back: Your updates will be locked to your frame rate, in a simple game this might be okay, but if you want to make a physic simulation you don't want that.

Fixed timestep

With fixed timesteps your game loop will be time-based instead of frame-locked and it is more deterministic than a variable timestep.

For fixed timesteps we need more variables than just the delta time, we also need an accumulator for recording the elapsed time, an interval for how much time should be between updates and also an alpha value that is needed for interpolation.

float delta;
float accumulator = 0f;
float interval = 1f / 30f;
float alpha;

In this example we want to have 30 updates per second, so the interval is about 33,33 milliseconds.
Now that we have our variables let's get into the loop. Like in the variable timestep we first have to get our delta time, but in this loop we add that time to our accumulator.

delta = timer.getDelta();
accumulator += delta;

Next we just handle our input and after that we have to check if enough time has passed so that we can update our game.

while (accumulator >= interval) {
    update(interval);
    accumulator -= interval;
}

Well after that it could be that we have some left over time. This can be seen with a simple example.
Let's say the current game time is at 48ms, but our next update is at 66,67ms, because we have an update every 33,33 milliseconds. We know that our accumulator is at 48ms - 33,33ms = 14,67ms.
So our current game state is 14,67 milliseconds in the past, now we could render the same screen again, but then our game looks like rendering with 30 FPS instead of our target time and that frame was a waste to render. To show the state between those two updates we have to interpolate, and for that we need the alpha value.

alpha = accumulator / interval;

In our example the alpha value would be 14,67 / 33,33 = 0,44, so we are about 44% of the way to the next update. For rendering we need to keep the last and the current state to interpolate between them, but we will take a look at this in another tutorial.

In the end the fixed timestep loop looks like this:

public void gameLoop() {
    float delta;
    float accumulator = 0f;
    float interval = 1f / targetUPS;
    float alpha;

    while (running) {
        delta = timer.getDelta();
        accumulator += delta;

        input();

        while (accumulator >= interval) {
            update();
            accumulator -= interval;
        }

        alpha = accumulator / interval;
        render(alpha);

        window.update();
    }
}

One note about the update method: Adding the interval to your update method is optional if you won't change it during development.
For this tutorial update() will call update(delta) and render() will call render(alpha), so it doesn't matter which timestep you want to use.

public void update() {
    update(1f / targetUPS);
}

public void render() {
    render(1f);
}

Next steps

In the next tutorial we will take a look into rendering with shaders.


Source code

References

Clone this wiki locally