Handling game time is problem that almost all games have to deal with. A very good question recently came up in my C++ game from scratch tutorial about why I didn’t normalize the players movements which reminded me of a post on the subject.
Years ago Shawn Hargreaves of XNA ( and Allegro ) fame blogged about how XNA handles the Update and Render loops. I simply post it here again because it is one of the most succinct posts I have encountered on the subject. So, if you are looking into creating a game and need to figure out how to implement the game loop, I recommend you give it a read.
By default XNA runs in fixed timestep mode, with a TargetElapsedTime of 60 frames per second. This provides a simple guarantee:
- We will call your Update method exactly 60 times per second
- We will call your Draw method whenever we feel like it
Digging into exactly what that means, you will realize there are several possible scenarios depending on how long your Update and Draw methods take to execute.
The simplest situation is that the total time you spend in Update + Draw is exactly 1/60 of a second. In this case we will call Update, then call Draw, then look at the clock and notice it is time for another Update, then Draw, and so on. Simple!
What if your Update + Draw takes less than 1/60 of a second? Also simple. Here we call Update, then call Draw, then look at the clock, notice we have some time left over, so wait around twiddling our thumbs until it is time to call Update again.
What if Update + Draw takes longer than 1/60 of a second? This is where things get complicated. There are many reasons why this could happen:
- The computer might be slightly too slow to run the game at the desired speed.
- Or the computer might be way too slow to run the game at the desired speed!
- The computer might be basically fast enough, but this particular frame might have taken an unusually long time for some reason. Perhaps there were too many explosions on screen, or the game had to load a new texture, or there was a garbage collection.
- You could have paused the program in the debugger.
We do the same thing in response to all four causes of slowness:
- Set GameTime.IsRunningSlowly to true.
- Call Update extra times (without calling Draw) until we catch up.
- If things are getting ridiculous and we are too far behind, we just give up.
If you think about how this algorithm deals with the four possible causes of slowdown I listed above, you’ll see it handles them all rather well:
- Even if the computer is too slow to run Update + Draw inside a single frame, chances are that Update + Update + Draw will fit into two frames. The game may look a little jerky, but it will still play correctly. If the programmer is particularly smart they might even notice that we set IsRunningSlowly, and automatically reduce detail or turn off special effects to speed things up.
- If the computer is way too slow (ie. if the Update method alone is too slow to fit into a single frame) there isn’t really much we can do, other than set IsRunningSlowly, cross our fingers, and hope the game might do something clever in response to that. Most of the time, though, if you find yourself in this situation you just need a faster computer!
- If a particular frame happens to take unusually long, we automatically call Update some extra times to catch up, after which everything carries on as normal. The player may notice a slight glitch, but we automatically correct for this to minimize the impact.
- Pausing in the debugger will leave the game a long way behind where the clock says it should be, so our algorithm will give up, accept that it has lost some time, and continue running smoothly from the time execution was resumed.
Shawn’s blog in general is a very good read, but this this post in particular is one that I seem to keep coming back to.
For the record, Pang from the C++ tutorial does not gracefully handle hitting a breakpoint ( it adds a layer of unwanted complexity for learning purposes ), so will do downright stupid things when it hits a breakpoint causing it to pause for a while. I believe on the next frame it will cause your movement to be really really really far. It would be as simple as adding if(elapsedTime > someReallyBigValue) startTimeOver() if you really wanted to fix this.
Sorry for digging up the past, now back to your regular broadcast!