Eine gute Möglichkeit, eine Spieleschleife in OpenGL zu erstellen

31

Ich fange gerade an, OpenGL in der Schule zu lernen, und ich habe neulich angefangen, ein einfaches Spiel zu machen (allein, nicht für die Schule). Ich verwende freeglut und erstelle es in C, also habe ich für meine Spieleschleife wirklich gerade eine Funktion verwendet, an die ich mich gewöhnt habe, glutIdleFuncum die gesamte Zeichnung und Physik in einem Durchgang zu aktualisieren. Dies war in Ordnung für einfache Animationen, bei denen mir die Framerate nicht so wichtig war, aber da das Spiel hauptsächlich auf Physik basiert, möchte ich wirklich festhalten, wie schnell es aktualisiert wird (müssen).

Daher bestand mein erster Versuch darin, meine Funktion an glutIdleFunc( myIdle()) zu übergeben, um zu verfolgen, wie viel Zeit seit dem vorherigen Aufruf vergangen ist, und die Physik (und derzeit die Grafiken) alle so viele Millisekunden zu aktualisieren. Früher timeGetTime()habe ich das gemacht (mit <windows.h>). Und das brachte mich zum Nachdenken: Ist die Verwendung der Leerlauffunktion wirklich eine gute Methode, um die Spielschleife zu durchlaufen?

Meine Frage ist, was ist ein besserer Weg, um die Spieleschleife in OpenGL zu implementieren? Sollte ich es vermeiden, die Leerlauffunktion zu verwenden?

Jeff
quelle
gamedev.stackexchange.com/questions/651/… Duplizieren?
Die kommunistische Ente
Ich denke, diese Frage ist spezifischer für OpenGL und ob die Verwendung von GLUT für die Schleife ratsam ist
Jeff
1
Hier, Mann , benutze GLUT nicht für größere Projekte . Es ist jedoch in Ordnung für kleinere.
Bobobobo

Antworten:

29

Die einfache Antwort lautet: Nein, Sie möchten den glutIdleFunc-Rückruf nicht in einem Spiel verwenden, das eine Art Simulation enthält. Der Grund dafür ist, dass diese Funktion die Animation trennt und Code aus der Fensterereignisbehandlung zeichnet, jedoch nicht asynchron. Mit anderen Worten, das Empfangen und Übergeben von Fensterereignissen, bei denen Stalls Code zeichnen (oder was auch immer Sie in diesen Rückruf einfügen), ist für eine interaktive Anwendung (Interaktion, dann Antwort) vollkommen in Ordnung, jedoch nicht für ein Spiel, bei dem die Physik oder der Spielstatus fortschreiten müssen unabhängig von Interaktion oder Renderzeit.

Sie möchten das Eingabehandling, den Spielstatus und den Zeichencode vollständig entkoppeln. Es gibt eine einfache und übersichtliche Lösung, bei der die Grafikbibliothek nicht direkt einbezogen wird (dh sie ist portabel und einfach zu visualisieren). Sie möchten, dass die gesamte Spielschleife Zeit produziert und die Simulation die produzierte Zeit (in Stücken) verbraucht. Der Schlüssel ist jedoch, die Zeit, die Ihre Simulation benötigt, in Ihre Animation zu integrieren.

Die beste Erklärung und das beste Tutorial, das ich dazu gefunden habe, ist Glenn Fiedlers Fix Your Timestep

Dieses Tutorial hat die vollständige Beschreibung. Wenn Sie jedoch keine tatsächliche Physiksimulation haben, können Sie die eigentliche Integration überspringen, aber die grundlegende Schleife läuft immer noch auf (in ausführlichem Pseudocode):

// The amount of time we want to simulate each step, in milliseconds
// (written as implicit frame-rate)
timeDelta = 1000/30
timeAccumulator = 0
while ( game should run )
{
  timeSimulatedThisIteration = 0
  startTime = currentTime()

  while ( timeAccumulator >= timeDelta )
  {
    stepGameState( timeDelta )
    timeAccumulator -= timeDelta
    timeSimulatedThisIteration += timeDelta
  }

  stepAnimation( timeSimulatedThisIteration )

  renderFrame() // OpenGL frame drawing code goes here

  handleUserInput()

  timeAccumulator += currentTime() - startTime 
}

Wenn Sie dies auf diese Weise tun, führen Stalls in Ihrem Rendercode, der Eingabeverarbeitung oder dem Betriebssystem nicht dazu, dass Ihr Spielstatus zurückfällt. Diese Methode ist auch portabel und unabhängig von der Grafikbibliothek.

GLUT ist eine gute Bibliothek, jedoch streng ereignisgesteuert. Sie registrieren Rückrufe und lösen die Hauptschleife aus. Sie geben die Kontrolle über Ihre Hauptschleife immer mit GLUT ab. Es gibt Hacks, um das zu umgehen , Sie können auch eine externe Schleife mit Timern und dergleichen vortäuschen, aber eine andere Bibliothek ist wahrscheinlich der bessere (einfachere) Weg. Es gibt viele Alternativen, hier einige (mit guter Dokumentation und kurzen Anleitungen):

  • GLFW , mit dem Sie Eingabeereignisse inline abrufen können (in Ihrer eigenen Hauptschleife).
  • SDL liegt jedoch nicht speziell auf OpenGL.
charstar
quelle
Guter Artikel. Um dies in OpenGL zu tun, würde ich nicht die glutMainLoop verwenden, sondern meine eigene? Wenn ich das tun würde, könnte ich die glutMouseFunc und andere Funktionen verwenden? Ich hoffe, das macht Sinn, ich bin noch ziemlich neu in all dem
Jeff
Leider nicht. Alle in GLUT registrierten Rückrufe werden in glutMainLoop ausgelöst, wenn die Ereigniswarteschlange geleert und iteriert wird. Ich habe dem Antworttext einige Links zu Alternativen hinzugefügt.
Charstar
1
+1 für das Abwerfen von GLUT für alles andere. GLUT war meines Wissens nie für etwas anderes als Testanwendungen gedacht.
Jari Komppa
Sie müssen aber immer noch mit Systemereignissen umgehen, nicht wahr? Mein Verständnis war, dass mit glutIdleFunc nur die (in Windows) GetMessage-TranslateMessage-DispatchMessage-Schleife behandelt und Ihre Funktion aufgerufen wird, wenn keine Nachricht abgerufen werden muss. Sie würden das sowieso selbst tun oder die Ungeduld des Betriebssystems erleiden, wenn Sie Ihre Anwendung als nicht reagierend markieren, oder?
Ricket
@Ricket Technisch behandelt glutMainLoopEvent () eingehende Ereignisse durch Aufrufen der registrierten Rückrufe. glutMainLoop () ist effektiv {glutMainLoopEvent (); IdleCallback (); } Eine wichtige Überlegung hierbei ist, dass das Neuzeichnen auch ein Rückruf innerhalb der Ereignisschleife ist. Sie können eine ereignisgesteuerte Bibliothek dazu bringen, sich wie eine
zeitgesteuerte zu
4

Ich denke, glutIdleFuncist gut so; Es ist im Wesentlichen das, was Sie am Ende sowieso von Hand tun würden, wenn Sie es nicht verwenden würden. Egal, was Sie haben werden, eine enge Schleife, die nicht in einem festen Muster aufgerufen wird, und Sie müssen die Wahl treffen, ob Sie sie in eine Schleife mit fester Zeit umwandeln oder eine Schleife mit variabler Zeit beibehalten und ausführen möchten Stellen Sie sicher, dass alle Ihre mathematischen Berechnungen die Interpolationszeit berücksichtigen. Oder auch irgendwie eine Mischung daraus.

Sie können sicherlich eine myIdleFunktion haben, an die Sie übergeben glutIdleFunc, und innerhalb dieser Funktion die verstrichene Zeit messen, durch Ihren festgelegten Zeitschritt dividieren und eine andere Funktion so oft aufrufen.

Folgendes sollten Sie nicht tun:

void myFunc() {
    // (calculate timeDifference here)
    int numOfTimes = timeDifference / 10;
    for(int i=0; i<numOfTimes; i++) {
        fixedTimestep();
    }
}

fixedTimestep würde nicht alle 10 Millisekunden aufgerufen. Tatsächlich würde es langsamer als in Echtzeit ablaufen, und Sie könnten am Ende falsche Zahlen erhalten, um zu kompensieren, wenn die eigentliche Wurzel des Problems im Verlust des Restbetrags liegt, wenn Sie timeDifferencedurch 10 dividieren .

Tun Sie stattdessen Folgendes:

long queuedMilliseconds; // static or whatever, this must persist outside of your loop

void myFunc() {
    // (calculate timeDifference here)
    queuedMilliseconds += timeDifference;
    while(queuedMilliseconds >= 10) {
        fixedTimestep();
        queuedMilliseconds -= 10;
    }
}

Diese Schleifenstruktur stellt nun sicher, dass Ihre fixedTimestepFunktion einmal pro 10 Millisekunden aufgerufen wird, ohne dass Millisekunden als Rest oder Ähnliches verloren gehen.

Aufgrund des Multitasking-Charakters von Betriebssystemen können Sie sich nicht darauf verlassen, dass eine Funktion genau alle 10 Millisekunden aufgerufen wird. Die obige Schleife würde jedoch schnell genug ablaufen, so dass sie hoffentlich eng genug wäre, und Sie können davon ausgehen, dass jeder Aufruf von fixedTimestepIhre Simulation um 10 Millisekunden inkrementieren würde.

Bitte lesen Sie auch diese Antwort und insbesondere die in der Antwort enthaltenen Links.

Ricket
quelle