Framerate Limit

04/02/2012 18:24 X0R0N#1
Hi Zusammen, da ich seid kurzem in C++ eingestiegen bin, schreibe ich gerade ein Spiel in C++ mit SDL und openGL.
Funktioniert schon alles ganz gut, nur hab ich das Problem, dass meine Framerate abhängig davon ist, wie schnell die Hauptschleife (Input, logic und renderingteil) durchlaufen wird. Das hat zur Folge, dass der Spielablauf auf verschiedenen Rechnern verschieden schnell ist.

Ich hab nun versucht die Framerate auf ein Festes Limit zu zwingen, indem ich die gesamte Schleife übersprungen hab, wenn das Limit überstiegen wurde, da ich mir nicht ganz vorstellen kann, wie ich die Bewegungen in Abhängigkeit von der Framerate berechnen kann.
Hier ein Stück code dazu zur Veranschaulichung:


Code:
#include <time.h>

int frames = 0;
int frameRate = 60;

if( frames/(clock()/CLOCKS_PER_SEC) <= frameRate)
{
    frames++;
    //INPUT
    ...
    //LOGIC
    ...
    //RENDERING
    ...
}
Jetzt habe ich aber das Problem, dass das Spiel immer kurz stehen bleibt, sobald die ausgerechnete Framerate (frames/(clock()/CLOCKS_PER_SEC))
den von mir in frameRate festgelegten Wert 60 übersteigt.
Weis vielleicht jemand, wie ich die Framerate limitieren, und doch den flüssigen Ablauf beibehalten kann?

Gruß,
X0R0N
04/02/2012 19:04 MrSm!th#2
Außerdem hättest du noch das Problem, dass das Spiel auf Rechnern langsamer läuft, die nichtmal dein Framerate Limit erreichen.

Normalerweise macht man das, indem man in jede Bewegung einen Zeitfaktor einfließen lässt, sodass halt die Bewegung abhängig ist, wie lange das Rendering dauert.

Bspw. mit
Code:
//bewegung eines spielers
while(key_pressed("w"))
    player.position += velocity*time_passed;
Je schneller gerendert wird, desto weniger Zeit vergeht und desto weniger schnell ist der Spieler dafür.
04/02/2012 19:07 Dr. Coxxy#3
[Only registered and activated users can see links. Click Here To Register...]

liefert die benötigte auflösung...
04/02/2012 19:20 jacky919#4
Wenn du es mit Hilfe von SDL lösen willst kannst du das hier nutzen:
Klasse:

Implementierung

Nutze am besten nichts, was mit der WinAPI auf direktem Wege zu tun hat. Ich meine wenn schon SDL und OpenGL dann auch richtig ;)
04/02/2012 20:20 MrSm!th#5
Quote:
Nutze am besten nichts, was mit der WinAPI auf direktem Wege zu tun hat. Ich meine wenn schon SDL und OpenGL dann auch richtig
Plattformunabhängigkeit ist nicht der einzige Grund für die SDL, also wieso sollte man deshalb vollständig auf die Winapi verzichten?
04/02/2012 20:41 jacky919#6
Quote:
Originally Posted by MrSm!th View Post
Plattformunabhängigkeit ist nicht der einzige Grund für die SDL, also wieso sollte man deshalb vollständig auf die Winapi verzichten?
Das war jetzt auch mehr auf diesen Fall bezogen. Wenn man nicht drum herum kommt ist das natürlich vollkommen legitim, aber ich finde es unnötig, wenn SDL dem Programmierer schon die Möglichkeit gibt, die Dinge mit einem viel höheren Abstraktionsgrad von sich aus zu nutzen.
04/02/2012 20:52 ef784fH/FAE7#7
Hallo,
Ich denke Ich kann dir helfen.

Code:
  // Framebremse (etwas veraltete, dafür einfache Variante, das 
  // Physikverhalten bei höheren Frameraten einigermaßen korrekt zu halten)
  // Achtung: hier werden Pipeline-Effekte des OpenGL vernachläßigt!
  //while((clock()-frame_start_time)*120 < CLOCKS_PER_SEC);						//Framebremse ist deaktiviert
clock_t frame_start_time = clock();
04/02/2012 21:23 X0R0N#8
Quote:
Originally Posted by MrSm!th View Post
Außerdem hättest du noch das Problem, dass das Spiel auf Rechnern langsamer läuft, die nichtmal dein Framerate Limit erreichen.

Normalerweise macht man das, indem man in jede Bewegung einen Zeitfaktor einfließen lässt, sodass halt die Bewegung abhängig ist, wie lange das Rendering dauert.

Bspw. mit
Code:
//bewegung eines spielers
while(key_pressed("w"))
    player.position += velocity*time_passed;
Je schneller gerendert wird, desto weniger Zeit vergeht und desto weniger schnell ist der Spieler dafür.
Danke, das hört sich gut an. Doch wie komme ich auf diesen Zeitfaktor?
Wenn ich, so wie ich es mache die FPS berechne, hab ich ja schonmal die Geschindigkeit, mit der die Schleife durchlaufen wird bestimmt (frames/(clock()/CLOCKS_PER_SEC)).
Damit sollte ich ja was anfangen können. Mir ist nur nicht ganz klar, wie ich den Wert verrechne, sodass ein passender Faktor rauskommt, der das Spiel auf allen Systemen unabhängig von der Framerate gleich schnell laufen lässt, wenn ich diesen wiederrum mit der Bewegungsgeschwindigkeit eines Objekts im Spiel verrechne.
04/02/2012 21:38 jacky919#9
Quote:
Originally Posted by X0R0N View Post
Danke, das hört sich gut an. Doch wie komme ich auf diesen Zeitfaktor?
Klasse:

Implementierung

Quote:
Originally Posted by X0R0N View Post
Wenn ich, so wie ich es mache die FPS berechne, hab ich ja schonmal die Geschindigkeit, mit der die Schleife durchlaufen wird bestimmt (frames/(clock()/CLOCKS_PER_SEC)).
Damit sollte ich ja was anfangen können. Mir ist nur nicht ganz klar, wie ich den Wert verrechne, sodass ein passender Faktor rauskommt, der das Spiel auf allen Systemen unabhängig von der Framerate gleich schnell laufen lässt, wenn ich diesen wiederrum mit der Bewegungsgeschwindigkeit eines Objekts im Spiel verrechne.
Also wenn du das von mit benutzt musst du nur Vergange Zeit(in Millisekunden)*Weg pro Millisekunde. Da das Rechnen mit Millisekunden vielleicht etwas umständlich ist, kannst du die Zeit natürlich einfach durch 1000 teilen, um mit Sekunden zu rechnen. Nach dem Rendern musst du den Timer dann einfach wieder zurücksetzen.
04/02/2012 21:51 MrSm!th#10
Quote:
Originally Posted by X0R0N View Post
Danke, das hört sich gut an. Doch wie komme ich auf diesen Zeitfaktor?
Wenn ich, so wie ich es mache die FPS berechne, hab ich ja schonmal die Geschindigkeit, mit der die Schleife durchlaufen wird bestimmt (frames/(clock()/CLOCKS_PER_SEC)).
Damit sollte ich ja was anfangen können. Mir ist nur nicht ganz klar, wie ich den Wert verrechne, sodass ein passender Faktor rauskommt, der das Spiel auf allen Systemen unabhängig von der Framerate gleich schnell laufen lässt, wenn ich diesen wiederrum mit der Bewegungsgeschwindigkeit eines Objekts im Spiel verrechne.
Das ist einfach die vergangene Zeit.
Am Anfang jedes Durchgangs rufst du sie beispielsweise (Achtung: WinApi!) so ab:

Code:
DWORD time_passed = 0;

//in der loop
time_passed = timeGetTime() - time_passed;
//übergabe von time_passed an alle funktionen, die von der geschwindigkeit des PCs abhängig sein und es deshalb in die aktionen mit einberechnen sollen
Quote:
Also wenn du das von mit benutzt musst du nur Vergange Zeit(in Millisekunden)*Weg pro Millisekunde. Da das Rechnen mit Millisekunden vielleicht etwas umständlich ist, kannst du die Zeit natürlich einfach durch 1000 teilen, um mit Sekunden zu rechnen. Nach dem Rendern musst du den Timer dann einfach wieder zurücksetzen.
Ähm, es wäre eher umständlich, erst durch 1000 teilen zu müssen, zumal man dann Float als Datentyp nehmen muss, was wieder langsamer als int in der Berechnung ist (auch wenn es in so einem Fall vermutlich egal ist).
Da er die Zeit einfach immer nur weitergibt und Werte mit ihr multipliziert, gibt es keinen Grund, in Sekunden umrechnen zu müssen. Wo soll da der Vorteil liegen? Er will sie ja nicht ausgeben o.Ä. wo das Format eine Rolle spielen würde.
04/02/2012 22:13 jacky919#11
Quote:
Originally Posted by MrSm!th View Post
Ähm, es wäre eher umständlich, erst durch 1000 teilen zu müssen, zumal man dann Float als Datentyp nehmen muss, was wieder langsamer als int in der Berechnung ist (auch wenn es in so einem Fall vermutlich egal ist).
Da er die Zeit einfach immer nur weitergibt und Werte mit ihr multipliziert, gibt es keinen Grund, in Sekunden umrechnen zu müssen. Wo soll da der Vorteil liegen? Er will sie ja nicht ausgeben o.Ä. wo das Format eine Rolle spielen würde.
Um floats wird er eh schlecht herum kommen, denn es sind ja relativ lange Wege pro Sekunde erforderlich, damit der Weg pro Sekunde größer als bzw. gleich 1 ist.
04/02/2012 23:49 X0R0N#12
Der sagt bei mir timeGetTime() ist nicht deklariert, obwohl die includes da sind, darum hab ich jetzt einfach mal SDL_GetTicks() genommen. Liefert im Grunde nichts anderes.

Ich habs so gemacht:
Code:
    int ticksPassed = 0;

    //Main Game loop ######################################
    while(isRunning)
    {
        ticksPassed = SDL_GetTicks() - ticksPassed;
        std::cout << "Time Passed: " << ticksPassed << " (Current: " << SDL_GetTicks() << ")\n";
Zum Test die cout Zeile eingefügt, die liefert mir jetzt aber unerwartete Ergebnisse... Ziemlich unregelmäßig und vor allem immer größer werdend.

Die kann ich ja wohl nicht verwenden... Eigentlich müsste der Millisekundenwert immer ziemlich gleich sein, da es ja immer der zeitabstand zum letzten Frame ist oder irre ich mich?


€dit:

Quote:
Originally Posted by MrSm!th View Post
Code:
DWORD time_passed = 0;

//in der loop
time_passed = timeGetTime() - time_passed;
//übergabe von time_passed an alle funktionen, die von der geschwindigkeit des PCs abhängig sein und es deshalb in die aktionen mit einberechnen sollen

Die Rechnung hatte einfach nicht so ganz gestimmt. Habs jetzt etwas korrigiert:

Code:
    int ticks = SDL_GetTicks();
    int ticksPassed = 0;

    //Main Game loop ######################################
    while(isRunning)
    {
        ticksPassed = SDL_GetTicks() - ticks;
        ticks = SDL_GetTicks();

        std::cout << "Time Passed: " << ticksPassed << " (Current: " << SDL_GetTicks() << ")\n";
Und bekomme jetzt auch akzeptable Werte:


Demnach dauert auf meinem Rechner ein Schleifendurchlauf im Schnitt 10ms.
Soll ich das jetzt einfach durch 10 Teilen, damit 1 rauskommt, und die velocity immer damit multiplizieren? Dürfte dann ja auf anderen Rechnern gleich schnell laufen, oder irre ich mich?
04/03/2012 01:07 MrSm!th#13
Stimmt, hast Recht, war ein kleiner Denkfehler von mir. timeGetTime sollte eigentlich vorhanden sein, wenn du windows.h einbindest.

Wenn du durch 10 teilst erhälst du 1, korrekt. Damit hast du aber nichts gewonnen, was passiert denn, wenn du etwas mit 1 multiplizierst?
Du sollst nun einfach alles mit dieser 10 multiplizieren (genauer genommen mit ticksPassed). Auf PCs, die etwas langsamer sind wird dort vielleicht nicht 10 sondern 100 stehen, d.h. die Schleife wird zwar 10 mal weniger in der Sekunde durchlaufen, dafür bewegen sie sich 10 mal schneller und das ganze wird aufgehoben.
Ein Problem wird es erst, wenn die Zahl der Schleichendurchläufe so gering wird, dass es halt anfängt zu ruckeln, also die Schleife 2 mal durchlaufen wird und dafür die Geschwindigkeit 500facht wird.

Quote:
Um floats wird er eh schlecht herum kommen, denn es sind ja relativ lange Wege pro Sekunde erforderlich, damit der Weg pro Sekunde größer als bzw. gleich 1 ist.
Es war ein schwaches Argument, gebe ich zu, trotzdem gibt es auch keinen sinnvollen Grund, float hier int vorzuziehen.
Dennoch gebe ich dir nicht 100%ig Recht. Ein int bietet viel Speicherplatz, das kann dir theoretisch also egal sein. In S4 sind die Koordinaten nur Ganzzahlen, (auch wenn sie theoretisch in float gespeichert werden, ist ein Schritt circa ein +1) da ist dann ne Map halt mal 2000 Einheiten lang, na und?
Finde ich persönlich sogar einleuchtender als solche Spiele, in denen sich deine Koordinaten zwischen 1 und 10 bewegen :/

Du hast da übrigens einen kleinen Formulierungsfehler drin:

Quote:
denn es sind ja relativ lange Wege pro Sekunde erforderlich, damit der Weg pro Sekunde größer als bzw. gleich 1 ist.
Nö, dafür wird ein Weg von 1 pro Sekunde benötigt :p

Du meintest vermutlich es sind relativ lange Wege pro Sekunde erforderlich, damit die Wege pro Millisekunde 1 sind. Aber, wo ist das Problem? Interessiert doch nicht, wie hoch nun die tatsächliche Zahl ist, du kannst das in einer virtuellen Welt ja interepretieren wie du willst. Musst ja nicht die 1 = 1 Meter setzen.

Ich sehe weiterhin keinen Vorteil darin, float zu nutzen.
04/03/2012 02:56 X0R0N#14
Oh sehr gut, dann muss ich, wenn ich das *ticksPassed nehme nur noch die Velocity bei mir so einstellen, dass es mit der neuen Rechnung optimal schnell läuft, und mein Problem müsste gelöst sein.
Danke für die Hilfe, ich schreib das dann nachher fertig und meld mich hier nochmal.
Jetzt erstmal ne Mütze Schlaf holen. N8

€dit: So funktioniert perfekt. Vielen Dank für die Hilfe!