The Game Loop 10

This tutorial will cover one of the most basic components of a game, the game loop. The game loop is what actual drives your game. It manages the calls to your logic and drawing functions. While doing this, it performs the vital role of maintaining a consistent game speed (also called the logic rate). Creating a game loop is not always an easy thing. Many newer programmers get stuck on this. This tutorial will explain what a game loop is, how to create one, and what are some common pitfalls.

A Simple (and Flawed) Game Loop

First, what is a game loop? At its most basic, a game loop is a state machine. For a game, there are usually four main states.

  • Start: This is where the program performs any initialization.
  • Stop: Here, any open resources should be released before exiting.
  • Update: All the game logic goes here, modifying the state of objects.
  • Draw: This is where all the drawing goes.

A simple game loop may consist of these four states. Start is called first, performing any initialization, then the main game loop is entered. Update and draw are called repeatedly until the program exits to the stop state. Figure 1 shows a diagram of this simple game loop.

A simple game loop.
Figure 1: A simple game loop.

A game loop like this is as easy to implement as it is to draw.

public abstract class GameLoop
{
    private boolean runFlag = false;

    public void run()
    {
        runFlag = true;

        startup();
        while(runFlag)
        {
            update();
            draw();
        }
        shutdown();
    }

    public void stop()
    {
        runFlag = false;
    }

    public abstract void startup();
    public abstract void shutdown();
    public abstract void update();
    public abstract void draw();
}

This method has a significant problem. Because the game loop runs as fast as possible, update will be called much more frequently on a fast computer than a slow one. This means that the game will run at different speeds for different users, something that you definitely don’t want.

A Frame Rate Limited Game Loop

There are several options at this point. The first would be making the updates aware of how much time the loop took. For example, if the loop takes half as long as expected, only move the objects half as far. A game loop that has this behavior is sometime referred to as a “delta loop” or using “delta time”. Unless you are very careful, this can cause problems, particularly when updates are very fast or slow. This also makes the behavior of the game much less repeatable.

Another option would be to repeat the drawing step until it is time to update again. This would work, but it is not necessary, and could in fact be harmful. If the game state has not been updated, the drawing probably has not changing either. The harm can come from overworking the graphics card. Starcraft 2 had this problem when it was released. The frame-rate was not capped in menu screens causing graphics cards to overheat.

To deal with this, we will put the program to sleep if we finish the loop early. Figure 2 shows the new state diagram. Sleeping has a few additional benefits. If our game is multi-threaded, this gives other parts of the code time to execute. It also behaves better with the other processes on the computer.

A frame rate limited game loop.
Figure 2: A frame rate limited game loop.

Here is the code for the frame rate limited game loop.

public abstract class GameLoop
{
    private boolean runFlag = false;

    /**
     * Begin the game loop
     * @param delta time between logic updates (in seconds)
     */
    public void run(double delta)
    {
        runFlag = true;
        
        startup();
        // convert the time to seconds
        double nextTime = (double)System.nanoTime() / 1000000000.0;
        while(runFlag)
        {
            // convert the time to seconds
            double currTime = (double)System.nanoTime() / 1000000000.0;
            if(currTime >= nextTime)
            {
                // assign the time for the next update
                nextTime += delta;
                update();
                draw();
            }
            else
            {
                // calculate the time to sleep
                int sleepTime = (int)(1000.0 * (nextTime - currTime));
                // sanity check
                if(sleepTime > 0)
                {
                    // sleep until the next update
                    try
                    {
                        Thread.sleep(sleepTime);
                    }
                    catch(InterruptedException e)
                    {
                        // do nothing
                    }
                }
            }
        }
        shutdown();
    }

    public void stop()
    {
        runFlag = false;
    }

    public abstract void startup();
    public abstract void shutdown();
    public abstract void update();
    public abstract void draw();
}

There are a few subtleties to notice in the code. First, we now have an additional parameter called delta. This is the amount of time we want to pass between each logic update. The variable nextTime stores the time in the future when we want to call update again. By adding delta to nextTime after each update, we get updates at the desired frequency. It is important that delta is added to nextTime, and not currTime. Otherwise, small pieces of time would be lost, particularly if the update is late. Another consideration is that every loop either updates and draws OR sleeps. If this was not the case, the time would be outdated when the sleep section was reached.

An Advanced Game Loop

We are not done improving our game loop yet. So far we have draw being called every time update is. What if our program is running slow? It may be a good idea to skip some of the calls to draw so that we continue to update at the correct rate. We can now update our state diagram to figure 3.

An advanced game loop
Figure 3: An advanced game loop.

We only need to change one line of the code to implement this change.

                draw();

becomes…

                if(currTime < nextTime) draw();

A More Advanced Game Loop

The game loop so far should work just fine in most situations, but what if the updates get significantly behind? Currently, the game loop would try to run as many updates as necessary to catch up. This may cause the game to skip a significant amount ahead, a major problem if players are expected to react quickly to events. What we need to do is allow the nextTime to skip ahead only if we have fallen too far behind.

Another potential problem when the game is running slow is that draw will never get called. This can be fixed by forcing the game to be drawn every so often. Both of these changes have the consequence that the game may no longer run at the correct rate if the updates and draws are taking to long. Whether or not this is acceptable depends on the game, but keeping the game responsive is often more important. If you do not want that functionality, just don’t include the last two changes. Figure 4 shows the final state diagram.

A more advanced game loop
Figure 4: A more advanced game loop.

Here is the final source code:

public abstract class GameLoop
{
    private boolean runFlag = false;

    /**
     * Begin the game loop
     * @param delta time between logic updates (in seconds)
     */
    public void run(double delta)
    {
        runFlag = true;
        
        startup();
        // convert the time to seconds
        double nextTime = (double)System.nanoTime() / 1000000000.0;
        double maxTimeDiff = 0.5;
        int skippedFrames = 1;
        int maxSkippedFrames = 5;
        while(runFlag)
        {
            // convert the time to seconds
            double currTime = (double)System.nanoTime() / 1000000000.0;
            if((currTime - nextTime) > maxTimeDiff) nextTime = currTime;
            if(currTime >= nextTime)
            {
                // assign the time for the next update
                nextTime += delta;
                update();
                if((currTime < nextTime) || (skippedFrames > maxSkippedFrames))
                {
                    draw();
                    skippedFrames = 1;
                }
                else
                {
                    skippedFrames++;
                }
            }
            else
            {
                // calculate the time to sleep
                int sleepTime = (int)(1000.0 * (nextTime - currTime));
                // sanity check
                if(sleepTime > 0)
                {
                    // sleep until the next update
                    try
                    {
                        Thread.sleep(sleepTime);
                    }
                    catch(InterruptedException e)
                    {
                        // do nothing
                    }
                }
            }
        }
        shutdown();
    }

    public void stop()
    {
        runFlag = false;
    }

    public abstract void startup();
    public abstract void shutdown();
    public abstract void update();
    public abstract void draw();
}

-Eric

  • Beernutts

    I don’t think the method you describing is the best way at all. You SHOULD be drawing every frame, but where the objects are you draw should be dependent on the time between frames. So, moving objects need to have a velocity associated with them based on pixels/second (or a similar speed). That way, on slow computers, the objects will be updated less often, but they will have moved the same distance as fast computers, which update more often.

    For example, you’re game loop should be:
    while(bPlaying)
    {
    // DeltaTimeInMs is the time since the last frame was drawn, in milliseconds
    DeltaTimeInMs = GetDeltaTimeInMs();

    // pass the time since the last update, and have the objects that are moving be updated based on how long it’s been since the last update.
    bPlaying = update(DeltaTimeInMs();

    draw();
    }

    • http://entropyinteractive.com Eric

      I do mention that as an option, but I am not a huge fan of it. One issue that can come up is that the game is not consistent on different systems. The place where this is most obvious is physics. Most physics engines will break down if the step size is to big, and less commonly, to small. Also, is you want any sort of replay system, variable step sizes would be a nightmare.

      It is completely possible to make a game loop like that. You just need to be aware of the issues. I feel it is simpler (particularly for beginners) to have every game loop step the same amount of time.

    • Beernutts

      FWIW, my game uses variable step sizes, and I also have a replay system that just records position of sprites on the screen every 30 ms (or is it 60? I forget) and replays it that way.

      I had trouble if I used a fixed time for some really slow machines, so I just use variable time.

    • http://entropyinteractive.com Eric

      I did some more research. Delta time game loops are more common than a had previously thought. I rewrote the game loop to allow for either delta time of fixed time game loops. I don’t think I will modify this tutorial, but I will post a new one soon with the expanded game loop. Thanks for the feedback.

  • Pingback: How can I tell OpenGL how often to draw stuff? - Programmers Goodies

  • Chris

    I know this is a fairly old article, but just o n the off-chance you see this comment anyway, I’m writing a game using a mainloop heavily based on the one given here, but my framerate is locked to 20 Hz. I want the game logic to update at 20 Hz (for the time being, anyway; this is so that it doesn’t vary by computer), but the framerate shouldn’t be locked to that, should it?

    Is it not better to have the game logic update at a fixed rate, and let the graphics be redrawn as frequently as the hardware allows?

    What about input/events? Should they be locked to the same rate as the game logic (in my case, 20 Hz at the moment).

    • http://entropyinteractive.com Eric

      20 Hz seems pretty low, but I don’t know what your requirements are so that may be appropriate. You can allow the graphics to update faster than the logic with two caveats. First, because your logic isn’t being run as fast, nothing will change positions between some of your frames making them redundant to draw. You can so some degree of interpolation between frames, but that is well beyond the scope of this article. Second, updating faster than the screen refresh (60 Hz generally) is pointless.

      One option is to do some basic logic between your game’s main logic steps. These can do things like update mouse position or shift the viewpoint according to the players input. This will give the player the impression of the game being more responsive, even if the actual game logic is slower.

    • Chris

      I think for the time being I’ll just leave it as it is, but I’ll set the logic rate to 60 Hz and enable vertical sync. Thanks.

  • Pingback: Event Driven Programming: A sequence of unfortunate events | Question and Answer

  • mrfunky

    This is two years old and it works great. I actually copy this model for my games