Frameunabhängig programmieren

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
X0r
Beiträge: 2770
Registriert: 15.03.2007 21:47
Kontaktdaten:

Frameunabhängig programmieren

Beitrag von X0r »

Hallo!
Ich will euch in diesem kurzen "Tutorial" zeigen, wie man in frameunabhängig programmiert.

Wofür ist das gut?
Frameunabhängiges Programmieren sorgt dafür, dass eure Programm auf allen Rechner gleich schnell laufen. Natürlich läuft es nicht wirklich gleichschnell.
Der Trick ist ganz einfach. Man ermittelt die Zeit nach dem windowsstart am Anfang der Schleife. Am Ende ermittelt man ihn nochmal und speichert die Differenz vom neuen Wert und vom alten Wert in eine Variable. Wenn man nun zum Beispiel in einem Spiel sein Auto um 1 LE auf der x-Achse bewegen will, schreibt man:
MoveMesh(car,1*differenz,y,z).


Hier ein Beispiel:

Code: Alles auswählen

OpenConsole()

Repeat
 times=gettickcount_()

 ;Delay(2000)
 a=a+1*time
 PrintN(Str(a))

 time=gettickcount_()-times
ForEver

CloseConsole()

Falls ihr es wirklich testen wollt, dann kompiliert den code ohne "Delay(2000)" und einmal mit "Delay(2000)" und ihr werdet sehen..

In Spielen, vor allem in Online-Spielen, sollte man IMMER frameunabhängig programmieren!

Viele denken:"Ach was, muss nicht sein". Muss wohl sein. Wenn man in seinem Spiel eine Zeit programmiert, mit der bestimmte Sachen im Spiel passieren sollen, kann es dann zu ernsthaften Problemen führen.


Edit:
Hier noch ein "Zeit"-Beispiel:

Code: Alles auswählen

OpenConsole()

Repeat
 times=gettickcount_()

 ;delay(2000)
 a=a+1*time
 If(Str(a/1000)<>la.s)
 PrintN(Str(a/1000))
 la.s=Str(a/1000)
 EndIf

 time=gettickcount_()-times
ForEver

CloseConsole()
Wieder könnt ihr delay nehmen und werdet sehen, dass es "genauso schnell wie ohne delay läuft".
Zuletzt geändert von X0r am 21.10.2007 13:48, insgesamt 1-mal geändert.
Benutzeravatar
AND51
Beiträge: 5220
Registriert: 01.10.2005 13:15

Beitrag von AND51 »

Kann man dafür nicht auch SetFrameRate() nehmen?
PB 4.30

Code: Alles auswählen

Macro Happy
 ;-)
EndMacro

Happy End
Benutzeravatar
Thalius
Beiträge: 476
Registriert: 17.02.2005 16:17
Wohnort: Basel / Schweiz

Beitrag von Thalius »

AND51 hat geschrieben:Kann man dafür nicht auch SetFrameRate() nehmen?
Nur wenn VSync aktiviert ist und du auch was auf dem Screen zeichnest. Zb. die meisten Windowed apps arbeiten ohne Vsync - sprich 100% CPU Last.

gettickcount_() iss ausserdem genauer - Die Genauigkeit kommt zb. bei movements im 3D Space zum tragen, da du dort 32 bit floats verwendest ( noch ... ;).

Thalius
THEEX
Beiträge: 804
Registriert: 07.09.2004 03:13

Beitrag von THEEX »

Allerdings ist gettickcount_() nicht gerade sonderlich genau, für genauere Messungen ist davon abzuraten! Die Unterschiede könne durchaus 10 - 20 ms betragen.
Benutzeravatar
AND51
Beiträge: 5220
Registriert: 01.10.2005 13:15

Beitrag von AND51 »

Richtig, CSprengel. GetTickcount_() ist ziemlich dasselbe wir ElapsedMilliseconds(), und beide haben ihre Ungenauigkeit. Da muss man schon hochauflösende Timer wie QueryPerformanceCounter_() benutzen.

Thalius, VSync kannst du in PB ganz einfach mit FlipBuffers() ein- und ausscahlten; außerdem kannst du sogar seit PB 4 damit einen VSync-Modus einschalten, der eine CPU-schonendere Variante enthält.

Forge, deine Gedanken sind schon ok, aber an deiner Stelle würde ich dann doch eher mit den hochauflösenden Timer arbeiten oder einfach ganz auf FlipBuffers() und SetFrameRate() umsteigen.
PB 4.30

Code: Alles auswählen

Macro Happy
 ;-)
EndMacro

Happy End
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

völlig richtig, CSprengel

die bessere methode ist meiner meinung nach, für die abfolge im spiel einen zeittakt anzulegen.
sagen wir mal, alle 20ms soll das game einen "schritt" machen.
dann sieht das gerüst so aus:

Code: Alles auswählen

#timestep = 20
timer = GetTickCount_()

Repeat
  If GetTickCount_() > timer
    GameStep()
    timer + #timestep
  EndIf
Until EXIT
dadurch, dass der timer nirgendwo in der schleife auf einen aktuellen GetTickCount_() gesetzt wird,
sondern dass er jeweils um einen festen wert erhöht wird,
ist eine ungenauigkeit von GetTickCount_() vernachlässigbar.

GameStep() wird vielleicht mal 10ms früher oder später aufgerufen,
aber innerhalb einer stunde wird er ziemlich genau so oft aufgerufen werden wie er soll.
(in diesem fall 180.000 mal +/- 3mal)

PS:
war ElapsedMilliseconds() jetzt der wrapper von GetTickCount_(), oder von TimeGetTime_()?
Der Narr denkt er sei ein weiser Mann.
Der Weise weiß, dass er ein Narr ist.
Benutzeravatar
Thalius
Beiträge: 476
Registriert: 17.02.2005 16:17
Wohnort: Basel / Schweiz

Beitrag von Thalius »

Thalius, VSync kannst du in PB ganz einfach mit FlipBuffers() ein- und ausscahlten; außerdem kannst du sogar seit PB 4 damit einen VSync-Modus einschalten, der eine CPU-schonendere Variante enthält.
Da sieht man wie lange ich nix mehr mit PB's Grafikfunktionen gemacht habe ... ;)

Muss ich mal testen... und das gettickcount_() nicht das genauste ist - iss klar ;) aber für sowas wie nen "Gametic" isses ideal.

elapsedmilliseconds() == gettickcount_() :D

Thalius
a14xerus
Beiträge: 1440
Registriert: 14.12.2005 15:51
Wohnort: Aachen

Beitrag von a14xerus »

setframerate beschränkt aber nur die geschwindigkeit bei schnellen pc's , macht das spiel auf alten pc's aber nicht schneller ;)
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

stimmt genau...

die beste lösung ist immernoch, unterschiedliche threads für darstellung und berechnung zu verwenden...

die berechnung wird getimed, so wie von mir vorgeschlagen oder mit nem timer-callback,
die darstellung läuft durch ohne SetFrameRate. notfalls kann man nen "verändert"-flag von der game routine setzen lassen, um ein neuzeichnen einer unveränderten darstellung zu umgehen.

note: wenn irgendwo ein thread auf nen flag warten soll, die Delays nicht vergessen, um die CPU nicht zu braten....
Der Narr denkt er sei ein weiser Mann.
Der Weise weiß, dass er ein Narr ist.
Benutzeravatar
X0r
Beiträge: 2770
Registriert: 15.03.2007 21:47
Kontaktdaten:

Beitrag von X0r »

Irgendjemand sagte:
>die beste lösung ist immernoch, unterschiedliche threads für darstellung und berechnung zu verwenden...

Wobei man bei threads und DirectX aufpassen muss(Außer man will nicht DirectX rendern lassen).



Noch eine Denkweise:

Code: Alles auswählen

Global FPS_MAX = 60
FramePeriod = 1000/FPS_MAX 
FrameTime = GetTickCount_()  - FramePeriod 

repeat

repeat
      FrameEllapsed = GetTickCount_()  - FrameTime 

   Until FrameEllapsed 
   FrameTicks = FrameEllapsed/FramePeriod 
   FrameTween#  = Float(FrameEllapsed Mod FramePeriod) / Float(FramePeriod) 
   For FrameLimit = 1 To FrameTicks 
         FrameTime = FrameTime + FramePeriod 
     
            UpdateWorld() 
   Next 
forever

 
Nun, viele Spieleentwickler, die ich kenne, machen es mit Gettickcount_(). Ob es auf die paar Millisekunden wirklich ankommt.....darüber lässt sich streiten.

Nun poste ich noch ein Beispiel zum ermitteln der FPS:

Code: Alles auswählen

repeat


frames=frames+1

if gettickcount_()-ltime>1000
fps=frames
frames=0
ltime=gettickcount_()

endif


forever

AND51 sagte:
>FlipBuffers() und SetFrameRate()

FlipBuffers(), nun ja, tauscht 2 Buffers aus(Frontbuffer mit aktuellem Buffer), und?
SetFrameRate? Kann sein, solch eine Funktion kannte ich bisher noch nicht.
Diese OGRE Engine scheint es einem wirklich leicht zu machen.
Werde mal einen Blick drauf werfen.


>setframerate beschränkt aber nur die geschwindigkeit bei schnellen pc's , macht das spiel auf alten pc's aber nicht schneller
Nein? Wirklich schade. Dachte, dass das Programm dann konsequent mit X fps läuft.

P.S: Schneller wird dadurch nix. :wink: Wär schön. Du meinst das bestimmt anders.
Antworten