When writing a game, it's tempting to make an animation advance by one step every time the main loop runs.
For example, you might have something like:
Angle# = Angle# + 2.5
That works perfectly well; until the frame rate changes.
If the game is running at 60 frames per second, the animation advances 60 times every second. But if the same program drops to 30 frames per second, it only advances half as often.
The result is that the animation itself now runs at half speed.
This is where DeltaTime becomes useful.
Instead of asking:
"How many frames have passed?"
we can ask:
"How much actual time has passed?"
Once the animation is based upon elapsed time, the physical frame rate becomes largely irrelevant to its speed.
Thinking In Time
In this example, we remember the time at which the animation started:
StartTime = Timer()
Then, inside our main loop, we can calculate how much time has passed:
TimePast# = Timer() - StartTime
Timer() gives us the current time in milliseconds, so `TimePast#` represents the number of milliseconds since the animation began.
The next step is to convert that elapsed time into the equivalent number of frames.
If our ideal game frame rate is 60 FPS, then each frame represents:
1000.0 / 60.0
or approximately 16.67 milliseconds.
So we can calculate the equivalent frame number with:
Frame = TimePast# / (1000.0 / 60.0)
Now `Frame` isn't necessarily the actual number of frames the computer has rendered.
It's the number of 60 FPS frames that would have passed during that amount of time.
That's an important distinction.
Why This Works
Imagine the animation starts at exactly the same time on two different machines.
One machine manages to render the game at 60 FPS.
The other can only manage 30 FPS.
After one second, both machines have experienced approximately 1000 milliseconds of real time.
Our calculation therefore produces approximately:
1000 / 16.67 = 60 frames
for both machines.
The 60 FPS machine may have actually rendered around 60 frames.
The 30 FPS machine may have only rendered around 30.
But both animations have progressed by the same amount because the animation is being driven by elapsed time, not by the number of times the main loop happened to execute.
This gives us a simple way of separating the speed of an animation from the frame rate used to display it.
Applying It To The Animation
Once we have our virtual frame number, we can use it anywhere we would normally use a frame counter.
In this example, the animation changes the radius of a circle:
The angle is effectively saying:
"At this point in time, where would this animation be if it were running at our ideal 60 FPS?"
We can then use that angle to calculate the current radius of the circle.
The actual object could just as easily be a sprite, character, camera, particle, UI element, colour value, or pretty much anything else that changes over time.
The Complete Example
Here's the complete PlayBASIC example:
It Doesn't Have To Be 60 FPS
There's nothing particularly special about 60 FPS here.
We're simply using 60 FPS as our reference frame rate.
You could instead define it as:
GAME_FRAME_RATE = 30
or:
GAME_FRAME_RATE = 120
and calculate the duration of an ideal frame from that.
The important thing is that the animation has a consistent relationship with time, rather than depending upon how quickly the main loop happens to execute.
A Small Change With A Big Benefit
This is one of those techniques that can seem unnecessarily complicated when you're first writing a game.
If your program always runs at 60 FPS, simply advancing an animation every frame appears to work perfectly well.
But as soon as frame rates become variable, the difference becomes obvious.
By introducing elapsed time into the equation, we can make our animations behave consistently across different machines and different frame rates.
The computer is still drawing frames as quickly as it can.
We're simply no longer allowing the number of frames it draws to dictate how fast time appears to pass inside our game.
For example, you might have something like:
Angle# = Angle# + 2.5
That works perfectly well; until the frame rate changes.
If the game is running at 60 frames per second, the animation advances 60 times every second. But if the same program drops to 30 frames per second, it only advances half as often.
The result is that the animation itself now runs at half speed.
This is where DeltaTime becomes useful.
Instead of asking:
"How many frames have passed?"
we can ask:
"How much actual time has passed?"
Once the animation is based upon elapsed time, the physical frame rate becomes largely irrelevant to its speed.
Thinking In Time
In this example, we remember the time at which the animation started:
StartTime = Timer()
Then, inside our main loop, we can calculate how much time has passed:
TimePast# = Timer() - StartTime
Timer() gives us the current time in milliseconds, so `TimePast#` represents the number of milliseconds since the animation began.
The next step is to convert that elapsed time into the equivalent number of frames.
If our ideal game frame rate is 60 FPS, then each frame represents:
1000.0 / 60.0
or approximately 16.67 milliseconds.
So we can calculate the equivalent frame number with:
Frame = TimePast# / (1000.0 / 60.0)
Now `Frame` isn't necessarily the actual number of frames the computer has rendered.
It's the number of 60 FPS frames that would have passed during that amount of time.
That's an important distinction.
Why This Works
Imagine the animation starts at exactly the same time on two different machines.
One machine manages to render the game at 60 FPS.
The other can only manage 30 FPS.
After one second, both machines have experienced approximately 1000 milliseconds of real time.
Our calculation therefore produces approximately:
1000 / 16.67 = 60 frames
for both machines.
The 60 FPS machine may have actually rendered around 60 frames.
The 30 FPS machine may have only rendered around 30.
But both animations have progressed by the same amount because the animation is being driven by elapsed time, not by the number of times the main loop happened to execute.
This gives us a simple way of separating the speed of an animation from the frame rate used to display it.
Applying It To The Animation
Once we have our virtual frame number, we can use it anywhere we would normally use a frame counter.
In this example, the animation changes the radius of a circle:
PlayBASIC Code:
Speed# = 2.5 Angle# = WrapAngle(Frame * Speed#) Radius# = 50 + Sin(Angle#) * 40
The angle is effectively saying:
"At this point in time, where would this animation be if it were running at our ideal 60 FPS?"
We can then use that angle to calculate the current radius of the circle.
The actual object could just as easily be a sprite, character, camera, particle, UI element, colour value, or pretty much anything else that changes over time.
The Complete Example
Here's the complete PlayBASIC example:
PlayBASIC Code:
; Remember the time this loop started as were going to ; use it to compute delta time during the main loop StartTime =timer() ; Main loop do ; Clear screen to black (default rgb colour) cls ; Elapsed time - start of current frame TimePast#=Timer()-Starttime ; Convert the Time past into frame index (at a frame rate of 60fps)) Frame = TimePast# /(1000.0/60.0) ; Animation speed (degrees changed per frame) Speed# = 2.5 ; Compute the animation angle ; frame * speed wraped back into 0-359 degrees Angle# = Wrapangle(Frame * Speed#) ; Use animation angle to compute radius of circle Radius# = 50+sin(Angle#)*40 ; draw a circle in the center of screen of default screen ; at the current size this frame. circle 400,300,Radius#,true ; show the newly drawn frame to the user sync ; loop back until the user hits the space bar loop spacekey()
It Doesn't Have To Be 60 FPS
There's nothing particularly special about 60 FPS here.
We're simply using 60 FPS as our reference frame rate.
You could instead define it as:
GAME_FRAME_RATE = 30
or:
GAME_FRAME_RATE = 120
and calculate the duration of an ideal frame from that.
The important thing is that the animation has a consistent relationship with time, rather than depending upon how quickly the main loop happens to execute.
A Small Change With A Big Benefit
This is one of those techniques that can seem unnecessarily complicated when you're first writing a game.
If your program always runs at 60 FPS, simply advancing an animation every frame appears to work perfectly well.
But as soon as frame rates become variable, the difference becomes obvious.
By introducing elapsed time into the equation, we can make our animations behave consistently across different machines and different frame rates.
The computer is still drawing frames as quickly as it can.
We're simply no longer allowing the number of frames it draws to dictate how fast time appears to pass inside our game.
