Es gibt Dutzende von Artikeln, Büchern und Diskussionen in Spieleschleifen. Allerdings stoße ich ziemlich oft auf Folgendes:
while(running)
{
processInput();
while(isTimeForUpdate)
{
update();
}
render();
}
Was mich an diesem Ansatz grundsätzlich stört, ist das "update-unabhängige" Rendern, z. B. das Rendern eines Frames, wenn sich überhaupt nichts ändert. Meine Frage ist also, warum dieser Ansatz oft gelehrt wird?
while (isTimeForUpdate)
, nichtif (isTimeForUpdate)
. Das Hauptziel ist nicht,render()
wenn es keine gabupdate()
, sondernupdate()
immer wieder dazwischenrender()
s. Unabhängig davon haben beide Situationen gültige Verwendungen. Ersteres ist gültig, wenn sich der Status außerhalb Ihrerupdate
Funktion ändern kann , z. B. basierend auf dem impliziten Status wie der aktuellen Uhrzeit, was gerendert wird. Letzteres ist gültig, weil Ihre Physik-Engine dadurch die Möglichkeit hat, viele kleine, präzise Aktualisierungen durchzuführen, die z. B. das Risiko des "Verziehens" durch Hindernisse verringern.Antworten:
Es gibt eine lange Geschichte, wie wir zu diesem gemeinsamen Kongress gekommen sind, mit vielen faszinierenden Herausforderungen auf dem Weg, also werde ich versuchen, ihn schrittweise zu motivieren:
1. Problem: Geräte laufen unterschiedlich schnell
Versuchen Sie jemals, ein altes DOS-Spiel auf einem modernen PC zu spielen, und es läuft unspielbar schnell - nur eine Unschärfe?
Viele alte Spiele hatten eine sehr naive Update-Schleife - sie sammelten Eingaben, aktualisierten den Spielstatus und renderten so schnell, wie es die Hardware zuließ, ohne zu berücksichtigen, wie viel Zeit vergangen war. Das heißt, sobald sich die Hardware ändert, ändert sich das Gameplay.
Im Allgemeinen möchten wir, dass unsere Spieler auf einer Reihe von Geräten ein konsistentes Spielerlebnis und Spielgefühl haben (sofern sie bestimmte Mindestanforderungen erfüllen), unabhängig davon, ob sie das letztjährige Telefon oder das neueste Modell, einen Top-End-Gaming-Desktop oder ein Tablet verwenden mittelgroßer Laptop.
Insbesondere für Spiele, die wettbewerbsfähig sind (entweder im Mehrspielermodus oder über Bestenlisten), möchten wir nicht, dass Spieler, die auf einem bestimmten Gerät laufen, einen Vorteil gegenüber anderen haben, da sie schneller laufen oder mehr Zeit haben, um zu reagieren.
Die todsichere Lösung besteht darin, die Rate festzulegen, mit der wir Spielstatusaktualisierungen durchführen. Auf diese Weise können wir garantieren, dass die Ergebnisse immer gleich sind.
2. Warum also nicht einfach die Framerate sperren (z. B. mit VSync) und trotzdem die Gameplay-Statusaktualisierungen und das Rendering im Gleichschritt ausführen?
Dies kann funktionieren, ist aber für das Publikum nicht immer angenehm. Es gab eine lange Zeit, als das Laufen mit soliden 30 fps als Goldstandard für Spiele galt. Spieler erwarten jetzt routinemäßig mindestens 60 fps, insbesondere in Multiplayer-Actionspielen, und einige ältere Titel sehen jetzt merklich abgehackt aus, da sich unsere Erwartungen geändert haben. Es gibt auch eine Gesangsgruppe von PC-Spielern, die überhaupt gegen die Framerate-Sperre sind. Sie haben eine Menge für ihre hochmoderne Hardware bezahlt und möchten diesen Computermuskel für die reibungsloseste Wiedergabe mit höchster Wiedergabetreue verwenden können.
Insbesondere in der VR ist die Framerate das A und O, und der Standard schleicht sich immer weiter an. Zu Beginn der jüngsten Wiederbelebung von VR liefen die Spiele häufig mit etwa 60 fps. Jetzt ist 90 mehr Standard, und Hardware wie die PSVR beginnt, 120 zu unterstützen. Dies dürfte noch weiter steigen. Wenn also ein VR-Spiel seine Framerate auf das beschränkt, was heute machbar und akzeptiert ist, wird es wahrscheinlich zurückbleiben, wenn sich Hardware und Erwartungen weiterentwickeln.
(In der Regel ist Vorsicht geboten, wenn gesagt wird, dass "Spieler nichts schneller als XXX wahrnehmen können", da dies normalerweise auf einer bestimmten Art von "Wahrnehmung" beruht, z empfindlich.)
Das letzte Problem hier ist, dass ein Spiel mit einer gesperrten Bildrate auch konservativ sein muss - wenn Sie jemals einen Moment im Spiel erreicht haben, in dem Sie eine ungewöhnlich hohe Anzahl von Objekten aktualisieren und anzeigen, möchten Sie Ihren Frame nicht verpassen Frist und verursachen ein merkliches Ruckeln oder Hacken. Sie müssen also entweder Ihr Inhaltsbudget so niedrig einstellen, dass Sie genügend Spielraum haben, oder Sie müssen in komplexere Funktionen zur dynamischen Qualitätsanpassung investieren, um zu vermeiden, dass das gesamte Spielerlebnis auf die schlechteste Leistung auf Hardware mit Mindestanforderungen beschränkt wird.
Dies kann besonders problematisch sein, wenn die Leistungsprobleme erst spät in der Entwicklung auftreten, wenn alle Ihre vorhandenen Systeme unter der Annahme einer Lockstep-Rendering-Framerate erstellt und optimiert wurden, die Sie jetzt nicht mehr immer erreichen können. Das Entkoppeln von Aktualisierungs- und Rendering-Raten bietet mehr Flexibilität beim Umgang mit Leistungsschwankungen.
3. Treten beim Aktualisieren zu einem festgelegten Zeitpunkt nicht die gleichen Probleme auf wie bei (2)?
Ich denke, dies ist das Kernstück der ursprünglichen Frage: Wenn wir unsere Updates entkoppeln und manchmal zwei Frames ohne dazwischen liegende Spielstatus-Updates rendern, ist dies nicht dasselbe wie das Lockstep-Rendering mit einer niedrigeren Framerate, da es keine sichtbaren Änderungen gibt der Bildschirm?
Tatsächlich gibt es verschiedene Möglichkeiten, wie Spiele die Entkopplung dieser Updates effektiv nutzen können:
a) Die Aktualisierungsrate kann schneller als die gerenderte Bildrate sein
Wie Tyjkenn in einer anderen Antwort festhält, wird insbesondere die Physik häufig mit einer höheren Frequenz als das Rendern ausgeführt, was zur Minimierung von Integrationsfehlern und zu genaueren Kollisionen beiträgt. Anstatt 0 oder 1 Aktualisierungen zwischen gerenderten Bildern zu haben, könnten Sie also 5 oder 10 oder 50 haben.
Jetzt kann der Spieler, der mit 120 fps rendert, 2 Aktualisierungen pro Frame erhalten, während der Spieler mit niedrigerer Hardware-Spezifikation, der mit 30 fps rendert, 8 Aktualisierungen pro Frame erhält und beide Spiele mit der gleichen Geschwindigkeit von Gameplay-Ticks pro Echtzeit-Sekunde laufen. Durch die bessere Hardware sieht es flüssiger aus, ändert aber nichts an der Funktionsweise des Spiels.
Hier besteht die Gefahr, dass bei einer Nichtübereinstimmung der Aktualisierungsrate mit der Bildrate eine "Taktfrequenz" zwischen den beiden Werten erzielt wird . Z.B. In den meisten Frames haben wir genug Zeit für 4 Spielzustandsaktualisierungen und einen kleinen Rest. Dann haben wir von Zeit zu Zeit genug Zeit, um 5 Aktualisierungen in einem Frame durchzuführen, was zu einem kleinen Sprung oder Stottern in der Bewegung führt. Dies kann angegangen werden durch ...
b) Interpolieren (oder Extrapolieren) des Spielzustands zwischen Aktualisierungen
Hier lassen wir den Spielstatus oft einen festen Zeitschritt in der Zukunft leben und speichern genug Informationen aus den letzten beiden Status, damit wir einen beliebigen Punkt zwischen ihnen rendern können. Wenn wir dann bereit sind, ein neues Bild auf dem Bildschirm anzuzeigen, werden wir nur zu Anzeigezwecken auf den entsprechenden Zeitpunkt überblendet (dh wir ändern hier nicht den zugrunde liegenden Gameplay-Status).
Wenn dies richtig gemacht wird, fühlt sich die Bewegung butterweich an und hilft sogar dabei, Schwankungen in der Framerate zu verbergen, solange wir nicht zu tief fallen.
c) Hinzufügen von Glätte zu Änderungen außerhalb des Spielzustands
Auch ohne Interpolation des Gameplay-Status können wir noch einige Smoothness-Gewinne erzielen.
Rein visuelle Änderungen wie Charakteranimationen, Partikelsysteme oder VFX und Benutzeroberflächenelemente wie HUD werden häufig unabhängig vom festgelegten Zeitschritt des Spielzustands aktualisiert. Das bedeutet, wenn wir unseren Gameplay-Status mehrmals pro Frame testen, zahlen wir nicht mit jedem Tick ihre Kosten - nur beim letzten Render-Pass. Stattdessen skalieren wir die Wiedergabegeschwindigkeit dieser Effekte, um sie an die Länge des Frames anzupassen, damit sie so flüssig wiedergegeben werden, wie es die Rendering-Framerate zulässt, ohne die Spielgeschwindigkeit oder die Fairness zu beeinträchtigen, wie in (1) erläutert.
Die Kamerabewegung kann dies auch tun - insbesondere in VR zeigen wir manchmal dasselbe Bild mehr als einmal, projizieren es jedoch neu, um die Kopfbewegung des Spielers dazwischen zu berücksichtigen , sodass wir die wahrgenommene Latenz und den Komfort verbessern können, selbst wenn wir können nicht von Haus aus alles so schnell rendern. Einige Spiele-Streaming-Systeme (bei denen das Spiel auf einem Server ausgeführt wird und der Spieler nur einen Thin Client ausführt) verwenden auch eine Version davon.
4. Warum nicht einfach diesen (c) Stil für alles verwenden? Wenn es für Animation und Benutzeroberfläche funktioniert, können wir dann nicht einfach die Gameplay-Statusaktualisierungen so skalieren, dass sie mit der aktuellen Bildrate übereinstimmen?
Ja * das ist möglich, aber nein, es ist nicht einfach.
Diese Antwort ist schon ein bisschen lang, so dass ich nicht auf alle Details eingehen werde, nur eine kurze Zusammenfassung:
Multiplikation mit
deltaTime
Werken zur Anpassung an Aktualisierungen mit variabler Länge für lineare Änderungen (z. B. Bewegung mit konstanter Geschwindigkeit, Countdown eines Timers oder Fortschritt entlang einer Animationszeitleiste)Leider sind viele Aspekte von Spielen nicht linear . Sogar etwas so Einfaches wie die Schwerkraft erfordert ausgefeiltere Integrationstechniken oder Teilschritte mit höherer Auflösung, um divergierende Ergebnisse bei unterschiedlichen Frameraten zu vermeiden. Die Eingabe und Steuerung durch den Spieler ist selbst eine große Quelle der Nichtlinearität.
Insbesondere hängen die Ergebnisse der Erkennung und Auflösung diskreter Kollisionen von der Aktualisierungsrate ab, was zu Tunnel- und Jitterfehlern führt, wenn die Frames zu lang werden. Eine variable Framerate zwingt uns, komplexere / teurere kontinuierliche Kollisionserkennungsmethoden für einen größeren Teil unseres Inhalts zu verwenden oder Variabilität in unserer Physik zu tolerieren. Sogar die kontinuierliche Kollisionserkennung ist eine Herausforderung, wenn sich Objekte in Bögen bewegen und kürzere Zeitschritte erfordern ...
Im allgemeinen Fall eines Spiels mittlerer Komplexität liegt die Aufrechterhaltung eines konsistenten Verhaltens und einer Fairness durch
deltaTime
Skalierung zwischen sehr schwierig und wartungsintensiv bis gar nicht realisierbar.Durch die Standardisierung einer Aktualisierungsrate können wir ein konsistenteres Verhalten unter verschiedenen Bedingungen gewährleisten , häufig mit einfacherem Code.
Dass die Aktualisierungsrate zu halten von Rendering entkoppelt gibt uns die Flexibilität , die Glätte und die Leistung der Erfahrung zu steuern , ohne Veränderung der Spiellogik .
Selbst dann bekommen wir nie wirklich die "perfekte" Unabhängigkeit von Frameraten, aber wie so viele Ansätze in Spielen gibt es uns eine steuerbare Methode, uns in Richtung "gut genug" für die Bedürfnisse eines bestimmten Spiels einzuwählen. Deshalb wird es allgemein als nützlicher Ausgangspunkt gelehrt.
quelle
read-update-render
beträgt die Latenz im schlimmsten Fall 17 ms (Grafikpipeline und Anzeigelatenz werden derzeit ignoriert). Bei einer entkoppelten(read-update)x(n>1)-render
Schleife mit derselben Framerate kann unsere Latenz im ungünstigsten Fall nur gleich oder besser sein, da wir Eingaben so häufig oder häufiger prüfen und darauf reagieren. :)Die anderen Antworten sind gut und sprechen darüber, warum die Spieleschleife existiert und von der Render-Schleife getrennt sein sollte. Was jedoch das konkrete Beispiel für "Warum einen Frame rendern, wenn sich nichts geändert hat?" Es kommt wirklich nur auf Hardware und Komplexität an.
Grafikkarten sind Zustandsautomaten und sie können wirklich immer wieder das Gleiche tun. Wenn Sie nur Dinge rendern, die sich geändert haben, ist dies tatsächlich teurer und nicht weniger. In den meisten Szenarien ist nicht viel statisch. Wenn Sie sich in einem FPS-Spiel leicht nach links bewegen, die Pixeldaten von 98% der Inhalte auf dem Bildschirm geändert haben, können Sie auch den gesamten Frame rendern.
Vor allem aber Komplexität. Das Verfolgen von Änderungen während eines Updates ist sehr viel teurer, da Sie entweder alles überarbeiten oder das alte Ergebnis eines Algorithmus verfolgen müssen, es mit dem neuen Ergebnis vergleichen und dieses Pixel nur rendern müssen, wenn die Änderung unterschiedlich ist. Das hängt vom System ab.
Das Design von Hardware usw. ist weitgehend für aktuelle Konventionen optimiert, und eine Zustandsmaschine war ein gutes Modell, um damit anzufangen.
quelle
Das Rendern ist normalerweise der langsamste Vorgang in der Spieleschleife. Menschen bemerken einen Unterschied in der Framerate nicht leicht schneller als 60, daher ist es oft weniger wichtig, Zeit für ein schnelleres Rendern zu verschwenden. Es gibt jedoch andere Prozesse, die von einer schnelleren Rate mehr profitieren würden. Die Physik ist eine. Eine zu große Änderung in einer Schleife kann dazu führen, dass Objekte direkt an den Wänden vorbeigehen. Es mag Möglichkeiten geben, einfache Kollisionsfehler in größeren Schritten zu umgehen, aber für viele komplexe physikalische Interaktionen werden Sie nicht die gleiche Genauigkeit erzielen. Wenn die Physikschleife jedoch häufiger ausgeführt wird, ist die Wahrscheinlichkeit von Störungen geringer, da Objekte in kleineren Schritten verschoben werden können, ohne jedes Mal gerendert zu werden. Mehr Ressourcen fließen in die sensible Physik-Engine und weniger werden für das Zeichnen von mehr Frames verschwendet, die der Benutzer nicht sehen kann.
Dies ist besonders wichtig bei grafikintensiven Spielen. Wenn es für jede Spielrunde ein Rendering gäbe und ein Spieler nicht über die leistungsstärkste Maschine verfügte, könnte es Punkte im Spiel geben, an denen die fps auf 30 oder 40 sinken Das Spiel würde ziemlich langsam werden, wenn wir versuchen würden, jede Änderung der Physik einigermaßen klein zu halten, um Störungen zu vermeiden. Der Spieler würde sich darüber ärgern, dass sein Charakter nur halb so schnell läuft wie normal. Wenn die Rendering-Rate jedoch vom Rest der Schleife unabhängig wäre, könnte der Player trotz des Frameratenabfalls eine feste Schrittgeschwindigkeit beibehalten.
quelle
Eine Konstruktion wie die in Ihrer Frage kann sinnvoll sein, wenn das Rendersubsystem eine Vorstellung von "verstrichener Zeit seit dem letzten Rendern" hat .
Betrachten Sie beispielsweise einen Ansatz, bei dem die Position eines Objekts in der Spielwelt durch feste
(x,y,z)
Koordinaten dargestellt wird, wobei der aktuelle Bewegungsvektor zusätzlich gespeichert wird(dx,dy,dz)
. Jetzt können Sie Ihre Spielschleife so schreiben, dass die Änderung der Position in derupdate
Methode erfolgen muss, aber Sie können sie auch so gestalten, dass die Änderung der Bewegung während erfolgtupdate
. Mit dem letzteren Ansatz wird sich Ihr Spielstatus zwar erst im nächsten Moment ändernupdate
, aber arender
-Funktion, die mit einer höheren Frequenz aufgerufen wird, könnte das Objekt bereits an einer leicht aktualisierten Position zeichnen. Während dies technisch zu einer Diskrepanz zwischen dem, was Sie sehen und dem, was intern dargestellt wird, führt, ist der Unterschied klein genug, um für die meisten praktischen Aspekte keine Rolle zu spielen, ermöglicht es jedoch, dass Animationen viel glatter aussehen.Die Vorhersage der "Zukunft" Ihres Spielzustands (trotz des Risikos, falsch zu sein) kann eine gute Idee sein, wenn Sie beispielsweise die Latenzen von Netzwerkeingaben berücksichtigen.
quelle
Neben anderen Antworten ...
Das Überprüfen auf Statusänderungen erfordert eine umfangreiche Verarbeitung. Wenn die Überprüfung auf Änderungen ähnlich lange dauert (oder länger dauert!) Als die eigentliche Verarbeitung, haben Sie die Situation nicht wirklich verbessert. Wenn Sie ein Bild rendern, wie @Waddles sagt, ist eine Grafikkarte wirklich gut darin, immer wieder das Gleiche zu tun, und es ist teurer, jeden Datenblock auf Änderungen zu überprüfen, als ihn einfach zu übertragen auf die Grafikkarte zur Verarbeitung. Auch wenn es sich um ein Gameplay handelt, ist es unwahrscheinlich, dass sich der Bildschirm im letzten Tick nicht geändert hat.
Sie gehen auch davon aus, dass das Rendern viel Prozessorzeit beansprucht. Dies hängt stark von Ihrem Prozessor und Ihrer Grafikkarte ab. Seit vielen Jahren liegt der Schwerpunkt darauf, immer komplexere Rendering-Aufgaben auf die Grafikkarte zu verlagern und den vom Prozessor benötigten Rendering-Aufwand zu reduzieren. Im Idealfall sollte der
render()
Anruf des Prozessors einfach eine DMA-Übertragung einrichten und das wars. Das Abrufen von Daten zur Grafikkarte wird dann an den Speichercontroller delegiert, und das Erzeugen des Bildes wird an die Grafikkarte delegiert. Sie können das in ihrer eigenen Zeit tun, während der Prozessor parallel arbeitetmacht weiter mit der Physik, der Gameplay-Engine und all den anderen Dingen, die ein Prozessor besser kann. Offensichtlich ist die Realität viel komplizierter, aber es ist auch ein wichtiger Faktor, die Arbeit auf andere Teile des Systems auslagern zu können.quelle