Erklärung zu "fixed time steps"
-
Ah danke, aber was ist wenn dann die Logik mehr als ein FPS braucht, weil z.B. mehr Einheiten berechnet werden müssen, dann sollte doch der Wert dynamisch in der Update Funktion beschrieben werden?
-
@chris4cpp Ich bin mir nicht sicher, aber wenn (Ausgehend vom ersten Beispiel)
TimePerFrame = 0.1s
ist, das Updaten 1s dauert und das Rendern 0.1s, was sehr unwahrscheinlich ist:-
Durchlauf:
render()
-
Durchlauf:
update()
render()
-
Durchlauf:
11 xupdate()
render()
-
Durchlauf:
111 xupdate()
render()
Update soll immer die gleiche
TimePerFrame
bekommen (Hier 0.1s). Wennupdate()
1s undrender()
0.1s dauern, ist dietimeSinceLastUpdate
also 1.1s. Daher wird im nächsten durchgang 11 x die while Schleife durchgegangen, weil solange 0.1s abgezogen werden, bis dietimeSinceLastUpdate
kleiner alsTimePerFrame
ist.
Das braucht aber jetzt wieder 11 x 1s, so dass immer öfterupdate()
und nur ganz seltenrender()
aufgerufen wird. Das verhältnis von Update- zu Renderaufrufen steigt bei dieser Ausgangsituation circa um den Faktor 10x pro neuem Durchgang. Aberupdate()
bekommt immer die gleiche Zeit
Ich hoffe ich rede keinen Unsinn
-
-
Und deswegen wird das in keinen realen Projekt so umgesetzt. Wenn die gemessene vergangene Zeit zu gross ist, wird man die clampen, so das es z.B. max 5 Updates gibt und das Programm in dem Moment einfach langsamer läuft.
-
@daniel sagte in Erklärung zu "fixed time steps":
Wenn immer die gleiche Zeit an update übergeben wird, kann ich die Übergabe ja auch weglassen
Richtig. Ausser wenn du ein Framework verwendest wo im Gerüst bereits eine Update-Funktion enthalten ist die einen Delta-Time Wert als Parameter hat. Ich kenne SFML nicht, daher weiss ich nicht wie das bei SFML ist.
-
@TGGC sagte in Erklärung zu "fixed time steps":
Wenn die gemessene vergangene Zeit zu gross ist, wird man die clampen, so das es z.B. max 5 Updates gibt und das Programm in dem Moment einfach langsamer läuft.
Interessanterweise ist das in der
timeSinceLastUpdate
Versionvieletwas einfacher umzusetzen als in meinem Beispiel. Vielleicht ist ja das der Grund dass das SFML Beispiel so aussieht wie es aussieht. Nur wäre dann gut den Punkt in dem Beispiel auch zu erwähnen oder gleich mit in den Code aufzunehmen.
-
Naja, man kann oben einfach noch eine Zeile einfügen, das reichte ja. Direkt nach der Zeitmessung: logicTime = max(realTime - 200, logicTime);
-
@TGGC
Ja, hast recht.Dann verstehe ich weiterhin nicht warum man den Code mit
timeSinceLastUpdate
schreiben würde, ich finde meine Variante nämlich einfacher zu verstehen. Aber gut, mega-kompliziert sind jetzt beide nicht.
-
@daniel sagte in Erklärung zu "fixed time steps":
player. x += vel.x * distancePerFrame
Wo du schon dabei bist: Weisst du auch, weshalb du überhaupt feste Zeitschritte eventuell einsetzen möchtest? Es kann mehrere Gründe geben, die obige Zeile zeigt einen davon auf: Bei variablen Schritten kann diese nämlich dazu führen, dass sich der
player
z.B. auf zwei unterschiedlich schnellen Rechnern selbst bei frame-exakten, identischen Eingaben nachher an verschiedenen Positionen befindet.
-
@Finnegan sagte in Erklärung zu "fixed time steps":
dass sich der player z.B. auf zwei unterschiedlich schnellen Rechnern selbst bei frame-exakten, identischen Eingaben nachher an verschiedenen Positionen befindet
Frame-exakte, identische Eingaben sind irgendwie schwer wenn die Rechner nicht gleich schnell sind, nen? (Weil ja dann die Frames zu unterschiedlichen Zeiten anfangen und es auch unterschiedlich viele Frames sind.) Man kann maximal die Inputs zur exakt identischen Zeit (relativ zum Spielstart) machen. Und: selbst bei nem fixed time step Game-Loop kann das dann passieren. Da man auf unterschiedlich schnellen Rechnern die Inputs nicht immer zur selben Zeit abgreift.
Das ist also weniger der Grund. Es wäre schön wenn man es erreichen könnte, aber man schafft es nicht.
Gibt aber andere Vorteile die man sehrwohl erreicht. z.B. bekommt man mit variablen Steps so Sachen wie unterschiedliche Sprung-Höhe oder -Weite, andere Kurvenradien, z.T. kann man bei krassen Spikes durch Wände laufen etc. Das alles lässt sich mit fixed time steps fixen.
-
Zur "gleichen Zeit" -> im selben lock-step
-
@hustbaer sagte in Erklärung zu "fixed time steps":
Frame-exakte, identische Eingaben sind irgendwie schwer wenn die Rechner nicht gleich schnell sind, nen?
Ich gebe zu, der Audruck "Frame" war hier etwas unglücklich gewählt. Nennen wir es mal besser "zeitgleich" bezüglich der Granularität der
clock
. Oder lassen wier die Eingaben mal gänzlich weg und lassen die Simulation einfach von einem vorgegebenen Ausgangszustand aus laufen.Worauf ich eigentlich hinaus wollte, ist, dass man vielleicht gerne hätte, dass äquivalente Berechnungen - wie hier die der Spielerposition - zu identischen Ergebnissen führen, egal wie flott der Rechner ist. Ist
distancePerFrame
jedoch besonders klein oder groß, bekommt man es mit sehr unterschiedlichen Fließkomma-Rundungsfehlern zu tun, die durch die Addition auch noch akkumuliert werden. Die selbe Simulation driftet auf unterschiedlichen Rechnern also immer weiter auseinander...Gibt aber andere Vorteile die man sehrwohl erreicht. z.B. bekommt man mit variablen Steps so Sachen wie unterschiedliche Sprung-Höhe oder -Weite, andere Kurvenradien, z.T. kann man bei krassen Spikes durch Wände laufen etc. Das alles lässt sich mit fixed time steps fixen.
... und das ist im Prinzip auch dasselbe, was du hier beschreibst. Mathematisch sind diese Berechnungen äquivalent, die Unterschiede kommen hier eben durch Rundungsfehler und unterschiedlich große Steps zustande.
-
@Finnegan sagte in Erklärung zu "fixed time steps":
Mathematisch sind diese Berechnungen äquivalent, die Unterschiede kommen hier eben durch Rundungsfehler und unterschiedlich große Steps zustande.
Ich würde das nicht mathematisch äquivalent nennen. Das Problem sind nämlich weniger die Rundungsfehler sondern die Fehler die bei der numerischen Integration entstehen. Die sind nämlich normalerweise sehr viel grösser.
Wenn du z.B. deine Spielfigur springen lässt, dann machst du z.B. etwas wie
this->position += this->speed * dT; this->speed.y -= g * dT;
Und dabei kommt halt was anderes raus wenn du es 1x mit dT=X ausführst bzw. 10x mit dT=X/10 -- selbst wenn du mit unendlicher Genauigkeit rechnest.
-
@hustbaer sagte in Erklärung zu "fixed time steps":
Ich würde das nicht mathematisch äquivalent nennen.
Ja das stimmt, die hatte ich dabei nicht auf dem Schirm. Habe mich zu sehr auf die eine zitierte Zeile fixiert und nicht an dieses größere Problem gedacht. So ist es das natürlich nicht äquivalent. Bin hier auch von einem (urealistischen) konstanten
vel
ausgegangen. Rundungsfehler sind durchaus ein anderes Problem, aber wie du schon sagst, ein eher kleineres.this->position += this->speed * dT; this->speed.y -= g * dT;
Jo, schon klar. Die Geschwindigkeit ist hierbei stückweise konstant und ignoriert die Beschleuinigung zwischen den Steps ("springt" also direkt auf eine neue Geschwindigkeit). Man integriert also eigentlich eine annähernde Treppenfunktion.
-
@Finnegan Genau. Und da sich in Spielen Geschwindigkeiten oft quasi-stetig und nicht sprunghaft ändern, hat man das relativ oft