Monday, 26 July 2010

Understanding Game Time Steps - Your 3 Options

I was just answering a question over at the gamedev Stack Exchange beta site and I realised I was pretty much writing a blog post about time steps for games. How you handle your time step has big implications on how your game architecture needs to be set up. There's a lot of confusion and misinformation in this space, but I think I've pretty much got my head around it. Here are your 3 options:

Option 1 - Do Nothing

Do nothing. Attempt to update and render at a certain interval, e.g. 30 times per second. If it falls behind, let it and don't worry. The game will slow down into jerky slow motion if the CPU can't keep up with your game, but there will be no jarring jumps across the screen. This option won't work at all for real-time multi-user games, but is fine for single player games and has been used successfully in many games.

Option 2 - Delta Time

Use the delta time between each update to vary the movement of objects. In Flash this is just a matter of calling getTimer() and deducting the value of the previous frame's getTimer() call. This tells you how much time has elapsed since the last frame. Now you can factor this value in to all your maths. Great in theory, especially if nothing in your game accelerates or decelerates, but just moves at a constant speed. In practice, many developers implement this badly, and it can lead to inconsistent collision detection and physics. It seems some developers think this method is easier than it is. If you want to use this option you need to step your game up considerably and bring out some big-gun maths and algorithms, for example using a Verlet physics integrator (rather than the standard Euler that most people use) and using rays for collision detection rather than simple Pythagoras distance checks. I asked a question about this on Stack Overflow a while back and got some great answers:

http://stackoverflow.com/questions/153507/calculate-the-position-of-an-accelerating-body-after-a-certain-time

Option 3 - Fix Your Time Step

Use Gaffer's "fix your time step" approach. Update the game in fixed steps as in option 1, but do so multiple times per frame rendered - based on how much time has elapsed - so that the game logic keeps up with real time, while remaining in discrete steps. This way, easy to implement game logic like Euler integrators and simple collision detection still work. You also have the option of interpolating graphical animations based on delta time, but this is only for visual effects, and nothing that affects your core game logic. You can potentially get in trouble if your updates are very intensive - if the updates fall behind, you will need more and more of them to keep up, potential making your game even less responsive. I'm now going to try and explain how to implement this in Flash - concentrate!

Firstly, one implication of this approach is that you cannot rely on ENTER_FRAME to update each object. You must have a single ENTER_FRAME for the whole game that loops through each object and calls a public update() method. Each ENTER_FRAME tick you may have to call these update functions 0, 1, 2, 3 or even more times in order to get back in sync with real time. To work out how many times to update, you call getTimer() and take away the value of getTimer() from the start of the game. This tells you how much time has elapsed since the game started. Now work out how up-to-date your game logic is, by multiplying the number times you have called update (you'll need to keep track of this) by the length of 1 frame (e.g. 1/30 seconds for 30fps) - this is how much time has passed in your game world. Compare how much time has elapsed in real life to how much has elapsed in the game world. If they are different by more than 1 time step (e.g. 1/30 seconds) then call update until they are in sync to within less than 1 time step. This may seem pretty full-on, but it's really pretty simple - I will try to get a code demo up soon.

Personally, I like Option 1 - Do Nothing, when I can get away with it, and Option 3 - Fix your Time Step, when I need to sync to real time. I respect that Option 2 can be a good option when you know what you're doing, but I know my limitations well enough to stay well away from it.

4 comments:

Mike said...

FWIW, I've found that option 2 with plain Euler integration is actually very safe for most games. You start running into problems only when a) your framerate varies wildly or b) you are trying to have very realistic physics. I've been getting away with it so far anyway :)

You an also use this trick to avoid the worst of Euler integration: http://www.niksula.cs.hut.fi/~hkankaan/Homepages/gravity.html

Al said...

Option 1 is essential option 2 with a deltaTime of 1. Using the deltaTime is really the only way to keep Flash game responsive.

Gaffers technique is great - but you must remember we have 1 thread in Flash and if we sit in a while loop (or multiple in the final method) for longer than the number of MS for a frame we end up in with a non-responsive game - as you mention.

By using deltaTime we are not limited to a Euler integrator, which as Mike says, is generally enough for simple games. For rigid-bodies, springs and so on we can use Runge-Kutta, LeapFrog (Velocity Verlet) and so on to perform each step. This keeps things from exploding due to inaccuracies.

Furthermore if you ever add a high-score to your game based on a timer, you must use deltaTime as there are tools to slow the CPU for cheating. As we found out on a certain soft-drink site.

Great site btw!

Iain said...

Thanks for your input Al. After Mike and your great contributions I think I will definitely take another look at delta time.

Fix your timestep also syncs with real time by the way, even if the CPU slowed right down to 1 fps, so that would have also stopped your cheats (and probably crashed their computer). Competitions shouldn't rely on client-side logic anyway as Flash is too easy to RAM hack etc.

Patrick said...
This comment has been removed by a blog administrator.