This site uses cookies to ensure you get the best experience. More info
Got it!

Let's talk about Game Loop



What Game Loop is

Game Loop is the heartbeat of every game. Its goal is to decouple the processing of game logic from user generated input and the CPU’s speed.

It’s worth to note, for the less gamedev savvy readers, that the game itself is usually processed in very tiny, discrete steps. A single such step is often called a tick or a frame.
Hence the common term Frames per second or FPS.

The game in single such step, must process the given input by user (if there is any; the game doesn’t wait for it, just processes it when needed).
Next up, the game loop needs to advance the game simulation by one step - which involves all AI, logic and physics related code. In other words, it updates the game state.
Finally, the current state of the game is rendered on screen.

If we could put what we know for now into code to make the simplest game loop, it could look a bit like this:

while(true)
{
    handleInput();
    update();
    render();
}

All in that one tiny step, and as you probably know, the games usually run at at least 30 or 60 FPS as a standard to be smooth and not laggy!

But hey! I have seen a game run at different number of FPS on my crappy desktop, and not even once but actually a lot of times! You probably even experienced it yourself!

And here comes another responsibility for the game loop - it needs to adapt to the hardware; it runs at consistent speed despite differences in underlying hardware.

So, just to make sure nobody is lost:

The game loop runs continuously, and during each step it handles user input without blocking the process, then updates the game state and renders it on screen.
It also runs the game at consistent speed regardless of hardware.

But, as you can see in code above - we in fact do not take any precautions depending on hardware or anything. For now, it works to do things as fast as possible eating all processing power it can get, which means that on slower machines it will play differently. Same thing for all game parts that require additional computing.

In order to address that issue we need to start playing with a timestep.

Let’s step it up

Timestep is used to dictate the tempo of the game. The way we handle our timestep can be done in a few different ways. For example, we can utilise a variable timestep or a fixed one, and both approaches have their pros and cons.

Fixed timestep

The most straightforward approach is to apply a timestep that’s fixed. Let’s say we want our game to run at 60 FPS, which gives us roughly 16ms per frame (1000ms/FPS = ms per frame).

So let’s alter our code to reflect it so we do not go faster than FPS and save some CPU cycles. All we need to do is to sleep for as much time as we have left till the time for the next frame. Genius and simple! Well, not really.

For our time measurements, let’s use the given Stopwatch class.

Stopwatch stopWatch = Stopwatch.StartNew();

while(true)
{
    TimeSpan currentTime = stopWatch.Elapsed;
    
    handleInput();
    update();
    render();

    TimeSpan difference = currentTime - stopWatch.Elapsed;

    Thread.Sleep(TARGET_FRAMERATE - difference.Milliseconds);
}

Now, everything works well if our loop goes too fast. If it applies the changes in 5ms, then we sleep for 11ms and the frame rate is kept under control.

But there is a dreadful other side of the coin here. If any frame takes MORE time to process than our beloved 16ms, what happens then? We sleep for a negative time every frame it happens? Oh how I wish I could do that…

If such situation happens - that our updates take longer than our targeted frame rate - our game slows down and can’t catch up. We could desperately fight with it by doing less work and thinning our game - but is it really a good thing to do? Can’t we do better than this?

Variable timestep

Probably the easiest way to go around the fixed timestep problems is to apply a variable timestep - which means that the timestep size can vary and be different every frame.
As a consequence, our game state updates need to be coupled with the timestep in order for things to work right.

To put it in other words - our state changes need to be scaled by the timestep so the game acts accordingly.

In variable timestep loop, each step we measure how much time elapsed using our Stopwatch instance.
Then, we subtract the last measured time so we get the elapsed time which is then passed to the update function in order to update the game’s state.

Stopwatch stopWatch = Stopwatch.StartNew();
TimeSpan lastTime;

while(true)
{
    TimeSpan currentTime = stopWatch.Elapsed;
    TimeSpan elapsedTime = currentTime - lastTime;
    lastTime = currentTime;

    handleInput();
    update(elapsedTime);
    render();
}

But there is a problem with this approach in some cases. If we would like to implement any physics simulation with use of such loop, our simulation would always get a different delta time passed in. The results may vary, the game might feel strange depending on the frame rate or, the simulation could get very bad and unstable, with objects traveling at too high speeds, tunnelling through others and collisions not getting detected. Physics needs constant deltas. Period.

Semi-fixed timestep to the rescue

To cope with such problem, we can implement a more sophisticated game loop that makes our updates processed with constant delta time.

This approach is all about calculating the passing time, and updating when an amount larger that a targeted frame rate has passed.
There is also a little twist - whenever for some reason a large amount of time has passed since last update, which is not just bigger than our targeted frame rate, but is larger than a multiple of it - we do a series of multiple updates to catch up.

Stopwatch stopWatch = Stopwatch.StartNew();
TimeSpan lastTime;
TimeSpan accumulatedTime;

while(true)
{
    TimeSpan currentTime = stopWatch.Elapsed;
    TimeSpan elapsedTime = currentTime - lastTime;
    lastTime = currentTime;

    accumulatedTime += elapsedTime;

    handleInput();

    while(accumulatedTime >= TARGET_FRAMERATE)
    {
        update();
        accumulatedTime -= TARGET_FRAMERATE;
    }

    render();
}

Now, this works pretty well, we catch up whenever there is a bigger lag, we apply a constant delta time to our game’s state updates and everything seems to be okay.

The TARGET_FRAMERATE here is left for you to decide. The shorter it is, the more processing it will take to catch up to real time, the longer it is - the less smooth game becomes.
Usually you will want to keep it high, say 60 FPS or more, unless the target platform limits you to some arbitrary value like 30.

Still not there, but close

There is yet another issue left unresolved. We update the game at a fixed rate, the deltas are constant, but we render at arbitrary points of time. We render whenever we can, it may not necessarily always be at the time of game update.
This issue is called residual lag.

Fortunately, we know how much of the lag we have accumulated hence we keep it in the accumulatedTime variable.

All that needs to be done, is to render the game state while keeping the lag in mind.

So instead of calling render() we would call render(accumulatedTime).

Which means, we would need to do some sort of extrapolation of the state, depending on the lag.

If we resolve this issue then our loop will seem to be working! At least for such simple case.

A few final words

Game Loop can get much more complex and ugly, with features like synchronizing with display’s refresh rate, multithreading and so on, but this general overview should give you some idea.

Next thing is, that we often need to play nice with the platform or OS our game is developed on. In most cases, that means we have to watch for any native event loop messages to be processed, and if there are not any, then we go and act like we previously did.

In the next post I’m going to quickly go through actual implementation in Praedium and talk about some decisions I made which influenced it and also show how the native messages issue was resolved by calling some managed code from User32.dll.
I’ll also list the definite features I would like to implement and prepare a quick mock-up of the visual side of the game for better understanding what the heck I’m actually creating.

If you have any questions or feedback, feel free to comment below.