Ich habe kürzlich diesen Artikel über Game Loops gelesen: http://www.koonsolo.com/news/dewitters-gameloop/
Und die empfohlene letzte Implementierung verwirrt mich zutiefst. Ich verstehe nicht, wie es funktioniert, und es sieht aus wie ein komplettes Durcheinander.
Ich verstehe das Prinzip: Aktualisiere das Spiel mit einer konstanten Geschwindigkeit, wobei alles, was übrig ist, das Spiel so oft wie möglich rendert.
Ich nehme an, Sie können nicht Folgendes verwenden:
- Holen Sie sich die Eingabe für 25 Ticks
- Render-Spiel für 975 Ticks
Annäherung, da Sie Input für den ersten Teil des zweiten bekommen würden und dies sich komisch anfühlen würde? Oder ist es das, was in dem Artikel vor sich geht?
Im Wesentlichen:
while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)
Wie ist das überhaupt gültig?
Nehmen wir seine Werte an.
MAX_FRAMESKIP = 5
Nehmen wir an, next_game_tick, das unmittelbar nach der Initialisierung zugewiesen wurde, bevor die Hauptspielschleife "500" lautet.
Da ich für mein Spiel SDL und OpenGL verwende und OpenGL nur zum Rendern verwendet wird, nehmen wir an, dass GetTickCount()
die Zeit seit dem Aufruf von SDL_Init zurückgegeben wird, was auch der Fall ist.
SDL_GetTicks -- Get the number of milliseconds since the SDL library initialization.
Quelle: http://www.libsdl.org/docs/html/sdlgetticks.html
Der Autor geht auch davon aus:
DWORD next_game_tick = GetTickCount();
// GetTickCount() returns the current number of milliseconds
// that have elapsed since the system was started
Wenn wir die while
Aussage erweitern, erhalten wir:
while( ( 750 > 500 ) && ( 0 < 5 ) )
750, weil die Zeit seit next_game_tick
der Zuweisung vergangen ist . loops
ist Null, wie Sie im Artikel sehen können.
Also haben wir die while-Schleife betreten, machen wir eine Logik und akzeptieren eine Eingabe.
Blabla.
Am Ende der while-Schleife, an die ich Sie erinnern möchte, befindet sich in unserer Hauptspiel-Schleife Folgendes:
next_game_tick += SKIP_TICKS;
loops++;
Lassen Sie uns aktualisieren, wie die nächste Iteration des while-Codes aussieht
while( ( 1000 > 540 ) && ( 1 < 5 ) )
1000, weil die Zeit verstrichen ist, um Eingaben zu machen und Dinge zu erledigen, bevor wir die nächste Stufe der Schleife erreicht haben, in der GetTickCount () aufgerufen wird.
540, weil im Code 1000/25 = 40, also 500 + 40 = 540
1, weil unsere Schleife einmal durchlaufen wurde
5 , du weißt warum.
Also, da diese While-Schleife CLEARLY ABHÄNGIG ist MAX_FRAMESKIP
und nicht die beabsichtigte, TICKS_PER_SECOND = 25;
wie soll das Spiel überhaupt richtig laufen?
Es war keine Überraschung für mich, dass das Spiel, als ich dies in meinen Code implementierte, nichts unternahm, da ich einfach meine Funktionen umbenannte, um Benutzereingaben zu verarbeiten und das Spiel so zu zeichnen, wie es der Autor des Artikels in seinem Beispielcode hat .
Ich habe eine fprintf( stderr, "Test\n" );
while-Schleife eingefügt, die erst am Ende des Spiels gedruckt wird.
Wie läuft diese Spieleschleife garantiert 25 Mal pro Sekunde, während sie so schnell wie möglich gerendert wird?
Für mich sieht es aus wie ... nichts, es sei denn, mir fehlt etwas GROSSES.
Und ist das nicht eine Struktur dieser while-Schleife, die angeblich 25 Mal pro Sekunde abläuft und dann das Spiel genau so aktualisiert, wie ich es zu Beginn des Artikels erwähnt habe?
Wenn das der Fall ist, warum könnten wir dann nicht etwas Einfaches machen wie:
while( loops < 25 )
{
getInput();
performLogic();
loops++;
}
drawGame();
Und rechne für die Interpolation auf eine andere Art und Weise.
Verzeihen Sie meine extrem lange Frage, aber dieser Artikel hat mir mehr geschadet als geholfen. Ich bin jetzt sehr verwirrt - und habe keine Ahnung, wie ich eine richtige Spieleschleife implementieren soll, weil all diese Fragen auftauchen.
quelle
Antworten:
Ich denke, der Autor hat einen winzigen Fehler gemacht:
while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)
sollte sein
while( GetTickCount() < next_game_tick && loops < MAX_FRAMESKIP)
Das heißt: Solange es noch nicht Zeit ist, unser nächstes Bild zu zeichnen, und wir nicht so viele Bilder wie MAX_FRAMESKIP übersprungen haben, sollten wir warten.
Ich verstehe auch nicht, warum er
next_game_tick
in der Schleife aktualisiert und ich nehme an, dass es ein weiterer Fehler ist. Da zu Beginn eines Frames festgelegt werden kann, wann der nächste Frame sein soll (bei Verwendung einer festen Framerate). Dasnext game tick
hängt nicht davon ab, wie viel Zeit wir nach dem Aktualisieren und Rendern übrig haben.Der Autor macht auch einen anderen häufigen Fehler
Dies bedeutet, dass derselbe Frame mehrmals gerendert wird. Dem Autor ist sogar bewusst:
Dies führt nur dazu, dass die GPU unnötige Arbeit leistet. Wenn das Rendern länger dauert als erwartet, kann dies dazu führen, dass Sie später als geplant mit der Arbeit an Ihrem nächsten Frame beginnen. Daher ist es besser, dem Betriebssystem nachzugeben und zu warten.
quelle
Vielleicht am besten, wenn ich es ein bisschen vereinfache:
while
loop inside mainloop wird verwendet, um Simulationsschritte von wo immer es war, zu wo es jetzt sein sollte, auszuführen.update_game()
Die Funktion sollte immer davon ausgehen, dassSKIP_TICKS
seit dem letzten Aufruf nur eine bestimmte Zeit vergangen ist. Dadurch wird die Spielphysik auf langsamer und schneller Hardware mit konstanter Geschwindigkeit ausgeführt.Durch Erhöhen
next_game_tick
um die Anzahl derSKIP_TICKS
Verschiebungen nähert sich die Zeit der aktuellen Zeit an. Wenn dies größer als die aktuelle Zeit ist, bricht es (current > next_game_tick
) und der Mainloop rendert weiterhin den aktuellen Frame.Nach dem Rendern
GetTickCount()
würde der nächste Aufruf an die neue aktuelle Zeit zurückgeben. Wenn diese Zeit höher ist alsnext_game_tick
, bedeutet dies, dass wir bereits hinter 1-N Schritten in der Simulation zurückliegen und dass wir aufholen sollten, indem wir jeden Schritt in der Simulation mit der gleichen konstanten Geschwindigkeit ausführen. In diesem Fall würde derselbe Frame, falls er niedriger ist, nur erneut gerendert (sofern keine Interpolation vorliegt).Der ursprüngliche Code hatte die Anzahl der Schleifen begrenzt, wenn wir zu weit weg waren (
MAX_FRAMESKIP
). Dies lässt es nur dann tatsächlich etwasGetTickCount()
anzeigen und sieht nicht so aus, als wäre es gesperrt, wenn es beispielsweise nach einer Unterbrechung fortgesetzt wird oder das Spiel im Debugger für eine lange Zeit angehalten wird (vorausgesetzt, es wird in dieser Zeit nicht gestoppt), bis es mit der Zeit aufgeholt hat.Um nutzloses gleiches Frame-Rendering zu vermeiden, wenn Sie keine Interpolation verwenden
display_game()
, können Sie es in die if-Anweisung einschließen , die wie folgt lautet:Dies ist auch ein guter Artikel dazu: http://gafferongames.com/game-physics/fix-your-timestep/
Vielleicht liegt es auch daran, dass Ihre
fprintf
Ausgaben am Ende Ihres Spiels nicht gelöscht wurden.Entschuldige mein Englisch.
quelle
Sein Code sieht völlig gültig aus.
Betrachten Sie die
while
Schleife des letzten Satzes:Ich habe ein System eingerichtet, das besagt: "Überprüfe die aktuelle Zeit, während das Spiel läuft - wenn sie größer ist als unsere laufende Bildrate, und wir haben weniger als 5 Bilder übersprungen, dann überspringen Sie die Zeichnung und aktualisieren Sie einfach die Eingabe und Physik: sonst zeichne die Szene und starte die nächste Update-Iteration "
Bei jedem Update erhöhen Sie die "next_frame" -Zeit um Ihre ideale Bildrate. Dann überprüfen Sie Ihre Zeit erneut. Wenn Ihre aktuelle Zeit jetzt kürzer ist als der Zeitpunkt, zu dem der nächste Frame aktualisiert werden soll, überspringen Sie das Update und zeichnen, was Sie haben.
Wenn Ihre aktuelle_Zeit größer ist (stellen Sie sich vor, dass der letzte Zeichenvorgang sehr lange gedauert hat, weil irgendwo ein Schluckauf aufgetreten ist oder eine Menge Garbage-Collection in einer verwalteten Sprache oder eine Implementierung für verwalteten Speicher in C ++ oder was auch immer), dann Das Zeichnen wird übersprungen und
next_frame
mit einem weiteren zusätzlichen Frame aktualisiert , bis entweder die Aktualisierungen auf dem Stand sind, an dem wir auf der Uhr sein sollten, oder es wurden genügend Frames übersprungen, die wir unbedingt zeichnen MÜSSEN, damit der Spieler sehen kann was sie tunWenn Ihr Computer superschnell oder Ihr Spiel supereinfach ist, ist dies
current_time
möglicherweise weniger alsnext_frame
häufig, was bedeutet, dass Sie während dieser Punkte keine Aktualisierung durchführen.Hier kommt die Interpolation ins Spiel. Ebenso könnte ein separater Bool außerhalb der Schleifen deklariert werden, um "schmutzigen" Raum zu deklarieren.
Innerhalb der Update-Schleife würden Sie festlegen
dirty = true
, dass Sie tatsächlich ein Update durchgeführt haben.Anstatt nur anzurufen
draw()
, würden Sie dann sagen:Dann verpassen Sie keine Interpolation für reibungslose Bewegungen, stellen jedoch sicher, dass Sie nur dann aktualisieren, wenn tatsächlich Aktualisierungen durchgeführt werden (anstatt Interpolationen zwischen Zuständen).
Wenn Sie mutig sind, gibt es einen Artikel mit dem Titel "Fix Your Timestep!" von GafferOnGames.
Es geht das Problem etwas anders an, aber ich halte es für eine hübschere Lösung, die zumeist das Gleiche tut (abhängig von den Funktionen Ihrer Sprache und davon, wie sehr Sie sich um die physikalischen Berechnungen Ihres Spiels kümmern).
quelle