Wie implementiere ich eine genaue bildratenunabhängige Physik?

7

Ich arbeite jetzt schon eine Weile an einem Projekt und bin kürzlich auf ein Problem gestoßen:

Der übliche Ansatz für die rahmenratenunabhängige Physik besteht darin, entweder ein festes Aktualisierungsintervall (dh Einheit) zu verwenden oder einfach jede physikalische Änderung mit Delta zu multiplizieren. Es würde also ungefähr so ​​aussehen:

update(int delta)
{
   ...
   positionX += velocityX * delta;
   ...
} 

Und wenn die Beschleunigung hinzugefügt wird, wird die Geschwindigkeitsänderung ähnlich behandelt:

update(int delta)
{
   ...
   velocityX += accelerationX * delta;
   ...
}

Dies funktioniert in den meisten Anwendungsfällen einwandfrei. In der Tat so gut, dass ich seit Monaten nichts mehr bemerkt habe. Es gibt jedoch ein Problem mit dieser Logik, nämlich dass die Position bei völlig unterschiedlichen Bildraten leicht abweicht. Dieses Problem ist bei der Arbeit mit kleinen Änderungen der Bildrate vernachlässigbar, macht sich jedoch vollständig bemerkbar, wenn sich die Deltazeit um größere Beträge ändert. Angenommen, die FPS-Rate liegt zwischen 30 und 600. In einem normalen Spiel sind die FPS nur gesperrt, und selbst wenn die Positionen für einige Frames um etwa 5% verschoben sind, spielt dies keine Rolle, da man es nicht bemerkt. Aber ich multipliziere das Delta manuell mit einem Faktor, um einen bestimmten Effekt zu erzielen, und der Fehler ist etwas auffällig und bricht das Spiel ab .

Ein kleines Beispiel, um zu verdeutlichen, was ich meine:

Es gibt ein Spielobjekt mit der Startposition posX = 0 und einer x-Geschwindigkeit von 10 und einer horizontalen Beschleunigung von 0,1.

Nach zwei Frames würde dies für 50 FPS zu (mit einem durchschnittlichen Delta) führen.

velocityX += acceleration * delta; 
posX += velocityX * delta;

velocityX += 0.1 * 20; //velocityX = 12
posX += 12 * 20 //posX = 240

//next frame

velocityX += 0.1 * 20; //velocityX = 14
posX += 14 * 20 //posX = 520 (240 + 280)

Nach einem Frame mit 25 FPS, was dem gleichen Zeitintervall wie zwei Frames mit 50 FPS entspricht, erhalten wir Folgendes:

velocityX += 0.1 * 40; //velocityX = 14
posX += 14 * 40; //posX = 560

In zwei Situationen mit demselben Zeitintervall erhalten wir unterschiedliche Ergebnisse (520 für 50 FPS und 560 für 25 FPS).

Das Problem besteht nun offensichtlich darin, dass es einen zusätzlichen Frame gibt (in diesem Beispiel kann es sich um eine beliebige Anzahl von Frames handeln, die Ergebnisse unterscheiden sich jedoch für dasselbe Intervall), in dem die Beschleunigung angewendet wird und Sie daher mit weniger FPS weiter kommen.

Die Stabilisierung des Deltas ist keine Option, da ich für diesen Effekt unterschiedliche Deltas benötige. Daher muss die Physik dort völlig unabhängig sein. Hat jemand jemals eine ähnliche Situation erlebt und gibt es Lösungen dafür?

Flottothemoon
quelle
mögliches Duplikat von festem Zeitschritt vs variablem Zeitschritt
msell
@msell Dies ist definitiv kein Duplikat. Es geht um verschiedene Themen. Die Frage, die Sie verknüpft haben,
betrifft
1
Die verknüpften Fragen und Antworten erklären, dass die Verwendung eines festen Zeitschritts wirklich der Weg ist, um Ihre Probleme zu lösen.
Msell
@msell Hast du meine Frage tatsächlich gelesen? Nein ist es nicht. Ich möchte effektiv verschiedene Deltas verwenden, die Frage ist, wie man es richtig implementiert.
Flottothemoon

Antworten:

3

Die Stabilisierung des Deltas ist keine Option, da ich für diesen Effekt unterschiedliche Deltas benötige. Daher muss die Physik dort völlig unabhängig sein.

Die Stabilisierung der Deltazeit ist Ihre einzige Option. Wenn wir den Zeitschritt einer numerischen Integration ändern könnten und garantiert die gleichen Ergebnisse erzielen würden, könnten wir dies nutzen, um mit kaum Rechenaufwand wirklich genaue Berechnungen zu erhalten. Leider gibt es heute keine kostenlosen Mittagessen. Wenn Sie die Genauigkeit Ihrer Simulation ändern, kann sich das Ergebnis ändern.

Wenn Sie mit geringfügigen Änderungen der Ergebnisse einverstanden sind, können Sie sich für eine andere Integrationsmethode entscheiden. Die Verlet-Integration ist in Ihrem Beispiel viel besser als die von Euler: Die Position nach 40 ms wird mit Δt = 40 ms auf 480 und mit Δt = 20 ms auf 480 geschätzt , aber auch sie variieren, wenn Sie eine nicht konstante Beschleunigung einführen.

Ich bin mir nicht ganz sicher, welchen Effekt Sie durch Ändern des Zeitschritts erzielen möchten. Wenn Sie jedoch einen Zeitlupeneffekt erzielen möchten, der nur die Wiedergabe beeinflusst, müssen Sie die Simulation von der Framerate entkoppeln. Ein Beispiel: Ihre Simulation arbeitet normalerweise mit einem Δt von 10 ms. Alle zehn Millisekunden erhöhen Sie die Physik um einen Tick. Das Spiel läuft mit 25 fps; Sie zeichnen den Bildschirm jedes vierte Häkchen neu. Wenn Sie auf die Hälfte der normalen Geschwindigkeit verlangsamen möchten, erhöhen Sie die Physik stattdessen alle 20 Millisekunden um einen Tick, aber Sie halten Δt bei 10 ms (da dies die Zeit ist, die im Spiel vergangen ist). Um eine konstante Framerate beizubehalten, müssen Sie den Bildschirm jetzt alle zwei Ticks neu zeichnen.

Unabhängig von der Wiedergaberate sind die Ergebnisse gleich, diese Methode weist jedoch andere Nachteile auf. Sie können keinen neuen Frame zeichnen, bis mindestens Δt im Spiel verstrichen ist. Da dieser Wert festgelegt ist, können Sie nur langsam fahren, ohne dass die Framerate sinkt. Sie können diese Untergrenze verschieben, indem Sie einen kleineren Wert für Δt auswählen. Sie verschwenden jedoch Ressourcen für die Genauigkeit, die Sie bei normaler Geschwindigkeit nicht benötigen. Möglicherweise kann Ihr Spiel überhaupt nicht mit normaler Geschwindigkeit ausgeführt werden, wenn noch ein Tick berechnet wird, wenn der nächste beginnen soll.

Marcks Thomas
quelle
Die von Ihnen erwähnte "Verlet-Integration" klingt ziemlich gut, was ich erreichen möchte. Sie muss nicht 100% genau sein, 99% oder so ist in Ordnung. Könnten Sie bitte erklären, was Sie damit meinen und wie Sie es auf meine Formeln anwenden können?
Flottothemoon
2
@ 1337: Euler geht davon aus, dass sich das Objekt für die Dauer des vergangenen Δt mit der neuen Geschwindigkeit bewegt hat. Verlet nimmt an, dass sich die Geschwindigkeit während des letzten Δt allmählich auf den neuen Wert geändert hat und nimmt die Durchschnittsgeschwindigkeit als Mittelpunkt zwischen der neuen und der vorherigen Geschwindigkeit. Beispielimplementierung .
Marcks Thomas
Vielen Dank für den Link, ich habe diesen Artikel gelesen, aber ich verstehe immer noch nicht ganz, wie es funktioniert. Was ist für jeden der Werte zu ersetzen? verwirrt - Und was ist mit Ableiten von \ vec {a} (t + \ Delta t) aus dem Interaktionspotential mit \ vec {x} (t ​​+ \ Delta t) gemeint (Schritt 2). (Ich weiß, das klingt wirklich wie ein Schreiben meiner Code-Anfrage, aber ich weiß wirklich nicht, wo ich hier anfangen soll)
Flottothemoon
@ 1337: Das Interaktionspotential ist hier nicht relevant. Berechnen Sie die Beschleunigung a (t + Δt) wie gewohnt. Wenn die Beschleunigung von der aktuellen Position abhängt, verwenden Sie dafür x (t + Δt) anstelle von x (t) . Wenn das Objekt beispielsweise zwischen t und t + Δt kollidiert , aktualisieren Sie zuerst die Position mit der Geschwindigkeit vor der Kollision, dann aktualisieren Sie die Geschwindigkeit mit der Beschleunigung nach der Kollision.
Marcks Thomas
Danke, ich habe es jetzt richtig zum Laufen gebracht! Genial! Übrigens, hier ist ein Link zu einer anderen Erklärung, wie dies implementiert werden kann: lolengine.net/blog/2011/12/14/understanding-motion-in-games
flotothemoon
4

Sie können dieses Problem beheben, indem Sie die Anfangs- und Endgeschwindigkeit mitteln:

velocityOld = velocityX
velocityX += acceleration * delta; 
posX += (velocityX + velocityOld)/2 * delta;

In diesem speziellen Beispiel wird die Abhängigkeit vom Delta vollständig aufgehoben. Im Allgemeinen reduziert diese Lösung die Wirkung von Delta.

Peter
quelle
Ich habe nach dieser Antwort gesucht - während die anderen das Problem der exakten Integration ausführlich beschreiben, wird dies wahrscheinlich die beste Lösung für das OP und die meisten zukünftigen Besucher sein. - Weil dies einfach die richtige Formel ist, um den Standort eines Objekts mit konstanter Beschleunigung nach einer bestimmten Zeit zu berechnen. - Sie können hinzufügen, dass dies kein "Trick" ist, aber so lernen Sie es tatsächlich im Physikunterricht, wenn Sie die Position eines Objekts im freien Fall berechnen.
Falco
2

Willkommen in der wundervollen Welt der Integralrechnung.

Speed = distance / time
Acceleration = distance / (time * time)

Um die richtige Antwort zu erhalten, müssen Sie das Integral Ihrer Geschwindigkeit und dann das Integral Ihres Standorts berechnen. Die Mathematik kann ziemlich entmutigend sein , und in den meisten Fällen wird sie übertrieben sein und die Dinge nur komplizierter machen. Sie können dies verwenden, um die Positionen zu berechnen. Um Kollisionen zu erkennen, müssen Sie jedoch die Zeit berechnen, zu der ein Objekt eine bestimmte Position erreicht, und für zwei sich bewegende Objekte die Ankunftszeiten ihrer Pfade. Nun, es ist noch mehr Kalkül zu berechnen und für sehr wenig Gewinn (wenn überhaupt, da Float möglicherweise nicht genau genug dafür ist).

Aus diesem Grund verwenden Sie normalerweise einen festen Zeitschritt für die Physikberechnungen, auch wenn Sie eine variable Bildrate haben. Behalten Sie einfach die verstrichene Zeit im Auge und führen Sie dann Ihre Physikberechnung in einem festen Zeitschritt aus:

for (timeElapsed += delta; timeElapsed > timeStep; timeElapsed -= timeStep) {
  velocityX += acceleration * timeStep; 
  posX += velocityX * timeStep;
}

Dies kann in Ihre Aktualisierungsschleife eingefügt werden, um die Physik auf einem festen Zeitschritt laufen zu lassen, unabhängig davon, zu welchem ​​Zeitschritt Ihre Aktualisierungsschleife ausgeführt wird. Der Wert von timeElapsed müsste natürlich über die Zeit beibehalten werden. Wenn Sie feste Zeitschritte verwenden möchten, sollten Sie einige Dinge beachten. Shawn Hargraeves hat einen sehr interessanten Blog-Beitrag dazu, in dem XNA-Besonderheiten erwähnt werden, die Konzepte jedoch für alle festen Zeitschritte gelten.

Daniel Carlsson
quelle
0

Das Berechnen der Physik mit einem Delta ist im Allgemeinen keine gute Idee, nicht nur aus den von Ihnen angegebenen Gründen, sondern auch, weil Sie jedes Mal, wenn Sie die App ausführen, leicht unterschiedliche Ergebnisse erhalten, selbst bei ähnlichen Bildraten, die möglicherweise bereits spielerisch sind. Wenn die Bildrate zu niedrig ist, können sich schnell bewegende Objekte vollständig durcheinander schneiden. (Angenommen, Sie verwenden keine kontinuierliche Kollisionserkennung.) Die übliche Problemumgehung (oder zumindest meine) besteht darin, zwei verschiedene "Tick-Ereignisse" für Ihre Welt zu haben: Ein festes Tick und ein Frame-Tick. Das feste Häkchen ist, wo Sie alle Physik-Sachen machen und alles, was immer genau gleich funktionieren muss. Diese Funktion erhöht den Simulationsstatus jedes Mal, wenn Sie ihn aufrufen, um einen festen Betrag. Sie messen einfach, wie viel Zeit vergangen ist, und rufen die Funktion entsprechend oft auf. Natürlich sollten Sie sich daran erinnern, wie viel Zeit über mehrere Frames vergangen ist, damit Sie keine Zeit "verlieren". In der Frame Tick Funktion / Event machen Sie Dinge, die zeitlich perfekt sein müssen, aber wo es keine Rolle spielt, ob die Ergebnisse reproduzierbar sind, wie die Kamera. (Wenn Sie die Kamera in der Funktion / dem Ereignis mit festem Tick bewegen würden, würden Sie leichtes, aber merkliches Zittern bekommen, da ihre Position nicht immer perfekt auf die tatsächlich verstrichene Zeit abgestimmt wäre.)

Ein Problem dabei ist, dass wenn Sie so CPU-gebunden sind, dass Sie nicht genügend Iterationen mit festen Ticks ausführen können, das Spiel sich nur vollständig verzögert, da jeder Frame immer länger dauert und Sie immer mehr ausführen müssen Iterationen jedes Frames. Aber das kann man nicht wirklich umgehen, man muss nur eine bestimmte Anzahl von Simulationsschritten ausführen, um genaue Ergebnisse zu erhalten.

cooky451
quelle