Posted: 27th Feb 2014 13:23
Hey guys,

just wanted to share another piece of code that provides a pretty stable method for timer based movement. It combines two different approaches: Scaling things using the time delta between two frames, and executing a fixed set of discrete steps for certain other operations.

The basic "library" (which mainly consists of an init and an update function) is very small and simple and doesn't really do much:

+ Code Snippet


remstart
    How to use this tiny library:
    *call game_init() once after starting your program
    *call game_update() somewhere in your main loop (preferably
     at the beginning, before updating anything else in the game)
    *You can use game_reset() if you wish to reset the library's
     timer back to 0 (e.g. after loading a new level)
    
    How to enable timerbased movement:
    *De/Incremental Changes to all continuous values (floats and 
     doubles) over time should be multiplied with game.timeFactor 
    *Noncontinuous values (or those that can't simply be scaled with 
     the timeFactor for other reasons) should be multiplied with
     game.fullUpdates (which is an int and usually 0 or 1, it only
     gets higher if the games runs with less FPS than defined in
     GAME__FULL_UPDATES_PER_SECOND)
    *For changes that can't even be scaled with game.fullUpdates,
     you may use a for loop ranging from 1 to game.fullUpdates
    *In theory you could just use game.fullUpdates for *everything*
     and leave game.timeFactor out completely, however, this would 
     mean that FPS rates above GAME__FULL_UPDATES_PER_SECOND would 
     not update any more often than that exact value each second.
     Hence the advantage of timerbased movement would only be
     given for slow computers, fast ones would not gain any benefit
    
    Note:
    *The constants below can be changed, however:
    *GAME__MS_PER_FULL_UPDATE * GAME__FULL_UPDATES_PER_SECOND should 
     always equal 1000!
    *GAME__FULL_UPDATES_PER_SECOND defines how many non-continuous 
     update steps are executed each secod
    *GAME__MAX_TIME_DIF defines an upper boundary for the virtual
     time passing between two frames. This effectively means that
     if the game runs slower than this value allows (i.e. the game
     loop requires more ms than this value), it will not be "timer
     based" anymore but slow down. This is meant to prevent extreme
     "jumping" of objects, which might allow player characters to
     clip through walls etc. You can change this value, but I'd
     recommend to not use anything above 100.
    *you can access the game.runtime variable to avoid calling the
     timer() function regularly. Note that it always starts at 0
     though.
remend

rem Only change these constants if you know what you're doing (and 
rem if you've read the paragraphs above)

#constant GAME__MS_PER_FULL_UPDATE = 20
#constant GAME__FULL_UPDATES_PER_SECOND = 50
#constant GAME__MAX_TIME_DIF = 100




type game_Type
    systimer as integer
    starttime as integer
    runtime as integer
    actualRuntime as integer
    timedif as integer `limited by GAME__MAX_TIME_DIF
    actualTimeDif as integer 
    timeFactor as float
    fullUpdates as integer `for high frame rates usually 0, but 50 times per second 1; for FPS lower than 30 this number will occasionally be higher than one for apply multiple updates at once
    nextFullUpdate as integer
    fullUpdateSum as integer
endtype

function game_init()
    global game as game_Type
    game.starttime = timer()
    game.runtime = 0
    game.nextFullUpdate = 0
    game.actualRuntime = 0
    game.fullUpdateSum = 0
endfunction

function game_reset()
    game_init()
endfunction

function game_update()
    game.systimer = timer()
    tprev = game.actualRuntime
    game.actualRuntime = game.systimer - game.starttime
    game.timedif = game.actualRuntime - tprev
    game.actualTimeDif = game.timedif
    if game.timedif > GAME__MAX_TIME_DIF then game.timedif = GAME__MAX_TIME_DIF
    inc game.runtime, game.timedif
    game.timeFactor = game.timedif/(1.0*GAME__MS_PER_FULL_UPDATE)
    game.fullUpdates = 0
    if game.runtime >= game.nextFullUpdate
        dif = game.runtime - game.nextFullUpdate
        updates = 1 + dif/GAME__MS_PER_FULL_UPDATE
        if updates > 10
            game.fullUpdates = 10
            game.nextFullUpdate = game.runtime + GAME__MS_PER_FULL_UPDATE
        else
            inc game.nextFullUpdate, updates*GAME__MS_PER_FULL_UPDATE
            game.fullUpdates = updates
        endif
    endif
    inc game.fullUpdateSum, game.fullUpdates
endfunction



The usage is explained in the comments in the beginning. To summarize it very shortly:
-Call game_init() in the beginning of your program and game_update() in the main loop.
-When dealing with floats, you can just multiply any changes (e.g. movement speed of a character) with game.timeFactor
-When dealing with integers or more complex operations that need to be executed a fixed amount of times each frame, you can use game.fullUpdates, which is an integer and is usually either 0 or 1, but can also get higher in case the game's FPS rate falls below 50 (the exact configuration can be changed using the three constants in the code above).

Note that the GAME__MAX_TIME_DIF constant defines an upper boundary for the time delta between two frames. If the game gets slower than that, it will actually slow down instead of keeping everything timer based. This is meant to prevent "jumping" of values (and objects), which could for instance cause the player character to clip through walls.

I wrote a small example demonstrating how to use it exactly.


+ Code Snippet
Rem Project: timer based movement
Rem Created: Wednesday, February 26, 2014

Rem ***** Main Source File *****


sync on
sync rate 0
sync

game_init()


size = 250
make matrix 1, size,size, 50,50

position camera size/2, 5.0, -10.0

autocam off

objects = 300
for i = 1 to objects
    make object cube i, 1.0*(2+rnd(100)*0.02)
    position object i, rnd(size), 0, rnd(size)
    rotate object i, rnd(359), rnd(359), rnd(359)
next

limitedsyncrate = 0
hitpoints = 500

rem One Update immediately before the main loop can't hurt to avoid time jumps due to the loading process
game_update()

do
    
    rem Calling game_update() should happen early in the main loop
    game_update()
    
    rem Movement
    ud = keystate(17)-keystate(31)
    rl = keystate(32)-keystate(30)
    if ud or rl
        if ud
            move camera 1.0*ud*game.timeFactor
            remstart
                Alternatively you could do this:
                move camera ud*game.fullUpdates
                which is "integer-friendly" (as game.fullUpdates is not a float, as opposed to game.timeFactor)
                or even this:
                for i = 1 to game.fullUpdates
                    move camera ud
                next
                which in this case does the same as the line above, but might be required in some
                more complicated places where you can't just scale a value up but have to run a
                certain operation X times
            remend
        endif
        if rl
            rotate camera 0, ay#+90, 0
            move camera 1.0*rl*game.timeFactor
            rotate camera ax#, ay#, 0
        endif
    endif
    
    rem Mouse - Note: mousemovex()/y() don't need additional timing, they behave equally for all fps rates
    ax# = camera angle x() + 1.0*mousemovey()
    if ax# > 85 then ax# = 85 else if ax# < -85 then ax# = -85
    ay# = camera angle y() + 1.0*mousemovex()
    rotate camera ax#, ay#, 0
    
    
    rem Damage Player when leaving the map
    cx# = camera position x() : cz# = camera position z()
    if cx# < 0 or cx# > size or cz# < 0 or cz# > size
        remstart 
            Hitpoints is an integer, hence decrementing it by something*game.timeFactor
            would not work properly (as is would mostly be rounded down to 0 and not
            change the hitpoints). Hence we use game.fullUpdates which sums up to exactly
            50 each second.
        remend    
        dec hitpoints, game.fullUpdates
        showhitmessage = 1
    else
        showhitmessage = 0    
    endif
    
    
    rem Update Cubes
    for i = 1 to objects
        rem Make sure each cube gets its own behaviour but stays consistent over time
        randomize i
        amp# = 0.3 + rnd(rnd(150))*0.04
        spd# = 0.3*(0.1 + rnd(100)*0.009)
        off# = rnd(359)
        yoff# = rnd(100)*0.05 - 2.5
        rem Use absolute timer value (game.runtime) for explicit placement
        position object i, object position x(i), yoff# + amp#*sin(spd#*game.runtime + off#), object position z(i)
    next
    
    
    rem T for sync rate
    if keystate(20)
        if tpressed = 0
            tpressed = 1
            if limitedsyncrate
                limitedsyncrate = 0
                sync rate 0
            else
                limitedsyncrate = 1
                sync rate 30
            endif
        endif
    else
        tpressed = 0
    endif
    
    
    rem Output and Stuff
    set cursor 0,0
    print "FPS: ", screen fps()
    print "Press T to change FPS rate"
    print "WASD to move, Mouse to look"
    print
    if showhitmessage then print "You're leaving the allowed zone. Not good!"
    
    rem Show Hitpoints
    if hitpoints > 0
        x = screen width()/2
        center text x, 20, "Hitpoints:"
        box x-hitpoints/2, 45, x+hitpoints/2, 60
    else
        center text x, 20, "You're Dead"
    endif
    
    
    sync
loop







remstart
    How to use this tiny library:
    *call game_init() once after starting your program
    *call game_update() somewhere in your main loop (preferably
     at the beginning, before updating anything else in the game)
    *You can use game_reset() if you wish to reset the library's
     timer back to 0 (e.g. after loading a new level)
    
    How to enable timerbased movement:
    *De/Incremental Changes to all continuous values (floats and 
     doubles) over time should be multiplied with game.timeFactor 
    *Noncontinuous values (or those that can't simply be scaled with 
     the timeFactor for other reasons) should be multiplied with
     game.fullUpdates (which is an int and usually 0 or 1, it only
     gets higher if the games runs with less FPS than defined in
     GAME__FULL_UPDATES_PER_SECOND)
    *For changes that can't even be scaled with game.fullUpdates,
     you may use a for loop ranging from 1 to game.fullUpdates
    *In theory you could just use game.fullUpdates for *everything*
     and leave game.timeFactor out completely, however, this would 
     mean that FPS rates above GAME__FULL_UPDATES_PER_SECOND would 
     not update any more often than that exact value each second.
     Hence the advantage of timerbased movement would only be
     given for slow computers, fast ones would not gain any benefit
    
    Note:
    *The constants below can be changed, however:
    *GAME__MS_PER_FULL_UPDATE * GAME__FULL_UPDATES_PER_SECOND should 
     always equal 1000!
    *GAME__FULL_UPDATES_PER_SECOND defines how many non-continuous 
     update steps are executed each secod
    *GAME__MAX_TIME_DIF defines an upper boundary for the virtual
     time passing between two frames. This effectively means that
     if the game runs slower than this value allows (i.e. the game
     loop requires more ms than this value), it will not be "timer
     based" anymore but slow down. This is meant to prevent extreme
     "jumping" of objects, which might allow player characters to
     clip through walls etc. You can change this value, but I'd
     recommend to not use anything above 100.
    *you can access the game.runtime variable to avoid calling the
     timer() function regularly. Note that it always starts at 0
     though.
remend

rem Only change these constants if you know what you're doing (and 
rem if you've read the paragraphs above)

#constant GAME__MS_PER_FULL_UPDATE = 20
#constant GAME__FULL_UPDATES_PER_SECOND = 50
#constant GAME__MAX_TIME_DIF = 100




type game_Type
    systimer as integer
    starttime as integer
    runtime as integer
    actualRuntime as integer
    timedif as integer `limited by GAME__MAX_TIME_DIF
    actualTimeDif as integer 
    timeFactor as float
    fullUpdates as integer `for high frame rates usually 0, but 50 times per second 1; for FPS lower than 30 this number will occasionally be higher than one for apply multiple updates at once
    nextFullUpdate as integer
    fullUpdateSum as integer
endtype

function game_init()
    global game as game_Type
    game.starttime = timer()
    game.runtime = 0
    game.nextFullUpdate = 0
    game.actualRuntime = 0
    game.fullUpdateSum = 0
endfunction

function game_reset()
    game_init()
endfunction

function game_update()
    game.systimer = timer()
    tprev = game.actualRuntime
    game.actualRuntime = game.systimer - game.starttime
    game.timedif = game.actualRuntime - tprev
    game.actualTimeDif = game.timedif
    if game.timedif > GAME__MAX_TIME_DIF then game.timedif = GAME__MAX_TIME_DIF
    inc game.runtime, game.timedif
    game.timeFactor = game.timedif/(1.0*GAME__MS_PER_FULL_UPDATE)
    game.fullUpdates = 0
    if game.runtime >= game.nextFullUpdate
        dif = game.runtime - game.nextFullUpdate
        updates = 1 + dif/GAME__MS_PER_FULL_UPDATE
        if updates > 10
            game.fullUpdates = 10
            game.nextFullUpdate = game.runtime + GAME__MS_PER_FULL_UPDATE
        else
            inc game.nextFullUpdate, updates*GAME__MS_PER_FULL_UPDATE
            game.fullUpdates = updates
        endif
    endif
    inc game.fullUpdateSum, game.fullUpdates
endfunction



Feel free to use the code in any of your projects. In case you don't like the "namespace" (everything starting with 'game'), simply use search & replace on the word "game" in the code above and replace it by "timing" or whatever you want, that should do the job.
Posted: 27th Feb 2014 22:48
Interesting, except one thing..
Timer() resolution is only about 16 milliseconds that is, imho, not suitable for a game
use PerfTimer() for hires timing. perfimer is double integer
also use preffreq() (in dbp7.7) or QueryPerformanceFrequency in Kernel32.dll to get the frequency..

+ Code Snippet
    Global PerfTimerDI As Double Integer, PrevTimerDI As Double Integer
    Global TimerDeltaDF As Double Float, TimerFreqDF As Double Float
    Load DLL "kernel32.dll", 1
    Ptr = Make Memory(8)
    Call DLL 1, "QueryPerformanceFrequency", Ptr
    Delete DLL 1
    
    TimerFreqDF = *Ptr
    Delete Memory Ptr
...


    Sync Rate 0
    Sync On
    PrevTimerDI = PerfTimer()
    Sync: rem render first frame
    Do

Rem * Timer *
    PerfTimerDI = PerfTimer()
    TimerDeltaDF = PerfTimerDI - PrevTimerDI
    PrevTimerDI = PerfTimerDI
    TimerDeltaDF = TimerDeltaDF / TimerFreqDF
    ElapsedTime# = TimerDeltaDF

...
Sync
Loop
Posted: 28th Feb 2014 0:57
Timer() resolution is only about 16 milliseconds


I don't think this is the case.

+ Code Snippet
for i = 1 to 10
    print timer()
    wait 1
next
wait key


..although for some reason "wait x" seems to wait x+1 instead of x ms.
Anyway, the timer() command seems to do just what you'd expect from it - return the system time in ms - accurately, at least on (all of) my system(s) and with DBP 7.X.

However, it's certainly true that more accuracy is preferable. I wasn't aware you could get the frequency like that, so thanks for the input.
Posted: 28th Feb 2014 2:06
I ran this on my (old) laptop and I have this result,


but maybe a faster machine has different result, if any one could confirm ?
Posted: 1st Mar 2014 13:22


Here's mine.

Le Verdier, out of interest what result do you get using Hitimer()?
Posted: 1st Mar 2014 17:39
Yes it's far better. All hardwares/OS are not equals, even for a basic clock..It's this kind of little thing that became a hassle for coders like us..

I tried Hitimer() and I get
1
2
3
4
5
6
7
8
9
10

With an adjusted code:
+ Code Snippet
PrevTimer = HiTimer()
For i = 0 To 1
   CurrentTimer = HiTimer()
   If PrevTimer <> CurrentTimer Then Print CurrentTimer: PrevTimer = CurrentTimer: Inc Count
   i = (Count = 10)
Next i
Suspend For Key


But for me, 1ms resolution is still not enough accurate for a Main Loop
Posted: 29th Mar 2014 18:20
What... suddenly I've got the same problem Le Verdier had with timer(). Its resolution went down to 16ms, without any changes to the system whatsoever. Worked without problems yesterday, and now it doesn't. Haven't changed a thing. Same DBP as before, same hardware as before, no Windows updates (that I know of)... how's that possible?

Guess I'm going to use hitimer as well then, but still... weird.


Edit: Now it's back to normal again. Without a restart or anything. Compiling the exact same code.

Edit²: Aaaand 16ms steps again. Ugh.
Posted: 7th Apr 2014 10:13
A side note to consider which you may be aware of is the that Timer() uses the system clock which commences once the computer has booted, unlike HiTimer() which counts time from when the application starts. Much safer on systems which do not get switched off which could lead to the return value rolling over, thus not exceeding the 32bit value range.
Posted: 10th Apr 2014 0:04
@Le Verdier,

You can use a parameter with HITIMER to get higher resolution if you need it. When I check code for performance, I tend to use a value of 1000000 instead of the default of 1000 and it works fine.

@ShellfishGames,

It can depend on what you are running on your system, as other programs can change the clock resolution.

You can also do it yourself too by using the SET TIMER RESOLUTION command from the same plug-in that HITIMER comes from.
Posted: 10th Apr 2014 2:32
Thank for these tips, this can be more handy than double variables..