Page 1 of 1

Delta Timing - Constant game speed at any framerate.

Posted: Sat Apr 01, 2006 8:14 am
by dagcrack
Okey someone requested this so I'm posting a small class and an example of how to use it.


If you were struggling with speed issues, this is your solution.



The class and example all together are quite old, I checked this a little and it should compile, if it doesnt, changes are minimal.

But I hope it helps someone... You can easily see the concept anyway.

Class\DeltaTime.pb

Code: Select all

Structure FrameRate
  TargetFPS.f
  SpeedFactor.f
  FPS.f	
  TicksPerSecond.l	
  CurrentTicks.l	
  FrameDelay.l
EndStructure

Global FL.FrameRate;

Procedure.l FrameLimitInit(target_FPS.f);
  FL\TargetFPS = target_FPS;
  FL\TicksPerSecond = 1000;
  FL\FrameDelay = gettickcount_();
EndProcedure

Procedure.l SetSpeedFactor()
  FL\CurrentTicks = gettickcount_();
  FL\SpeedFactor = (FL\CurrentTicks - FL\FrameDelay) / (FL\TicksPerSecond / FL\TargetFPS);
  If FL\SpeedFactor <= 0  
    FL\SpeedFactor = FL\TargetFPS* 0.001;
  EndIf
  FL\FPS = FL\TargetFPS / FL\SpeedFactor;    
  FL\FrameDelay = FL\CurrentTicks;
EndProcedure
  
;// Last modified: 01:40 a.m. 23/09/2005 | By GuShH_


DeltaTime Example.pb

Code: Select all

; delta timing in 2D (could be perfectly used on 3D or more dimensions ;) doesnt matter) - Sorry for the old code though!.
; by dagcrack

; Reason for this example is to show you how to use the
; DeltaTime Class...
;\\ Some basic stuff has been ripped off, we'll go to the grain on this example.
;\\ Although some parts are bloated, you'll be able to understand this easy concept.
;|>
;

;
;\\ Let's include the Delta Time class first...
IncludeFile "Class\DeltaTime.pb";
; And..
Structure ProgData;
  WinWidth.w
  WinHeight.w
  WinTitle.s
  WinID.l
  
  Quit.l
EndStructure

InitSprite();
InitKeyboard();

Global P.ProgData

P\WinWidth   = 640;
P\WinHeight  = 480;
P\WinTitle   = "DeltaTime Example | GuShH ";

hWnd.l = OpenWindow(#pb_any,0,0,P\WinWidth, P\WinHeight,13107201, P\WinTitle);
P\WinID = WindowID(hWnd);
OpenWindowedScreen(P\WinID,0,0,P\WinWidth, P\WinHeight,#null,#null,#null);

;- Our "game" stuff...

Structure Player
  SprPointer.l
  x.f
  y.f
  
  MoveFactor.f
EndStructure

Global Player.Player;


Procedure.l _CheckWinEvents();
  EV = WindowEvent();
  If EV = #PB_event_closewindow
    P\Quit=#True;
  EndIf
  ;SetWindowText_(P\WinID,P\WinTitle+Str(FL\FPS))
EndProcedure

Procedure.l _CheckKeyEvents();
  ExamineKeyboard();

  If KeyboardPushed(#PB_Key_Up);
    Player\y - Player\MoveFactor;
  EndIf
  If KeyboardPushed(#PB_Key_Down);
    Player\y + Player\MoveFactor;
  EndIf
  
  If KeyboardPushed(#PB_Key_Left);
    Player\x - Player\MoveFactor;
  EndIf
  If KeyboardPushed(#PB_Key_Right);
    Player\x + Player\MoveFactor;
  EndIf
  
  If KeyboardPushed(#PB_Key_Escape)
    P\Quit=#True;
  EndIf
  
EndProcedure


Procedure.l _CreateChar(Width.l, Height.l )
  spr = CreateSprite(#pb_any,Width.l, Height);
  StartDrawing(SpriteOutput(spr))
  Circle(Width*0.5,Height*0.5,(((Width+Height)*0.5)*0.25),$0000FF)
  StopDrawing()
  ProcedureReturn spr
EndProcedure

Player\SprPointer = _CreateChar(64,64);

;-! SEE HERE
FrameRate = 150; Change this lower or higher, you'll see.
; It doesnt really matter anymore how fast the PC can render your game
; Everything will move as fast or as slow as it should.

GAME_FrameRate = 30 ; This is your game's internal "frame"rate!

FrameStep.f = (FrameRate*0.1); Don't change this... We are "weighting" the steps.
SetFrameRate(FrameRate);Set frame rate via PB.
FrameLimitInit(GAME_FrameRate/FrameStep); Init the delta-time system.


Player\MoveFactor = 1.4; 

;-
DefType.f x,y;

;\\ Main loop
Repeat
  ; you might need to clear the screen though!
  ; the why I'm not clearing:
  ; the player sprite has big black surroundings...
  ; if the program "jitters", youll see red chunks on screen.
  ; (this helps you debug the delta time class).
  
  SetSpeedFactor();
  _CheckWinEvents();
  _CheckKeyEvents()
  
  ; It's important to Multiply by the SpeedFactor!
  x = x + Player\x * FL\SpeedFactor
  y = y + Player\y * FL\SpeedFactor
  ; If you don't trust me, go ahead.. comment down the multiplys!
  ; That'd be like running without Delta Timing at all.
  
  DisplaySprite(Player\SprPointer, x , y );
  FlipBuffers(0);

Until P\Quit =#True;

This implementation is quite good for 2D and 3D games as well, because it includes a weight idea I implemented some time ago, the idea is simple so I wont comment much about it. But basically the bigger your weight is, the smoother your graphics renderings will end up, but, the higher the values you'll need to move your sprites/entities.

I will post another example later showing how to use minimal CPU usage.

Posted: Sat Apr 01, 2006 8:41 am
by netmaestro
Great code, thanks for posting! The only thing I could suggest is laying the GetTickCount_() aside in favour of the QueryPerformanceFrequency_() and QueryPerformanceCounter_() APIs, now that PB v4 has quads and can easily use them. GetTickCount_() is only updated every 15 milliseconds, same as ElapsedMilliseconds(), while QueryPerformanceCounter_() can reliably cut one millisecond in a thousand parts. This proc will return the elapsed milliseconds reliably one at a time: (PB v4 only)

Code: Select all

Procedure.l TicksHQ() 
  Static maxfreq.q 
  Protected t.q 
  If maxfreq=0 
    QueryPerformanceFrequency_(@maxfreq) 
    maxfreq=maxfreq/1000 
  EndIf 
  QueryPerformanceCounter_(@t.q) 
  ProcedureReturn t/maxfreq 
EndProcedure 

Posted: Sat Apr 01, 2006 8:50 am
by dagcrack
Yes.. and if you read my posts I always encourage the usage of high resolution timers, But, if you read the class footer the code is a little old and by that time I was working on a low-end 2D game for testing some pathfinding theories... go figure :lol: (not that you had to know though).

By using a higher res timer, you'll get smoother results indeed.

http://www.purebasic.fr/english/viewtop ... 3&start=15
Also, I think I said "...or use your own timer system based on a high res timer. " :)

I might post this pathfinders later on...

Posted: Sat Apr 01, 2006 9:03 am
by vanleth
Read somewhere about timers that TimeGetTime() with TimeBeginPeriod(1) is more accurate than QueryPerformanceCounter(). It was something about QueryPerformanceCounter() could tend to leap ahead and rarely give some jerks.

Posted: Sat Apr 01, 2006 9:08 am
by netmaestro
Dagcrack was kind enough to start an excellent thread on delta timing, and I don't really want to morph it into a discussion of which hi-res timing method is best. But Microsoft recommends this:

http://msdn.microsoft.com/library/defau ... essors.asp

But thanks for posting that, it's a great alternative for PB v3.x programs.

Posted: Sat Apr 01, 2006 9:45 am
by vanleth
netmaestro wrote:Dagcrack was kind enough to start an excellent thread on delta timing, and I don't really want to morph it into a discussion of which hi-res timing method is best. But Microsoft recommends this:

http://msdn.microsoft.com/library/defau ... essors.asp

But thanks for posting that, it's a great alternative for PB v3.x programs.
True, no need to go into another timing discussion, but for those of interrest I found the article about QueryPerformanceCounter Leaps here:
http://support.microsoft.com/default.as ... S;Q274323&

Posted: Sat Apr 01, 2006 1:46 pm
by whertz
Thank you dagcrack. I'm in the middle of changing my game to use timing for the movements and this will help me understand more about it. :D

Posted: Sat Apr 01, 2006 8:50 pm
by dagcrack
No problem.

Hey I like timing flame wars :lol: Let's burn some clocks!
Anyway I'll be posting some other game-dev related sources later...

By the way, theres no problem -mostly- on Querying the performance timer on 3.x - although with doubles you can do it in a breeze with out work-arounds.


The reason here is to show you how delta timing works and how you can implement it in your game! :) - Which timers you use, is up to you, the programmer!.

Each project has its own scopes and set-limitations, not everything will work perfectly on all projects with out some adjustments.

Posted: Thu Aug 10, 2006 12:25 pm
by dell_jockey
Hi Dagcrack,

a rather old thread, I know, but I'd like to thank you for publishing this information. Very useful at the moment!

Thanks again!

Posted: Thu Aug 10, 2006 2:17 pm
by dagcrack
Good. well, I was going to publish much more (I have a few "half baked" docs over here) but I've got no time for this, unfortunately.

They did include: A*, FSMs, etc.

But Don't worry, I will have them ready for one of my projects :) as part of the included documentation / tutorial base. I might make them public though. But if not, Im sure theres plenty of useful docs out there.

One sample that I indeed forgot to post was the 2D game skeleton (multi threaded, etc). It was "Object Oriented", Nice and modular code. (including own mouse and keyboard modules, etc)

I'm just too busy for this, In fact If I'm not -ever- helping anyone here its simply because I hardly watch this (or any other) forums. I am here right now though because Im subscribed to this thread... :P

However heres the above code, updated (for pb4 of course).

Code: Select all

Structure FrameRate 
  fTargetFPS.f 
  fSpeedFactor.f 
  fFPS.f    
  lTicksPerSecond.l    
  lCurrentTicks.l    
  lFrameDelay.l
  
EndStructure 

Procedure.l FrameLimitInit(*FL.FrameRate, lFrameRate.l, lGameFrameRate.l); 
  Define.f fFrameStep, fTargetFPS
  
  fFrameStep.f = (lFrameRate*0.1)
  fTargetFPS = lGameFrameRate/fFrameStep
  
  With *FL
    \fTargetFPS = fTargetFPS; 
    \lTicksPerSecond = 1000; 
    \lFrameDelay = GetTickCount_(); 
  EndWith
  
EndProcedure 

Procedure.l SetSpeedFactor(*FL.FrameRate) 
  With *FL
    \lCurrentTicks = GetTickCount_(); 
    \fSpeedFactor = (\lCurrentTicks - \lFrameDelay) / (\lTicksPerSecond / \fTargetFPS); 
    If \fSpeedFactor <= 0  
      \fSpeedFactor = (\fTargetFPS * 0.001); 
    EndIf 
    \fFPS = \fTargetFPS / \fSpeedFactor;    
    \lFrameDelay = \lCurrentTicks; 
  EndWith
  
EndProcedure 

Macro SpeedFactor(Struct)
  Struct#\fSpeedFactor
EndMacro

;// Last modified: 09:58 a.m. 10/08/2006 | By GuShH_

The Usage, now:

Define in your program the structure, example:

Code: Select all

Define.FrameRate FpSSystem
Now, for any motion code, you'll use the SpeedFactor macro, example:

Code: Select all

x = x + Player\x * SpeedFactor(FpSSystem)
Of course, don't forget to setup the system once you defined your struct!.
In this case:

Code: Select all

FrameLimitInit(FpSSystem, 150, 30)
And, obviously, update the delta timer by calling, in this case:

Code: Select all

SetSpeedFactor(FpSSystem)
I think that covers it.

Posted: Sat Aug 12, 2006 4:34 pm
by Rescator
To shave off a few cycles of cpu one might want to do SetSpeedFactor()
as a macro instead, gets rid of that procedure stack stuff.

Posted: Sat Aug 12, 2006 6:44 pm
by dagcrack
Yeah, although back when I released that code, the PB Compiler did not support MacroInstructions.

I just added a macro when I converted the code, but didnt analyze it to see wether another macro could be used or not.

However, heres the macro:

Code: Select all

Macro mSetSpeedFactor(FRStruct)
  
  FRStruct#\lCurrentTicks = GetTickCount_()
  FRStruct#\fSpeedFactor = (FRStruct#\lCurrentTicks - FRStruct#\lFrameDelay) / (FRStruct#\lTicksPerSecond / FRStruct#\fTargetFPS) 
  
  If FRStruct#\fSpeedFactor <= 0  
    FRStruct#\fSpeedFactor = (FRStruct#\fTargetFPS * 0.001) 
  EndIf 
  
  FRStruct#\fFPS = FRStruct#\fTargetFPS / FRStruct#\fSpeedFactor    
  FRStruct#\lFrameDelay = FRStruct#\lCurrentTicks
  
EndMacro
Nasty... Plus, this whole "macro" thing is just a joke, must be the less powerful MacroInstruction support on a compiler that I've ever seen.


But, good call Rescator!.

Shouldnt this be expanded -correctly- by the compiler anyway?

Code: Select all

Macro mSetSpeedFactor2(FRStruct) 
  With FRStruct#
    \lCurrentTicks = GetTickCount_(); 
    \fSpeedFactor = (\lCurrentTicks - \lFrameDelay) / (\lTicksPerSecond / \fTargetFPS); 
    If \fSpeedFactor <= 0  
      \fSpeedFactor = (\fTargetFPS * 0.001); 
    EndIf 
    \fFPS = \fTargetFPS / \fSpeedFactor;    
    \lFrameDelay = \lCurrentTicks; 
  EndWith 
EndMacro
...

Posted: Sat Aug 12, 2006 9:05 pm
by Joakim Christiansen
dagcrack wrote:Plus, this whole "macro" thing is just a joke, must be the less powerful MacroInstruction support on a compiler that I've ever seen.
Why can't you come with some macrofeature requests then? :P

Posted: Sat Aug 12, 2006 9:31 pm
by dagcrack
/Me doesnt really care about PB.
/Me thinks that the authors should.
/Me thinks that if they would, this things wouldnt be as crude.
/Me goes away dodging lots of rocks from the PB guys

I thought you knew this!.
If you want, just PM me about it.. You could get a real explaination.