Delta Timing - Constant game speed at any framerate.

Advanced game related topics
dagcrack
Addict
Addict
Posts: 1868
Joined: Sun Mar 07, 2004 8:47 am
Location: Argentina
Contact:

Delta Timing - Constant game speed at any framerate.

Post 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.
! Black holes are where God divided by zero !
My little blog!
(Not for the faint hearted!)
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post 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 
BERESHEIT
dagcrack
Addict
Addict
Posts: 1868
Joined: Sun Mar 07, 2004 8:47 am
Location: Argentina
Contact:

Post 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...
! Black holes are where God divided by zero !
My little blog!
(Not for the faint hearted!)
vanleth
User
User
Posts: 79
Joined: Sat Jun 28, 2003 4:39 am
Location: Denmark - Valby

Post 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.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post 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.
BERESHEIT
vanleth
User
User
Posts: 79
Joined: Sat Jun 28, 2003 4:39 am
Location: Denmark - Valby

Post 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&
whertz
Enthusiast
Enthusiast
Posts: 124
Joined: Sat Jun 25, 2005 2:16 pm
Location: United Kingdom

Post 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
dagcrack
Addict
Addict
Posts: 1868
Joined: Sun Mar 07, 2004 8:47 am
Location: Argentina
Contact:

Post 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.
! Black holes are where God divided by zero !
My little blog!
(Not for the faint hearted!)
dell_jockey
Enthusiast
Enthusiast
Posts: 767
Joined: Sat Jan 24, 2004 6:56 pm

Post 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!
cheers,
dell_jockey
________
http://blog.forex-trading-ideas.com
dagcrack
Addict
Addict
Posts: 1868
Joined: Sun Mar 07, 2004 8:47 am
Location: Argentina
Contact:

Post 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.
! Black holes are where God divided by zero !
My little blog!
(Not for the faint hearted!)
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Post 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.
dagcrack
Addict
Addict
Posts: 1868
Joined: Sun Mar 07, 2004 8:47 am
Location: Argentina
Contact:

Post 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
...
! Black holes are where God divided by zero !
My little blog!
(Not for the faint hearted!)
User avatar
Joakim Christiansen
Addict
Addict
Posts: 2452
Joined: Wed Dec 22, 2004 4:12 pm
Location: Norway
Contact:

Post 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
I like logic, hence I dislike humans but love computers.
dagcrack
Addict
Addict
Posts: 1868
Joined: Sun Mar 07, 2004 8:47 am
Location: Argentina
Contact:

Post 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.
! Black holes are where God divided by zero !
My little blog!
(Not for the faint hearted!)
Post Reply