7 min read

Cracking the GameTime Problem

A Revealing Experiment

There’s a “problem” with XNA’s timing that many people run into. Often before they’re even done with their first game. The purpose of this tutorial is to introduce the problem to you (if you haven’t already found the problem in your own game, and introduced yourself), describe what’s actually happening, and give you some information about how to address it.

I’ll start with a simple example. To recreate the problem, all we need is a game that is using a fixed time step (the default) and runs slowly. To really see what’s going on, we’ll want to also see what kind of timing information we’re given in the Update and Draw methods, whenever they’re called.

Starting from scratch, I’ve created a new game with the following Update and Draw methods:

protected override void Update(GameTime gameTime)
{
    for (int index = 0; index < 10000000; index++)
    {
        index++;
        index--;
    }

    Console.WriteLine("Update: {0} (+{1})",
            gameTime.TotalGameTime.TotalSeconds,
            gameTime.ElapsedGameTime.TotalSeconds);
             
    base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    Console.WriteLine("Draw: {0} (+{1})",
            gameTime.TotalGameTime.TotalSeconds,
            gameTime.ElapsedGameTime.TotalSeconds);

    base.Draw(gameTime);
}

If you look at the code above, you can see that in the Update method I’ve got a loop that does essentially nothing, but it does it for long enough that the game is bound to run slower than the desired 60 frames per second. You can also see that in each update or draw, we display the current game time (total time since the start of the game) as well as how much time has elapsed since the last update.

If you run this code, what would you expect to see?

Probably not what you actually see. Here’s what is displayed:

Update: 0 (+0)
Update: 0 (+0.0166667)
Draw: 0.0166667 (+0.0166667)
Update: 0.0166667 (+0.0166667)
Update: 0.0333334 (+0.0166667)
Update: 0.0500001 (+0.0166667)
Update: 0.0666668 (+0.0166667)
Update: 0.0833335 (+0.0166667)
Draw: 0.1000002 (+0.0833335)
Update: 0.1000002 (+0.0166667)
Update: 0.1166669 (+0.0166667)
Update: 0.1333336 (+0.0166667)
Update: 0.1500003 (+0.0166667)
Update: 0.166667 (+0.0166667)
Update: 0.1833337 (+0.0166667)
Update: 0.2000004 (+0.0166667)
Update: 0.2166671 (+0.0166667)
Update: 0.2333338 (+0.0166667)
Update: 0.2500005 (+0.0166667)
Update: 0.2666672 (+0.0166667)
Update: 0.2833339 (+0.0166667)
Update: 0.3000006 (+0.0166667)
Update: 0.3166673 (+0.0166667)
Update: 0.333334 (+0.0166667)
Update: 0.3500007 (+0.0166667)
Update: 0.3666674 (+0.0166667)
Update: 0.3833341 (+0.0166667)
Update: 0.4000008 (+0.0166667)
Update: 0.4166675 (+0.0166667)
Update: 0.4333342 (+0.0166667)
Update: 0.4500009 (+0.0166667)
Update: 0.4666676 (+0.0166667)
Draw: 0.4833343 (+0.3833341)

When you run this code, you’ll notice several things that seem out of place.

First, WHY ARE THERE SO MANY STINKIN’ UPDATES?! In everything we’ve done before, we’ve come to expect one update, followed by one draw. And we’re just simply not seeing that here. There are tons of calls to Update, and only an occasional Draw call.

Second, even though it says 0.166667 seconds have passed between updates, it is taking far longer than that to actually do an update. Why on Earth does it only say 0.0166667 seconds passed?!

When you first stumble into this, most people are left scratching their heads.

But rest assured, this isn’t a bug. It’s just a common misunderstanding of what’s going on, and it has a perfectly logical explanation.

The Explanation

To help us get to the bottom of this, it is worth pointing out that we only see this happen when running with a fixed time step, and only when it is taking too long to get through a cycle of the game loop (caused by too much in the Draw method, or too much in the Update method). So we know there’s something unique about fixed time steps and running slowly.

To begin our discussion of what’s going on, I want to point out the name of the type of variable that we’re using: GameTime. That’s actually fairly meaningful here and will go quite a ways to help us understand. People have a tendency to assume that this is the same as “wall time.” That if 1 second passes on the clock on the wall during an update, gameTime should report 1 second passed between updates. But that’s now how it works when a fixed time step game is running slowly.

Game time makes more sense when you put it in the context of being in the game world. Compare this to positions in the game world. You have an object in the game world that is located at, say, (3, 4, -9). The object doesn’t have a position in the real world (outside of the game). It only exists within the game, and that doesn’t necessarily correspond to anything from outside of the game world. Game time is a similar thing. It represents how much time has passed in the game world, not in the real world. (I use that phrase a little loosely because you can still define an alternate time frame within your game, allowing days to pass within a few “real” minutes, or having turns represent years or decades, like Civilization does.)

The point is, this is game time, not wall-clock time, and the name of the type clearly indicates so. In many cases, the two are the same, but that’s not always the case; if a fixed time step game is running slowly, the two will be different.

So that explains why GameTime reports a different time than what we were expecting, but that still doesn’t explain why there are so many updates.

Interestingly, if you’re thinking that, you’ve probably got it backward. Rather than doing lots of updates, XNA is actually skipping draw calls, in an attempt to get caught back up. It’s making the assumption that the drawing was the slow part, not the updating.

That may or may not be true, but skipping updates buys us nothing. We’d end up drawing the exact same thing repeatedly, with no changes between what’s drawn. (You’re not doing any updating in the Draw method, are you?) Furthermore, skipping updates would mean we no longer have a fixed time step. Doing so breaks the commitment we’ve made to do so. Because it doesn’t make sense to skip update calls, and it would only serve to break the idea of a fixed time step, we only skip draw calls.

One side note to that is, as far as I can tell, XNA will always do a draw every 0.5 seconds of game time anyway, even if it is still behind. If your target frame rate is less than 2 frames per second, you should still see all of your draw calls. (But if your target frame rate is 2 FPS, you’re doing something wrong….) Doing these occasional draw calls will force the screen to occasionally update, even if it is running really slow. At the very least, it provides programmers/players with a sanity check that the game is running slowly, not dead.

The Solution: Using A Variable Time Step

caution

The meat of this tutorial was written a very long time ago, and I want to point out that there are some compelling reasons to not make a variable time step. The following link describes some of the problems with variable time steps pretty well, and is worth a read: https://gafferongames.com/post/fix_your_timestep/.

Once people realize and begin to understand the problem, their next thought is, “Well how do I get it to report wall clock time instead of game time?” I’ve seen a lot of people respond to this question by saying, “Use the Stopwatch class or the Timer class.” Fair enough. Either of those could be used to get the equivalent of wall time.

But in my opinion, both of those are sort of missing the point. Instead of that, you should just use a variable time step, which doesn’t have these problems. In fact, it is worth noting that in their documentation for GameTime, Microsoft calls variable time step “real-time”, and indicates that a fixed time step is often used in simulations or offline rendering.

With a variable time step, there’s no delay between frames, regardless of if it is being slow or not. It just goes as fast as it can. This means game time will accurately match wall time. Like we discussed in the previous tutorial, this means more complicated coding, but it will get around this game time vs. wall time problem.

The easiest and best solution is to simply set up your game to use a variable time step as we discussed in the last tutorial.

One final thing that I want to point out before wrapping up here is that this problem is even more evidence for keeping your updating code and drawing code separate. Update and Draw are not always guaranteed to be called back to back, or even the same number of times. Drawing code should only draw the current state of the game, and updating code should only advance the state of the game without doing any drawing.