C++ Gameloop

Rooky420

Cadet 4th Year
Registriert
Nov. 2015
Beiträge
98
Hallo ich versuche grade eine Gameloop für SFML zu schreiben.
Kann das so Stimmen?
Fraps zeigt mir nämlich immer noch um die 250 fps an.

Code:
void loop(sf::RenderWindow* window)
{
	auto lastTime = std::chrono::high_resolution_clock::now();
	double amountOfTicks = 60.0;
	double ns = 1000000000 / amountOfTicks;
	double delta = 0;
	while (window->isOpen()){
		auto now = std::chrono::high_resolution_clock::now();
		delta += std::chrono::duration_cast<std::chrono::nanoseconds>(now - lastTime).count() / ns;
		lastTime = now;
		while (delta >= 1){
			delta--;
			std::this_thread::sleep_for(std::chrono::milliseconds(1));
		}
		update(window);
		render(window);
	}
}

oder kann mir jemand ein Seite mit gutem "Tutorial" für eine richtig funktionierende Gameloop geben?

LG
 
Ergänzung ()

Ok, 2. Versuch:
Es scheint so, als ob du dir die Vorletzte der hier beschriebenen Game-Loops zum Vorbild genommen hättest. Falls dem so ist, hast du evtl. deren Zweck missverstanden:

Das Ziel ist es nicht, die Framerate künstlich zu limitieren, sondern die Framerate so hoch wie möglich zu halten, während die Spielwelt aber unabhängig davon immer in fixen Zeitschritten simuliert wird. Wenn also die Grafik nicht besonders komplex ist, dann kann die Framerate beliebig hoch werden.
Ergänzung ()

Übrigens, falls du mit VS2015 oder nem aktuellen g++ programmierst würde ich die Schleife vermutlich so schreiben:

Code:
using namespace std::chrono;
void loop(sf::RenderWindow* window) {
	const auto timeStep =  1'000'000us/60;

	auto accumulator= 0ns;
	auto lastTime = steady_clock::now();
	while (window->isOpen()) {
		auto now = steady_clock::now();
		accumulator += now - lastTime;
		lastTime = now;
		while (accumulator >= timeStep) {
			accumulator -= timeStep;
			//simulate one timestep
		}
		update(window);
		render(window);
	}
}
 
Zuletzt bearbeitet:
Also ich kenne mich mit "Gameloop" nicht aus, aber warum
Code:
lastTime = now;
while (delta >= 1){
	delta--;
	std::this_thread::sleep_for(std::chrono::milliseconds(1));
}

Und nicht einfach
Code:
std::this_thread::sleep_for(std::chrono::milliseconds(--delta));
?

Soweit ich weiß gibt dir
Code:
std::chrono::nanoseconds
auch keine wirklichen Nanosekunden, kannst also eigentlich direkt auf Millisekunden wechseln.


Ich würde auch einfach
Code:
1000 / ZIEL_FPS - VERSTRICHENE_MS
warten.

Falls ich grade einen totalen Denkfehler habe vergesst meinen Post einfach, bin wenn überhaupt nur halb wach.
 
Danke für die Antworten:

Miuwa schrieb:
Das Ziel ist es nicht, die Framerate künstlich zu limitieren, sondern die Framerate so hoch wie möglich zu halten, während die Spielwelt aber unabhängig davon immer in fixen Zeitschritten simuliert wird. Wenn also die Grafik nicht besonders komplex ist, dann kann die Framerate beliebig hoch werden.

Ich dachte mir so eine Schleife ist da damit bei schnellen PCs das spiel genauso schnell läuft als mit langsamen PCs.
Es ist ja so bei jedem Aufruf von Update irgendwelche werte geändert werden. Wenn man also einen schnelleren PC hat und die Schleife schneller durchlaufen wird, wird update öfter aufgerufen d.h. der Spieler bewegt sich schneller beispielsweise oder hab ich jetzt einen Denkfehler?
 
Ich dachte mir so eine Schleife ist da damit bei schnellen PCs das spiel genauso schnell läuft als mit langsamen PCs.
Es ist ja so bei jedem Aufruf von Update irgendwelche werte geändert werden. Wenn man also einen schnelleren PC hat und die Schleife schneller durchlaufen wird, wird update öfter aufgerufen d.h. der Spieler bewegt sich schneller beispielsweise
So ist es. Es geht ja nicht nur um PC Geschwindigkeit sondern auch darum, ob andere Tasts CPU Zeit stehlen. Man will also einfach nur wissen, wieviel Zeit seit dem letzten Durchlauf vergangen ist, um davon abhängig die Spielwelt um genau diese Zeit weiter 'laufen' zu lassen.
Hier ist der Experten-Beitrag:
http://gamedev.stackexchange.com/questions/651/tips-for-writing-the-main-game-loop
 
Spricht etwas dagegen mit den CLOCKS_PER_SECOND zu arbeiten?

Wenn du den Loop auf 60/Sekunde begrenzt kann genau das passieren, was man bei Fallout 4 sieht: Das Spiel läuft auf zu langsamen Systemen langsamer als auf schnellen Systemen. Die Simulation ist dann abhängig von deiner Rechenleistung.
IdR möchte man ja aber, dass das Spiel überall gleich schnell läuft unabhängig von der Zeit die ein System zur Berechnung des nächsten Zustandes braucht. Und dafür ist es nur entscheidend zu ermitteln wie viel Zeit seit der letzten Berechnung vergangen ist und einen entsprechend großen Zeitschritt für die Berechnung des nächsten SChrittes zu benutzen. Am Ende ist natürlich das Ziel möglichst homogene Zeitschritte zu bekommen. Das steht aber hinten an und ist dann eine Frage der Optimierung.
Diese Methode hat dann den Nachteil, dass die Genauigkeit der Simulation bei extrem langen Berechnungszeiten teils deutlich leidet.
 
Diese Methode hat dann den Nachteil, dass die Genauigkeit der Simulation bei extrem langen Berechnungszeiten teils deutlich leidet.
Wobei man da bei sehr niedrigen Frameraten wieder auf auf eine begrenzte Anzahl fester Zeitschritte zurückfallen kann. Also bei einer Framedauer von 120ms zum Beispiel zwei Schritte mit 50ms und einen mit 20ms ausführen, oder aber drei mit 40ms. Oder einfach davon ausgehen, dass der Spieler ohnehin mit mehr als 20 FPS spielt und die Schrittweite auf 50ms o.ä. begrenzen, so wie es z.B. Witcher 3 macht.

Feste Zeitschritte haben aber eigentlich nur Nachteile. Wenn man sein Spiel jetzt auf 60 FPS hin programmiert, der Spieler aber nur mit 55 FPS spielt, dann wird meist nur ein Schritt pro Frame berechnet, alle 11 Frames aber zwei. Das führt dann zu einem sehr ruckeligen Spielablauf, obwohl die Frameraten eigentlich flüssig erscheinen.
 
Rooky420 schrieb:
Ich dachte mir so eine Schleife ist da damit bei schnellen PCs das spiel genauso schnell läuft als mit langsamen PCs.
Genau das tut sie ja auch - nur weil deine Framerate sehr hoch ist schreitet das Spiel nicht schneller voran (es sei denn, deine Spiellogik liegt irgendwie in der update(window) Funktion - da gehört sie natürlich nicht hin).

Nochmal: diese Art der Game Loop (es gibt natürlich auch andere) entkoppelt die Spielzeit von der Framerate und versucht die Spielzeit so nah wie möglich an der Echt-Zeit zu halten.

Wenn du stattdessen einfach willst, dass deine Gameloop auf allen Systemen (die schnell genug sind) mit 60Hz ausgeführt wird, dann kannst du folgendes schreiben:

Code:
void loop(sf::RenderWindow* window) {
	const auto timeStep =  1'000'000us/60;
	auto accumulator= 0ns;
	auto nextTime = steady_clock::now()+timeStep;
	while (window->isOpen()) {
		update(window);
		render(window);
		
		//Spielwelt um timeStep weiter simulieren
		
		std::this_thread::sleep_until(nextTime);
		nextTime += timeStep;
	}
}
Mit dieser Methode wird alle 16,66...ms genau ein Frame gerendert und die Spielwelt um 16,66.. ms weiter simuliert - das führt natürlich zu Problemen, wenn der PC nicht schnell genug ist um mit den Berechnungen innerhalb von 16ms fertig zu werden
Ergänzung ()

VikingGe schrieb:
Wobei man da bei sehr niedrigen Frameraten wieder auf auf eine begrenzte Anzahl fester Zeitschritte zurückfallen kann.
Genau das macht die Gameloop von Rooky420 ja - die Spielwelt wird immer in festen Zeitschritten (in diesem Fall 16,66... ms) weiterberechnet. Je nachdem wie schnell die Grafikkarte ist kann das mehrmals zwischen zwei Bildausgaben geschehen oder es kann auch sein, (wenn die Framerate über 60Hz steigt), dass mehrmals hintereinander das gleiche Bild gerendert wird.

Eine feste zeitliche Auflösung der Simulation kann die Programmierung und Optimierung ungemein erleichtern, ist aber nur sinnvoll, wenn sie klein genug ist, damit die von dir beschriebenen Effekte nicht auffallen.
 
Ich würde - vor allem bei Physik-Simulation - mit 60 Schritten pro Sekunde rechnen und einfach zwischen den Schritten linear interpolieren.

Also wenn wir Zeitpunkt 3/120 (Sekunden) haben, dann Schritt 1 und 2 (bis 2/60=4/120) berechnen und zwischen Schritt 1 und 2 interpolieren, in dem Fall sind die Objekte dann genau in der Mitte zwischen ihren Positionen im ersten und im zweiten Schritt.
 
Zurück
Oben