div0 icon

← Go to page index ←

Game loops: the heart of computer games

by Izak Halseide on 2023.04.08 (last updated on 2023.04.08)

This is a short article about what a game developer should know about the computer programs that run games. This is not about gameplay loops, which are loops that game players themselves are put into. This is a simple concept that I think is taken for granted in game programming tutorials and needs a more explicit explanation. I think it is necessary to explicitly state the mechanics of using a game loop in an architecture. The page at GameProgrammingPatterns.com and this page from GafferOnGames.com are excellent reads. I am assuming that the game in question is a real-time graphical game that responds to user input immediately, as opposed to a command line interface game that has buffered input.

Why use time steps

In a game, you write the code for how the game world behaves over time, which expands into how each type of game object behaves over time. More specifically, you break down time into slices, time steps. You program what each object does at each game update call or frame or tick or time step. This constraint comes from the fact that you need to display the game world on the screen at a certain frame rate so that things appear to be moving. Since the output needs to sliced up into a sequence of frames, like a movie, you have no choice but to somehow calculate what the game world is like in those different frames. Also, it is difficult to model the behaviors of many interesting game objects as a long-term function of time, but it is simpler to describe what a game object does next in a short period of time at each step.

How do computer games work with these time slices? Well, there is one essential idea for game programs. Games can run for a long time or an indefinitely long time. In programming, running the game for a long time requires a loop of some kind. Usually this loop is infinite or only broken out of when the player closes and quits the game. The time steps are then simulated in this loop as long as the game is running.

So, somewhere in every game program is a loop that in general means the same thing as the C code below (ignoring the time step for now):

while (!game_should_quit) {
    update_game();
}

The elements game_should_quit and update_game would be substituted with the proper variables or function calls for your game architecture.

This loop is fine and dandy, but there can be a big problem with just updating the game like this without a time step duration variable. Typically you are writing a game that will run on multiple different computers, which will simulate and run the game at different speeds. The performance of the computer's hardware that the game will run on affects the speed of the game. If you adjust speeds solely to work on the computer you are developing on, then a different player who uses a slightly faster or slower computer will run the game, and it will feel completely different or could be unplayable due to being too fast or too slow.

A common solution to this is to introduce a time step variable or parameter, usually called "delta time", "delta t", or just "dt". This could look like the following in C code:

while (!is_quit) {
    float dt = time_since_last_update();
    update_game(dt);
}

While there is some nuance about whether you want a fixed time step or a variable time step, you usually want the computer that the game is running on to determine the time step amount before the loop begins or continuously during each loop tick, like in the example above. This can make the game physics/simulation updates not run faster or slower based on the computer's performance. A more performant computer will have a smaller time step than a less performant computer, and the more performant computer will execute more of these smaller time steps and thus have a higher FPS but will still get the same (or at least close enough) result in the physics updates. Having a smaller time step can lead to more accurate results, but if your game physics are mostly linear calculations, there should not be much of a difference between having a few big time steps versus many smaller time steps.

A simple derivation

So how is the time step dt used in the game update function update_game? Generally, you use it as a multiplier for physics steps. Say that you want a bullet to move at 50 pixels in a second. If you were running at 1 frame per second, the code would then look like this:

bullet_x_pixels += 50.0f; 

However, you want way more than 1 FPS. Say that you ideally want 60 FPS. Then your game_update function runs 60 times in a second. Each update tick, you move the bullet by how fast it moves in a frame. Ideally you may want 60 frames per second, so your formula for speed would be something like the following:

bullet_x_pixels += 50.0f / 60.0f;

The above code will run 60 times in a second, and by the time one second has passed, the bullet will have moved: (50.0 / 60.0) * 60 pixels, which is 50 pixels. The equivalent math is replacing the division by 60 with a multiplication by 1/60:

bullet_x_pixels += 50.0f * (1/60.0f);

Then, if we extract the 1/60 into a variable called dt, we get the following:

float dt = 1/60.0f;
bullet_x_pixels += 50.0f * dt;

Then, the last step would to let dt be a parameter to the function that the bullet update code resides in. This gets us a general form that we can apply in any update code. So, in general, we can specify what we want to happen in our game per second and then multiply by the fraction of a second that each game tick (a function call to update_game) represents. The dt value is passed as a parameter to many other functions that update_game may call. So, much of your game update logic ends up being a function of dt, rather than time, t (which starts to look a lot like function integration)!

A typical strategy is to have a hierarchical function call tree to update your game:

∟ main_loop()
 ∟ update_game()
  ∟ update_gui()
  ∟ update_world()
    ∟ update_particles()
    ∟ update_entities()
     ∟ update_players()
       ∟ update_a_player()
     ∟ update_bullets()
       ∟ update_a_bullet()
     ∟ update_enemies()
       ∟ update_a_zombie()
       ∟ update_a_fireball()

And then dt is passed to each function, or it is a global variable, or a larger data structure of game info, including dt, is passed to every function or is globally accessible. Of course, if you must pass dt as a function parameter to update_a_fireball, for example, then you will probably end up having to pass it down from update_enemies, which gets it passed from update_entities, which gets it passed from update_world, which gets it passed from update_game.

Functions of t or dt

However, your game logic might not all depend on dt, or it could be better to have some logic depend on elapsed game time, t. A function of time is more numerically stable because floating point errors do not accumulate on a per-frame basis. Also, behaviors that are only a function of t instead of dt do not need to store as much current state from the last frame. An amazing example that exploits this fact, in a particle system, can be seen in this YouTube video about running particle systems efficiently on the PlayStation. Another example is animation. If you have a sphere that shrinks and grows at a certain rate over time, you could express it in terms of dt or t. If you express it in terms of dt, then you specify how much it changes over a little slice of time and add that to the sphere's radius, which needs to be stored as state somehow. But, if you express it in terms of t, then you always just compute the sphere's radius to be some function of time that does not require knowledge of the sphere's previous state, the radius.

(I may make a web page of its own about functions of dt versus functions of t in games, because it seems that a bunch of people mistakenly think that they should always or only use dt.)

The most important limiting factor

Depending on your game's technology stack (the game, engine, libraries, operating system), you may or may not have control of the event loop. In pygame, for example, you do have main control, but you need to query the event queue to make sure the main window keeps functioning. Other setups may require you to provide a callback function for game updates that gets called at times determined by the OS or the library, and so game loop is outside of your control.

A Pygame example

Below is an empty example main game loop for use in pygame:

import pygame

ORIGINAL_WIDTH = 1280
ORIGINAL_HEIGHT = 720
TARGET_FPS = 60

pygame.init()
screen = pygame.display.set_mode((ORIGINAL_WIDTH, ORIGINAL_HEIGHT))

def update_game(dt):
    # add the start of your game logic here
    pass

clock = pygame.time.Clock()
should_quit = False
# This try-finally block makes sure that pygame quits when Python has an error
try:
    while not should_quit:
        if pygame.event.get(pygame.QUIT):
            should_quit = True
        dt = clock.tick(TARGET_FPS)
        update_game(dt)
        pygame.display.flip()
finally:
    pygame.quit()

Further Exploration

↑ Return to top ↑