Computer Science Canada

Delay-Free Animation

Author:  Insectoid [ Wed Jun 12, 2013 10:57 pm ]
Post subject:  Delay-Free Animation

Most of the projects that get posted here have at least one delay in them to attempt to regulate the game speed. This may work on your computer but as soon as you try to share it, your timing will be completely messed up due to the different clock speeds. This can be mitigated somewhat with Time.DelaySinceLast, but why not let your computer do something in that spare time instead of waiting? Why design your program around a static framerate when you could just let it run as fast as possible? Instead of waiting to draw the next frame, why not just draw more frames?

This is relatively easy to do, though it does take a little extra work. Most students will assign their objects a static speed- maybe 10 pixels per frame. We're going to do the same, except we'll measure our speed in pixels per second. In our game loop, we will calculate how much time has passed since our last frame and then decide how far our object should have moved in that time.

Let's start with a couple variables:
Turing:
var x : real := 50
var y : real := 50
var speed : int := 100 %speed in pixels per second


I'm sure you recognize X and Y, although usually they're integers. In this example, we'll be using reals because our objects can actually move less than a pixel per frame. Our speed in pixels per second is one hundred. It should take us about 4 seconds to move from the bottom of the standard output window to the top, regardless of how fast your computer is.

Now we need a way to decide how much time has elapsed between frames. Turing has a Time.Elapsed function that shows how long the program has been running. If we record the Time.Elapsed in one frame, and then the next frame record it again, we can subtract the first from the second and get the time in between.

Turing:
var timeAtLastFrame : int := 0 % this global variable records the time elapsed in the previous frame
function timeSinceLast : int %this function returns the time elapsed since the last time this function was called.
    var t : int := Time.Elapsed %here we get the time since the program started
    var out : int := t - timeAtLastFrame %subtract the time at the last frame from the current time
    timeSinceAtFrame := t %record the current time so that we have it for the next frame
    result out
end timeSinceLast


Now that we can calculate our time between frames, we can start moving things. In your bog-standard animations, you simply add your speed to your x or y variable: x := x + speed. We do the same thing here, but first we have to multiply the speed by the number of seconds since the last frame. Also, since our speed is in pixels per second and our time is in milliseconds, we need to divide it by one thousand. So to move up, our equation would look like 'y := y + speed * time_since_last_frame / 1000.0'.
Turing:
if keys (KEY_UP_ARROW) then
        y += speed * t / 1000.0 %t is our time since the last frame
    elsif keys (KEY_DOWN_ARROW) then
        y -= speed * t / 1000.0
    end if


Now all that's left to do is put it all together. I've added a variable delay so you can see what it would look like on a slower computer. Press 'w' to increase the delay by 10ms and 's' to decrease it. Use the arrow keys to move around. There's a timer at the top so you can see that the time it takes to move across the screen doesn't change, no matter the delay.
Turing:
%Delay-free animation
View.Set ('offscreenonly')

var x : real := 50
var y : real := 50
var speed : int := 100 %speed in pixels per second

var _delay : int := 0
var _timeSinceLast : int := 0
var t : int

var keys : array char of boolean

function timeSinceLast : int
    var t : int := Time.Elapsed
    var out : int := t - _timeSinceLast
    _timeSinceLast := t
    result out
end timeSinceLast

loop
    t := timeSinceLast
    Input.KeyDown (keys)
   
    if keys (KEY_UP_ARROW) then
        y += speed * t / 1000.0
    elsif keys (KEY_DOWN_ARROW) then
        y -= speed * t / 1000.0
    end if
   
    if keys (KEY_LEFT_ARROW) then
        x -= speed * t / 1000.0
    elsif keys (KEY_RIGHT_ARROW) then
        x += speed * t / 1000.0
    end if
    if keys ('w') then
        _delay += 10
    elsif keys ('s') and _delay > 0 then
        _delay -= 10
    end if
    delay (_delay)
    cls
    locate (1, 1)
    put Time.Elapsed/1000.0
    Draw.FillOval (round (x), round (y), 10, 10, red)
    View.Update
end loop

Author:  Insectoid [ Wed Jun 12, 2013 11:32 pm ]
Post subject:  RE:Delay-Free Animation

You can also apply this to variable-velocity movement models, where the object accelerates instead of moving at a constant speed. In this case, we swap out our speed variable for an acceleration variable measured in pixels per second squared. We also gain X and Y velocity variables measured in pixels per second. This again takes a little more work- we need to account for time elapsed when calculating how much we've accelerated in this frame as well as how far we've moved. In this sample, you can press the spacebar to return the ball to the starting position in case you lose it.
Turing:
%Delay-free animation
View.Set ('offscreenonly')

var x : real := 50
var y : real := 50
var velX : real := 0 %velocity in pixels per second
var velY : real := 0
var acceleration : int := 400 %acceleration in pixels per second
var deceleration : int := 200 %deceleration in pixels per second, for when no keys are pressed.

var _delay : int := 0
var _timeSinceLast : int := 0
var t : int

var keys : array char of boolean

function timeSinceLast : int %this hasn't changed
    var t : int := Time.Elapsed
    var out : int := t - _timeSinceLast
    _timeSinceLast := t
    result out
end timeSinceLast

loop
    t := timeSinceLast
    Input.KeyDown (keys)
   
    if keys (KEY_UP_ARROW) then
        velY += acceleration * t / 1000.0 %here we add our acceleration corrected for time to our velocity
    elsif keys (KEY_DOWN_ARROW) then
        velY -= acceleration * t / 1000.0
    else %if no key is pressed, we slow down
        if velY > 0 then
            velY -= deceleration * t / 1000.0 %reduce velocity if it's positive, again corrected for time
        elsif velY < 0 then
            velY += deceleration * t / 1000.0 %increase velocity if it's negative
        end if
    end if
   
    if keys (KEY_LEFT_ARROW) then %same thing for the X axis
        velX -= acceleration * t / 1000.0
    elsif keys (KEY_RIGHT_ARROW) then
        velX += acceleration * t / 1000.0
    else
        if velX > 0 then
            velX -= deceleration * t / 1000.0
        elsif velX < 0 then
            velX += deceleration * t / 1000.0
        end if
    end if
   
    if keys ('w') then
        _delay += 10
    elsif keys ('s') and _delay > 0 then
        _delay -= 10
    end if
   
    if keys (' ') then %this returns the ball to the starting position when the spacebar is pressed.
        x := 50
        y := 50
        velX := 0
        velY := 0
    end if
   
    %When decelaration is corrected for time, we often end up bouncing between very small positive and negative velocities, so the ball never stops.
    %This corrects it. When the velocity is smaller than the smallest possible deceleration corrected for time, we set it to 0. Using some kinematic
    %equations, we could also set the X and Y values to exactly where the ball should be in this time, but that's not important for this demonstration.
    if velX < deceleration*t/1000 and velX > -deceleration*t/1000 then
        velX := 0
    end if
    if velY < deceleration*t/1000  and velY > -deceleration*t/1000 then
        velY := 0
    end if
   
    y += velY * t / 1000.0 %Finally, we add the velocity corrected for time to the X and Y values.
    x += velX * t / 1000.0
    delay (_delay)
    cls
    locate (1, 1)
    put Time.Elapsed/1000.0
    Draw.FillOval (round (x), round (y), 10, 10, red)
    View.Update
end loop

Author:  Tony [ Wed Jun 12, 2013 11:44 pm ]
Post subject:  RE:Delay-Free Animation

Nice.

A caveat is that this assumes constant speed. Things get somewhat more complicated when one wants to animate say... a jump. (edit: that has already been addressed above)

The basic idea is still the same -- figure out where along the expected trajectory the objects should be at time t, and draw them there. All the relevant formulas are available from a standard high school physics textbook.

One has to be careful though then steps average/round intermediate calculations. This shouldn't be a problem when the entire jump's trajectory can be calculated ahead of time, but if it's done step by step (because maybe there's in-air movement/collision), then one ends up with things like Quake3 where the frame rate affects the height of the jump -- http://www.psycco.de/125fps/UpsetChaps%20Quake3%20Guide%20-%20Why%20Your%20Framerate%20Affects%20Jumping.htm

Author:  Insectoid [ Wed Jun 12, 2013 11:55 pm ]
Post subject:  RE:Delay-Free Animation

I made sure only to round when actually drawing the object. All calculations are done on the raw floats, though there may be floating point errors involved.

Actually, I do round it off in deceleration, and this was causing an issue initially at high values of _delay as I mentioned in the comments in the code. I've already fixed that in my own code so the X & Y values are adjusted before setting velocity to 0.


: