XNA Game Loop Basics

At the core of all games, a permanent loop is running which repeatedly gathers data from the input devices, moves all active objects in the game world and then generates the visual and audible representation of that world on the screen and on your speakers.

Time to examine this game loop then!

A Naive Game Loop

A naive implementation of a game loop could not like this:

public void RunGame() {

  // Continue running until we are asked to terminate
  while(!this.endRequested) {

    // Loop over all entities in the game, update the positions
    // and render their audible and visual representations
    foreach(Entity entity in this.activeEntities) {
      entity.Update();
      entity.Render();
    }

  }

}

Now we are about to encounter one of the most basic problems game programmers have had to cope with for all times: Not all PCs are equally fast, some will take longer to update the positions of your game's objects, others will not be able to render them quite as fast. Performance might even change inmidst of the game since the number of things on the screen will vary greatly.

With the above game loop, the game would simply run slower since the objects' positions will be updated less often than on a faster PC. On the other side, if the game is run on a faster machine, it might run faster than you expected and is likely to become unplayable. The next chapter will show you how to avoid that.

Performance-Independent Game Loop

In order to make a game independent of how fast the PC in runs on can run the game loop, we need to scale the amount by which objects are moved by the time the previous iteration of the game loop took. In other words, measure how much time has passed since the last time your objects were updated, calculate the up-to-date positions of all objects based on that time and render another frame.

public void RunGame() {
  DateTime lastIterationTime = DateTime.Now;

  // Continue running until we are asked to terminate
  while(!this.endRequested) {

    // Temporary to avoid slight loss of time between passed time
    // calculation and update of lastIterationTime.
    DateTime now = DateTime.Now;

    // Calculate how much time has passed since the last iteration
    // and update the counter to the current time
    TimeSpan timePassed = now - lastIterationTime;
    lastIterationTime = now;

    // Loop over all entities in the game, update the positions
    // and render their audible and visual representations
    foreach(Entity entity in this.activeEntities) {
      entity.Update(timePassed);
      entity.Render();
    }

  }

}

For example, consider a bullet that's moving through the game world at a desired speed of 1 units per second:
If the game loop took 0.5 seconds, we move the bullet by 0.5 units on each iteration of the loop. If the game loop took 1.0 seconds, however, we move the bullet by 1 unit per iteration. In both cases, after a second has passed, the bullet will have moved by 1 unit.

But wait! This game loop still has major problems!

Fully Performance-Independent Game Loop

Take above game loop which updates objects sequentially based on the time that has passed since the last loop iteration. Now assume you were programming a racing game and two cars were headed towards each other for a big frontal crash. Both cars are moving at 10 units per second and are 10 units apart (so after half a second, they will both have moved by 5 units towards each other, reducing their distance to zero - bang!).

Let's observe this scene with on two different computers!

John's Computer

John's computer is rather slow and is running your game at a breathtaking 1 frame per second, so each iteration of the game loop takes 1 second.

At the beginning of the update, both cars are still 10 units apart:

Cars at time index 0 in flawed game loop

Now the first car's position will be updated. Since 1 second has passed from when the last iteration was performed, the car will be moved by a full 10 units:

Cars at time index 1 in flawed game loop

Bang! The updated car has reached its opponent before it even had the chance to be updated also. But weren't they supposed to meet in the middle?

Rick's Computer

Rick's computer is a real performance monster. It is running your game at no less than 4 frames per second, making each iteration of the game loop take 0.25 seconds. Here, the collision will occur like this:

We'll start when the first car's position is updated. Since only 0.25 seconds have passed from when the last iteration of the game loop took place, the car will only be moved by 2.5 units:

Cars at time index 0.25 in flawed game loop

Then the second car's position is updated, moving the car by 2.5 units as well:

Cars at time index 0.25 in flawed game loop

Nothing happened yet. So the game will render another frame and, 0.25 seconds later, we will be in the update phase once more. Again, the first car is moved another 2.5 units:

Cars at time index 0.5 in flawed game loop

Then the second car is also moved another 2.5 units:

Cars at time index 0.5 in flawed game loop

Bang! The cars have hit each other in the middle.

Conclusion

Now assume John and Rick were playing a multiplayer game and continued driving to the finish line after their accident. The game would be in a different state on each computer and slowly drift even further apart.

If you're thinking that this problem could be solved with math, you're right (keyword swept collision detection). But even then, due to the different steps by which both PCs update the game world, you would still have different rounding errors on each PC, causing the game states to drift apart nevertheless.

The only solution is to advance the game in fixed time steps. You will still have rounding errors and inaccuracies like the one demonstrated above, but at least they will be the same on all computers, allowing your game to record replays, have a multiplayer mode and in-game cutscenes with predictable outcome.

Here's one method to achieve this:

// We update at 100 iterations per second
public const TimeSpan StepSize = TimeSpan.FromMilliseconds(10);

public void RunGame() {
  DateTime lastIterationTime = DateTime.Now;

  // Continue running until we are asked to terminate
  while(!this.endRequested) {

    DateTime now = DateTime.Now;

    // Perform as many steps as needed to catch up with the time
    // passed since the last frame. If the time taken is not
    // evenly dividable by the step size, this code will leave
    // the remainder for the next frame
    while(lastIterationTime + StepSize <= now) {

      // Update the positions of all entities in the game
      foreach(Entity entity in this.activeEntities)
        entity.Update(timePassed);

      lastIterationTime += StepSize;
    }

    // Loop over all entities in the game and render their
    // audible and visual representations
    foreach(Entity entity in this.activeEntities)
      entity.Render();

  }

}

The next section will discuss more reasons why we want to seperate the update phase from the drawing phase and draw the connection to the Game class in Microsoft's XNA framework which provides you with a premade game loop that you can use in your games.

Advanced Game Loops

In the previous section, we have seperated the update phase and the drawing phase in our game loop. This not only makes sense when we want to use fixed time steps, but also:

  • Once your game becomes larger, you will not want to keep drawing and update all the objects in your game all the time to keep performance at reasonable levels. The subset of objects you're going to draw is different from the set of objects you'll need to move, so it makes sense to use two different loops.
  • The graphics card works independently of the CPU. So if you seperate the update phase and the drawing phase, the graphics card can work rendering the current frame while the CPU is already moving the objects to their new locations for the next frame. This can increase the performance of your game by as much as 100%.
  • If your game makes use of a physics engine, care has to be taken that time steps don't grow too large. Current physics engines tend to explode their simulation at large time steps. So even in a singleplayer game, seperating the update phase from the drawing phase makes sense so you can move the game objects at multiple smaller steps instead of a single big one.

All this causes our updating and drawing code to becoming much, much more complicated once we go from pong and tetris clones to intermediate games. Therefore it's wise to extract all this code into seperate methods which we'll call Update() and Draw() for now:

public const TimeSpan StepSize = Seconds(0.01); // 1/100th seconds

public void RunGame() {
  DateTime lastIterationTime = DateTime.Now;

  // Continue running until we are asked to terminate
  while(!this.endRequested) {

    DateTime now = DateTime.Now;

    // Perform as many steps as needed to catch up with the time
    // passed since the last frame. If the time taken is not
    // evenly dividable by the step size, this code will leave
    // the remainder for the next frame
    while(lastIterationTime + StepSize <= now) {
      Update(StepSize);

      lastIterationTime += StepSize;
    }

    Draw();

  }

}

public void Update(TimeSpan stepSize) {

  // Update the positions of all entities in the game
  foreach(Entity entity in this.activeEntities)
    entity.Update(timePassed);

}

public void Draw() {

  // Put all entities which could potentially be seen by the
  // player into a list
  buildPotentiallyVisibleSet();

  // Render the entities in aforementioned list
  foreach(Entity entity in this.potentiallyVisibleEntities)
    entity.Render();

}

Game Loops in Microsoft's XNA Framework

By chance, the XNA framework provides a Game base class that already contains a main loop just like the one shown in this article, using the exact same names for the Draw() and Update() methods.

This base class is contained in the Microsoft.Xna.Framework.Game assembly and can be used like shown in the following example:

public class MyGame : Microsoft.Xna.Framework.Game {

  /// <summary>Initializes the game</summary>
  public MyGame() {
 
    // Configure the main loop to advance in fixed steps of 10 ms
    this.TargetElapsedTime = TimeSpan.FromMilliseconds(10);
    this.IsFixedTimeStep = true;

  }

  /// <summary>
  ///   This is called when the game should draw itself.
  /// </summary>
  /// <param name="gameTime">Provides a snapshot of timing values.</param>
  protected override void Draw(GameTime gameTime) {

    // Put all entities which could potentially be seen by the
    // player into a list
    buildPotentiallyVisibleSet();

    // Render the entities in aforementioned list
    foreach(Entity entity in this.potentiallyVisibleEntities)
      entity.Render();

  }

  /// <summary>
  ///   Allows the game to run logic such as updating the world,
  ///   checking for collisions, gathering input and playing audio.
  /// </summary>
  /// <param name="gameTime">Provides a snapshot of timing values.</param>
  protected override void Update(GameTime gameTime) {

    // Update the positions of all entities in the game
    foreach(Entity entity in this.activeEntities)
      entity.Update(gameTime);

  }

}

Now that you know how to set up a game loop, your next topic should be how to use the XNA framework to display 2D or 3D graphics on the screen!

Koen Witters's picture

Game Loops compared

Nice overview on how you can do things on XNA! If you want more info on different ways to implement the game loop, and compare them to each other, you can take a look at my game loop article.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Lines and paragraphs break automatically.
  • Allowed HTML tags: <br> <a> <em> <strong> <u> <i> <b> <cite> <blockcode> <code> <ul> <ol> <li> <dl> <dt> <dd> <p> <pre> <span>
  • You can highlight code with any of the following tags: <blockcode>

More information about formatting options