Gamasutra: The Art & Business of Making Gamesspacer
View All     RSS
September 26, 2017
arrowPress Releases






If you enjoy reading this site, you might also want to check out these UBM Tech sites:


 

Fixing your time step, the easy way with the golden 4⅙ ms.

by Bram Stolk on 04/08/16 01:44:00 pm   Featured Blogs

The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.

 

For your game, you somehow need to harmonize the display intervals from your monitor/renderer on the one hand, and the simulation intervals on the other hand.

Getting this wrong is easy, as expertly explained in Glenn Fiedler in his seminal article titled 'Fix Your Timestep!'

A trivial approach to measure display period, and use that as simulation step is typically ill-advised, as physics engines don't like varying time steps. I use Chipmunk2D and OpenDE, and both these engines warn against using timesteps that are not fixed. Additionally, if a game has to be deterministic, you can't afford varying timesteps either.

Glenn's final solution is to have the render time accumulate and only when there is enough time accumulated for a simulation step, the sim (physics,ai,etc) is triggered.

For a perfectly smooth movement, the actual positions and orientations are interpolated from two timestamped world states. It's not immediately clear from his code example, but the interpolated final result, used for rendering, lags a full 'sim' frame. To avoid this lag, one could opt for extrapolation instead, which brings other issues with it.

I think we can improve on this with an alternate approach: I suggest to step to the simulation with a 4⅙ ms period, which is 240Hz. And we will let the display/render timing dictate how many of those steps to take. Further, I suggest to blatantly pretend that all displays do either 30Hz, 34.29Hz, 40Hz, 48Hz, 60Hz, 90Hz, 120Hz or 240Hz. Then we do 8,7,6,5,4,3,2 or 1 sim step(s) with this fixed 4⅙ ms.

If the display period falls between these perfectly matching frequencies, we will simply discard the difference thereby speeding up or slowing down the entire game.

In code, determining the number of steps to take each frame looks like this (I wrote down a lot of explicit tests as opposed to a single expression, to clearly show all situations possible.)

static inline int num_240Hz_steps( double elapsed )
{
        // Our simulation frequency is 240Hz, a 4⅙  (four one sixth) ms period.
        double e = 1 / 240.0;

        // We will pretend our display sync rate is one of these:
        if ( elapsed > 7.5 * e )
                return 8;                       // 30 Hz        ( .. to 32 Hz )
        else if ( elapsed > 6.5 * e )
                return 7;                       // 34.29 Hz     ( 32 Hz to 36.92 Hz )
        else if ( elapsed > 5.5 * e )
                return 6;                       // 40 Hz        ( 36.92 Hz to 43.64 Hz )
        else if ( elapsed > 4.5 * e )
                return 5;                       // 48 Hz        ( 43.64 Hz to 53.33 Hz )
        else if ( elapsed > 3.5 * e )
                return 4;                       // 60 Hz        ( 53.33 Hz to 68.57 Hz )
        else if ( elapsed > 2.5 * e )
                return 3;                       // 90 Hz        ( 68.57 Hz to 96 Hz )
        else if ( elapsed > 1.5 * e )
                return 2;                       // 120 Hz       ( 96 Hz to 160 Hz )
        else
                return 1;                       // 240 Hz       ( 160 Hz to .. )
}

So what do we win with this, compared to interpolation-using-accumulated-time in Glenn Fiedler's article?

  1. We no longer need to do double bookkeeping of the current state and previous state of the world. This bookkeeping can get tricky as it needs to deal with objects that may not exist in state N-1, but do in state N, or vice versa.
  2. We no longer suffer a full simulation step of lag.
  3. We will have a more even load on the CPU. If we were to use the accumulator approach with fast gamer display of let's say 144Hz and a sim step of 30Hz, then most of the displayed frames will do no simulation, and we would see infrequent CPU spikes instead.

And what do we lose?

  1. Game speed is no longer constant: typically a no-go for networked multi player games.
  2. Doing more physics steps (because of the small period) means a higher load on the CPU overall (Even though this load is more even.)
  3. No Bullet-time or slow-motions, and possibly slightly less smooth than the interpolated physics states.

If you have CPU cycles to spare (because your physics may be 2D e.g.) then you can alleviate the first issue by going even smaller with the simulation period. When running at 480Hz, or a 2𐧶ms (two one twelfth) period, the game speed variation will be lower. The code for that period looks like this:

static inline int num_480Hz_steps( double elapsed )
{
        // Our simulation frequency is 480Hz, a 2𐧶 (two one twelfth) ms.
        double e = 1 / 480.0;

        // We will pretend our display sync rate is one of these:
        if ( elapsed > 15.5 * e )
                return 16;                      // 30 Hz        ( .. to 30.97 Hz )
        else if ( elapsed > 14.5 * e )
                return 15;                      // 32 Hz        ( 30.97 Hz to 33.10 Hz )
        else if ( elapsed > 13.5 * e )
                return 14;                      // 36.92 Hz     ( 33.10 Hz to 38.4 Hz )
        else if ( elapsed > 12.5 * e )
                return 13;                      // 40 Hz        ( 38.4 Hz to 41.74 Hz )
        else if ( elapsed > 11.5 * e )
                return 12;                      // 43.64Hz      ( 41.74 Hz to 45.71 Hz )
        else if ( elapsed > 10.5 * e )
                return 11;                      // 48 Hz        ( 45.71 Hz to 50.53 Hz )
        else if ( elapsed >  9.5 * e )
                return 10;                      // 53.33 Hz     ( 50.53 Hz to 56.47 Hz )
        else if ( elapsed >  8.5 * e )
                return 9;                       // 60 Hz        ( 56.47 Hz to 64 Hz )
        else if ( elapsed >  7.5 * e )
                return 8;                       // 68.57 Hz     ( 64 Hz to 73.85 Hz )
        else if ( elapsed >  6.5 * e )
                return 7;                       // 80 Hz        ( 73.85 Hz to 87.27 Hz )
        else if ( elapsed >  5.5 * e )
                return 6;                       // 96 Hz        ( 87.27 Hz to 106.67 Hz )
        else if ( elapsed >  4.5 * e )
                return 5;                       // 120 Hz       ( 106.67 Hz to 137.14 Hz )
        else if ( elapsed >  3.5 * e )
                return 4;                       // 160 Hz       ( 137.14 Hz to 192 Hz )
        else if ( elapsed >  2.5 * e )
                return 2;                       // 240 Hz       ( 192 Hz to 320 Hz )
        else if ( elapsed >  1.5 * e )
                return 2;                       // 480 Hz       ( 320 Hz to .. )
        else
                return 1;
}

So there you have it: an easy way to smooth simulation, without sacrificing compatibility with fast display cycles, and no need for double state bookkeeping. Let me know what you think in the comment section below.

Bram Stolk

 


Related Jobs

Deck Nine Games
Deck Nine Games — Westminster, Colorado, United States
[09.25.17]

Mobile Programmer
Deck Nine Games
Deck Nine Games — Westminster, Colorado, United States
[09.25.17]

Senior Console Programmer
DigitalFish, Inc.
DigitalFish, Inc. — San Mateo, California, United States
[09.25.17]

Software Engineer - Machine Learning
DigitalFish, Inc.
DigitalFish, Inc. — Mountain View, California, United States
[09.25.17]

Software Engineer - AR/VR





Loading Comments

loader image