Hi,
what you have there is a way to measure elapsed time. This is sufficient if all your code is time-based. For example, if you want to move an object with a certain velocity, you could use good old physics like this:
position += elapsed_time * velocity;
But not all physics or simulations can easily be written like that. What if you have colliding objects? Or gravity? As you will probably know, a lot of simulations use simple Euler integration:
acceleration = force / mass;
velocity += acceleration;
position += velocity;
or sometimes Verlet integration:
auto velocity = current_position - previous_position;
previous_position = current_position;
current_position += velocity;
current_position += gravity;
Anyway, the point is that time is not used in these equations and it is expected that the simulation runs a fixed number of steps per second. While the above equations can be easily rewritten with a time component, it becomes way more complex when you start performing collision detection and response, or flocking, or magnetism, or rope physics. Anything where objects react to each other based on range. The above equations allow you to handle those situations easily, as long as they run, for example, 60 times a second.
Enter the following update loop:
void MyApp::update()
{
// Use a fixed time step for a steady 60 updates per second.
static const double timestep = 1.0 / 60.0;
// Keep track of time.
static double time = getElapsedSeconds();
static double accumulator = 0.0;
// Calculate elapsed time since last frame.
double elapsed = getElapsedSeconds() - time;
time += elapsed;
// Update your simulation/physics/animations/objects.
accumulator += math<double>::min( elapsed, 0.1 ); // prevents 'spiral of death'
while( accumulator >= timestep ) {
m_GameAppPS.Update( mIsPaused ? 0.0 : timestep );
accumulator -= timestep;
}
}
private:
bool mIsPaused;
The above code will make sure that the m_GameAppPS.Update()
method is always called exactly 60 times a second, regardless of the actual frame rate of your application. Or more precisely: it will be called 60 times per second on average. Sometimes it’s 59 times, sometimes 61, but measured over a long period of time it will be exactly 60 times per second.
If your frame rate is exactly 60 fps, update will be called once per draw (so the loop is similar to Cinder’s default loop: update-draw-update-draw). If your frame rate is 120, the update will be called every other frame (so the loop becomes: update-draw-draw). If it’s 30, the update will be called twice per frame (update-update-draw). If your frame rate drops to 6 fps, it will start skipping frames to prevent a so-called spiral of death. This should not happen in a well-tested application, of course, but will happen while debugging, so it’s nice that your application will be able to recover. You can test this yourself if you compile this Gist.
Why do I think this is better than using time? Because reasons. For example, Box2D or Bullet want you to call their update or simulate functions with a relatively steady time between calls. It results in smoother and more predictable physics. Your own equations become much simpler as well and you’re free to use whatever integration method you’d like. You never have to worry that your animations run too fast or too slow anymore.
Of course, you can still use time if you want to. My game objects all keep track of their own time. A double mTime
member variable is enough. This allows you to independently slow down, speed up, restart or delay objects, while you can still pause and resume your application. The update method of my objects looks a bit like this:
void update( double elapsed = 0.0 )
{
mTime += elapsed;
// Do something time-based, or perform a simple integration step.
// There are no limits here.
const int32_t kVelocityIterations = 3;
const int32_t kPositionIterations = 8;
mBox2dWorld->Step( elapsed, kVelocityIterations, kPositionIterations );
if( mTime >= 60.0 )
gotoNextScene();
}
private:
double mTime;
Pausing your application is easy, too. Either stop calling the m_GameAppPs.Update()
entirely, or (recommended) call it with an elapsed time of 0 seconds.
Slow-motion? No problem:
mTime += 0.2 * elapsed;
Want to restart the timer?
mTime = 0.0;
Want to add a delay?
void start( double delay )
{
mTime = -delay;
mIsRunning = true;
}
void update( double elapsed )
{
if( mIsRunning )
mTime += elapsed;
double time = glm::max( 0.0, mTime );
}
private:
double mTime;
bool mIsRunning;
Love using Cinder’s Timeline
? With good reason. Just add your own timeline member variable and do this:
void update( double elapsed )
{
mTimeline->step( elapsed );
}
void fadein( float duration )
{
mTimeline->apply( &mAlpha, 1.0f, duration );
}
private:
ci::TimelineRef mTimeline;
ci::Anim<float> mAlpha;
As you can see, you have total flexibility and control over the animations and simulations in your application.
Sorry for the long post, but hopefully it comes in handy. Oh, by the way, the update loop was inspired by this great article that is definitely worth a read.
-Paul