EXTREM verwirrt über "Constant Game Speed ​​Maximum FPS" -Spielschleife


12

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 whileAussage erweitern, erhalten wir:

while( ( 750 > 500 ) && ( 0 < 5 ) )

750, weil die Zeit seit next_game_tickder Zuweisung vergangen ist . loopsist 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_FRAMESKIPund 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.


1
Rants richten sich besser an den Autor des Artikels. Welcher Teil ist Ihre objektive Frage ?
Anko

3
Ist diese Spieleschleife überhaupt gültig, erklärt jemand. Nach meinen Tests hat es nicht die richtige Struktur, um 25 Mal pro Sekunde zu laufen. Erkläre mir, warum es so ist. Auch das ist kein Scherz, das ist eine Reihe von Fragen. Muss ich Emoticons verwenden, scheine ich wütend?
Tsujp

2
Da Ihre Frage auf "Was verstehe ich nicht über diese Spieleschleife" hinausläuft und Sie viele fett gedruckte Wörter haben, wirkt sie zumindest verärgert.
Kirbinator

@Kirbinator Ich kann das zu schätzen wissen, aber ich habe versucht, alles zu erden, was ich in diesem Artikel ungewöhnlich finde, damit es keine flache und leere Frage ist. Ich halte es sowieso nicht für wesentlich, dass ich ein paar gültige Punkte haben möchte - schließlich ist das ein Artikel, der versucht zu lehren, aber nicht so gute Arbeit leistet.
Tsujp

7
Versteh mich nicht falsch, es ist eine gute Frage, es könnte nur 80% kürzer sein.
Kirbinator

Antworten:


8

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_tickin 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

mit was auch immer übrig bleibt, mache das Spiel so oft wie möglich.

Dies bedeutet, dass derselbe Frame mehrmals gerendert wird. Dem Autor ist sogar bewusst:

Das Spiel wird 50 Mal pro Sekunde aktualisiert und das Rendern erfolgt so schnell wie möglich. Beachten Sie, dass beim Rendern von mehr als 50 Bildern pro Sekunde einige nachfolgende Bilder gleich sind, sodass tatsächliche visuelle Bilder mit maximal 50 Bildern pro Sekunde angezeigt werden.

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.


+ Abstimmung. Mmmm. Dies lässt mich in der Tat verwirrt, was ich dann tun soll. Ich danke Ihnen für Ihre Einsicht. Ich werde wahrscheinlich ein wenig herumspielen. Das Problem ist, dass ich wirklich weiß, wie man FPS einschränkt oder wie man FPS dynamisch einstellt und wie man das macht. Meiner Meinung nach ist eine feste Eingabe erforderlich, damit das Spiel für alle gleich schnell läuft. Dies ist nur ein einfaches 2D-Platformer-MMO (auf lange Sicht)
Tsujp

Solange die Schleife korrekt erscheint und next_game_tick erhöht, ist sie dafür da. Damit kann die Simulation auf langsamer und schneller Hardware mit konstanter Geschwindigkeit ausgeführt werden. Das Ausführen des Rendering-Codes "so schnell wie möglich" (oder sowieso schneller als Physik) ist nur dann sinnvoll, wenn Interpolation im Rendering-Code vorhanden ist, um es für schnelle Objekte usw. (Ende des Artikels) reibungsloser zu gestalten Es wird mehr gerendert als das, was das Ausgabegerät (der Bildschirm) anzeigen kann.
Dotti

Sein Code ist also eine gültige Implementierung. Und wir müssen nur mit dieser Verschwendung leben? Oder gibt es Methoden, nach denen ich suchen kann?
Tsujp

Dem Betriebssystem nachzugeben und zu warten ist nur dann eine gute Idee, wenn Sie eine gute Vorstellung davon haben, wann das Betriebssystem die Kontrolle an Sie zurückgibt - möglicherweise nicht so schnell, wie Sie möchten.
Kylotan

Ich kann diese Antwort nicht bewerten. Ihm fehlt das Interpolationsmaterial komplett, wodurch die gesamte zweite Hälfte der Antwort ungültig wird.
AlbeyAmakiir

4

Vielleicht am besten, wenn ich es ein bisschen vereinfache:

while( game_is_running ) {

    current = GetTickCount();
    while(current > next_game_tick) {
        update_game();

        next_game_tick += SKIP_TICKS;
    }
    display_game();
}

whileloop 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, dass SKIP_TICKSseit 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_tickum die Anzahl der SKIP_TICKSVerschiebungen 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 als next_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 etwas GetTickCount()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:

while (game_is_running) {
    current = GetTickCount();
    if (current > next_game_tick) {
        while(current > next_game_tick) {
            update_game();

            next_game_tick += SKIP_TICKS;
        }
    display_game();
    }
    else {
    // could even sleep here
    }
}

Dies ist auch ein guter Artikel dazu: http://gafferongames.com/game-physics/fix-your-timestep/

Vielleicht liegt es auch daran, dass Ihre fprintfAusgaben am Ende Ihres Spiels nicht gelöscht wurden.

Entschuldige mein Englisch.


4

Sein Code sieht völlig gültig aus.

Betrachten Sie die whileSchleife des letzten Satzes:

// JS / pseudocode
var current_time = function () { return Date.now(); }, // in ms
    framerate = 1000/30, // 30fps
    next_frame = current_time(),

    max_updates_per_draw = 5,

    iterations;

while (game_running) {

    iterations = 0;

    while (current_time() > next_frame && iterations < max_updates_per_draw) {
        update_game(); // input, physics, audio, etc

        next_frame += framerate;
        iterations += 1;
    }

    draw();
}

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_framemit 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 tun

Wenn Ihr Computer superschnell oder Ihr Spiel supereinfach ist, ist dies current_timemöglicherweise weniger als next_framehä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:

if (is_dirty) {
    draw(); 
    is_dirty = false;
}

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).

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.