Wie kann ich die Schwerkraft implementieren?

Antworten:

52

Wie andere in den Kommentaren angemerkt haben, weist die in der Antwort von tenpn beschriebene grundlegende Euler-Integrationsmethode einige Probleme auf:

  • Selbst bei einfachen Bewegungen, wie beim ballistischen Springen unter konstanter Schwerkraft, kommt es zu einem systematischen Fehler.

  • Der Fehler hängt vom Zeitschritt ab. Das bedeutet, dass durch Ändern des Zeitschritts die Objektbahnen systematisch geändert werden, was den Spielern möglicherweise auffällt, wenn das Spiel einen variablen Zeitschritt verwendet. Selbst bei Spielen mit einem festgelegten Zeitschritt für die Physik kann das Ändern des Zeitschritts während der Entwicklung die Spielphysik merklich beeinflussen, z.

  • Es spart keine Energie, auch wenn die zugrunde liegende Physik sollte. Insbesondere Objekte, die ständig schwingen sollten (z. B. Pendel, Federn, umlaufende Planeten usw.), können ständig Energie ansammeln, bis das gesamte System auseinanderbricht.

Glücklicherweise ist es nicht schwer, die Euler-Integration durch etwas zu ersetzen, das fast so einfach ist und dennoch keines dieser Probleme aufweist - insbesondere einen symplektischen Integrator zweiter Ordnung wie die Sprung-Integration oder die eng verwandte Velocity-Verlet-Methode . Insbesondere wenn die grundlegende Euler-Integration die Geschwindigkeit und Position wie folgt aktualisiert:

Beschleunigung = Kraft (Zeit, Position) / Masse;
Zeit + = Zeitschritt;
Position + = Zeitschritt * Geschwindigkeit;
Geschwindigkeit + = Zeitschritt * Beschleunigung;

Die Velocity-Verlet-Methode sieht folgendermaßen aus:

Beschleunigung = Kraft (Zeit, Position) / Masse;
Zeit + = Zeitschritt;
Position + = Zeitschritt * ( Geschwindigkeit + Zeitschritt * Beschleunigung / 2) ;
newAcceleration = Kraft (Zeit, Position) / Masse; 
Geschwindigkeit + = Zeitschritt * ( Beschleunigung + neue Beschleunigung ) / 2 ;

Wenn Sie mehrere interagierende Objekte haben, sollten Sie alle ihre Positionen aktualisieren, bevor Sie die Kräfte neu berechnen und die Geschwindigkeiten aktualisieren. Die neue (n) Beschleunigung (en) können dann gespeichert und verwendet werden, um die Position (en) beim nächsten Zeitschritt zu aktualisieren, wodurch die Anzahl der Aufrufe force()auf einen (pro Objekt) pro Zeitschritt reduziert wird, genau wie bei der Euler-Methode.

Auch wenn die Beschleunigung normalerweise konstant ist (wie die Schwerkraft beim ballistischen Springen), können wir das Obige vereinfachen, um nur:

Zeit + = Zeitschritt;
Position + = Zeitschritt * ( Geschwindigkeit + Zeitschritt * Beschleunigung / 2) ;
Geschwindigkeit + = Zeitschritt * Beschleunigung;

wobei der fettgedruckte Zusatzbegriff die einzige Änderung gegenüber der grundlegenden Euler-Integration darstellt.

Im Vergleich zur Euler-Integration haben die Velocity-Verlet- und die Leapfrog-Methode mehrere nette Eigenschaften:

  • Für eine konstante Beschleunigung liefern sie genaue Ergebnisse (jedenfalls bis zu Gleitkomma-Abrundungsfehlern), was bedeutet, dass ballistische Sprungtrajektorien auch dann gleich bleiben, wenn der Zeitschritt geändert wird.

  • Sie sind Integratoren zweiter Ordnung, was bedeutet, dass der durchschnittliche Integrationsfehler auch bei variierender Beschleunigung nur proportional zum Quadrat des Zeitschritts ist. Dies kann größere Zeitschritte ermöglichen, ohne die Genauigkeit zu beeinträchtigen.

  • Sie sind sympathisch , was bedeutet, dass sie Energie sparen, wenn die zugrunde liegende Physik dies tut (zumindest solange der Zeitschritt konstant ist). Dies bedeutet insbesondere, dass Sie keine Dinge wie Planeten, die spontan aus ihren Umlaufbahnen fliegen, oder Objekte, die mit Federn aneinander haften, die allmählich mehr und mehr wackeln, bis das Ganze in die Luft geht.

Die Velocity-Verlet / leapfrog-Methode ist jedoch fast so einfach und schnell wie die grundlegende Euler-Integration und sicherlich viel einfacher als Alternativen wie die Runge-Kutta-Integration vierter Ordnung (die zwar im Allgemeinen ein sehr netter Integrator ist, aber keine symplektische Eigenschaft aufweist und vier Auswertungen erfordert) der force()Funktion pro Zeitschritt). Daher würde ich sie jedem empfehlen, der irgendeine Art von Spielphysik-Code schreibt, selbst wenn es so einfach ist, von einer Plattform zur anderen zu springen.


Edit: Während die formale Ableitung der Geschwindigkeit Verlet - Methode nur dann gültig ist , wenn die Kräfte unabhängig von der Geschwindigkeit sind, in der Praxis können Sie es ganz gut , auch mit geschwindigkeitsabhängigen Kräften wie Strömungswiderstand . Für beste Ergebnisse sollten Sie den anfänglichen Beschleunigungswert verwenden, um die neue Geschwindigkeit für den zweiten Aufruf zu schätzen force():

Beschleunigung = Kraft (Zeit, Position, Geschwindigkeit) / Masse;
Zeit + = Zeitschritt;
Position + = Zeitschritt * ( Geschwindigkeit + Zeitschritt * Beschleunigung / 2) ;
Geschwindigkeit + = Zeitschritt * Beschleunigung;
newAcceleration = Kraft (Zeit, Position, Geschwindigkeit) / Masse; 
Geschwindigkeit + = Zeitschritt * (neue Beschleunigung - Beschleunigung) / 2 ;

Ich bin nicht sicher, ob diese bestimmte Variante der Velocity-Verlet-Methode einen bestimmten Namen hat, aber ich habe sie getestet und sie scheint sehr gut zu funktionieren. Es ist nicht ganz so genau wie Runge-Kutta vierter Ordnung (wie man es von einer Methode zweiter Ordnung erwarten würde), aber es ist viel besser als Euler oder naives Geschwindigkeitsverlet ohne die mittlere Geschwindigkeitsschätzung, und es behält immer noch die symplektische Eigenschaft des Normalen bei Geschwindigkeitsverlet für konservative, geschwindigkeitsunabhängige Kräfte.

Edit 2: Ein sehr ähnlicher Algorithmus wird z. B. von Groot & Warren ( J. Chem. Phys. 1997) beschrieben , obwohl es den Anschein hat, als ob sie beim Lesen zwischen den Linien etwas Genauigkeit für zusätzliche Geschwindigkeit newAccelerationeinbüßten, indem sie den mit der geschätzten Geschwindigkeit berechneten Wert speicherten und es als das accelerationfür den nächsten Zeitschritt wiederverwenden. Sie führen auch einen Parameter 0 ≤ λ ≤ 1 ein, der mit accelerationder anfänglichen Geschwindigkeitsschätzung multipliziert wird; aus irgendeinem Grund empfehlen sie λ = 0,5, obwohl alle meine Tests darauf hindeuten, dass λ= 1 (was effektiv das ist, was ich oben benutze) funktioniert genauso gut oder besser, mit oder ohne die Beschleunigungswiederverwendung. Vielleicht hat das damit zu tun, dass ihre Streitkräfte eine stochastische Brownsche Bewegungskomponente enthalten.

Ilmari Karonen
quelle
Velocity Verlet es ist schön, aber es kann kein von der Geschwindigkeit abhängiges Potential haben, so dass die Reibung nicht implementiert werden kann. Ich denke, dass Runge-Kutta 2 das Beste für meinen Zweck ist;)
Pizzirani Leonardo
1
@PizziraniLeonardo: Sie können (eine Variante von) Velocity Verlet auch für geschwindigkeitsabhängige Kräfte verwenden; siehe mein edit oben.
Ilmari Karonen
1
Die Literatur gibt dieser Interpretation von Velocity Verlet keinen anderen Namen. Es basiert auf einer Predictor-Corrector-Strategie, wie auch in diesem Artikel fire.nist.gov/bfrlpubs/build99/PDF/b99014.pdf angegeben .
Teodron
3
@ Unit978: Das hängt vom Spiel und speziell vom implementierten Physikmodell ab. Die force(time, position, velocity)in meiner Antwort oben nur für „die Kraft auf ein Objekt wirkt auf ist eine Abkürzung positionzu bewegen velocitybei time“. Typischerweise hängt die Kraft davon ab, ob sich das Objekt im freien Fall befindet oder auf einer festen Oberfläche sitzt, ob andere Objekte in der Nähe eine Kraft auf es ausüben, wie schnell es sich über eine Oberfläche (Reibung) und / oder durch eine Flüssigkeit bewegt oder Gas (Drag) usw.
Ilmari Karonen
1
Dies ist eine großartige Antwort, aber unvollständig, ohne über einen festen Zeitschritt zu sprechen ( gafferongames.com/game-physics/fix-your-timestep ). Ich würde eine separate Antwort hinzufügen, aber die meisten Leute hören bei der akzeptierten Antwort auf, besonders wenn sie die meisten Stimmen mit einem so großen Abstand hat, wie es hier der Fall ist. Ich denke, die Community ist besser bedient, wenn sie diese erweitert.
Jibb Smart
13

Führen Sie in jeder Update-Schleife Ihres Spiels Folgendes aus:

if (collidingBelow())
    gravity = 0;
else gravity = [insert gravity value here];

velocity.y += gravity;

In einem Plattformer wird beispielsweise die Schwerkraft deaktiviert, sobald Sie springen (collidingBelow zeigt an, ob sich direkt unter Ihnen Boden befindet oder nicht). Wenn Sie den Boden berühren, wird sie deaktiviert.

Um außerdem Sprünge zu implementieren, gehen Sie wie folgt vor:

if (pressingJumpButton() && collidingBelow())
    velocity.y = [insert jump speed here]; // the jump speed should be negative

Und ziemlich offensichtlich müssen Sie in der Aktualisierungsschleife auch Ihre Position aktualisieren:

position += velocity;
Pecant
quelle
6
Was meinst du? Wählen Sie einfach Ihren eigenen Schwerkraftwert und da sich dadurch Ihre Geschwindigkeit und nicht nur Ihre Position ändert, sieht es natürlich aus.
Pecant
1
Ich mag es nicht, die Schwerkraft jemals auszuschalten. Ich denke, dass die Schwerkraft konstant sein sollte. Das, was sich ändern sollte (imho), ist deine Fähigkeit zu springen.
Ultifinitus
2
Wenn es hilft, stellen Sie es sich eher als "Fallen" als als "Schwerkraft" vor. Die Funktion als Ganzes steuert, ob das Objekt aufgrund der Schwerkraft fällt oder nicht . Die Schwerkraft selbst existiert genau so [hier den Schwerkraftwert einfügen]. In diesem Sinne, die Schwerkraft ist konstant, die Sie gerade verwenden Sie es nicht für irgendetwas , wenn der Gegenstand der Luft ist.
Jason Pineo
2
Dieser Code hängt von der Bildrate ab, was nicht besonders gut ist. Wenn Sie jedoch ein ständiges Update haben, lachen Sie.
Am
1
-1, Entschuldigung. Das Hinzufügen von Geschwindigkeit und Schwerkraft oder Position und Geschwindigkeit macht einfach keinen Sinn. Ich verstehe die Abkürzung, die Sie machen, aber es ist falsch. Ich würde jeden Studenten, Auszubildenden oder Kollegen treffen, der das mit dem größten Cluebat macht, den ich finden kann. Einheitenkonsistenz ist wichtig.
Sam Hocevar
8

Eine einwandfreie, von der Bildrate unabhängige * Newtonsche Physik-Integration:

Vector forces = 0.0f;

// gravity
forces += down * m_gravityConstant; // 9.8m/s/s on earth

// left/right movement
forces += right * m_movementConstant * controlInput; // where input is scaled -1..1

// add other forces in for taste - usual suspects include air resistence
// proportional to the square of velocity, against the direction of movement. 
// this has the effect of capping max speed.

Vector acceleration = forces / m_massConstant; 
m_velocity += acceleration * timeStep;
m_position += velocity * timeStep;

Passen Sie die Schwerkraftkonstante, die Bewegungskonstante und die Massenkonstante an, bis sie sich richtig anfühlt. Es ist eine intuitive Sache und kann eine Weile dauern, bis man sich großartig fühlt.

Es ist einfach, den Force-Vektor zu erweitern, um neues Gameplay hinzuzufügen. Fügen Sie beispielsweise eine Force hinzu, die von einer Explosion in der Nähe oder von Schwarzen Löchern entfernt ist.

* edit: Diese Ergebnisse werden mit der Zeit falsch sein, können aber für Ihre Wiedergabetreue oder Eignung "gut genug" sein. Weitere Informationen finden Sie unter folgendem Link: http://lol.zoy.org/blog/2011/12/14/understanding-motion-in-games .

Tenpn
quelle
4
Verwenden Sie keine Euler-Integration. Lesen Sie diesen Artikel von Glenn Fiedler, der die Probleme und Lösungen besser erklärt, als ich es könnte. :)
Martin Sojka
1
Ich verstehe, wie ungenau Euler im Laufe der Zeit ist, aber ich denke, es gibt Szenarien, in denen es nicht wirklich wichtig ist. Solange die Regeln für alle gleich sind und es sich richtig anfühlt, ist es in Ordnung. Und wenn Sie nur etwas über Phyiscs lernen, ist es sehr einfach, sich daran zu erinnern und es zu implementieren.
Tenpn
... aber guter Link. ;)
tenpn
4
Sie können die meisten Probleme mit der Euler-Integration einfach beheben, indem Sie die position += velocity * timestepobigen Angaben durch ersetzen position += (velocity - acceleration * timestep / 2) * timestep(wobei velocity - acceleration * timestep / 2einfach der Durchschnitt der alten und neuen Geschwindigkeiten angegeben wird). Insbesondere liefert dieser Integrator genaue Ergebnisse, wenn die Beschleunigung konstant ist, wie es für die Schwerkraft typisch ist. Für eine bessere Genauigkeit bei variierender Beschleunigung können Sie dem Geschwindigkeitsupdate eine ähnliche Korrektur hinzufügen, um die Geschwindigkeitsverlet-Integration zu erhalten .
Ilmari Karonen
Ihre Argumente machen Sinn und die Ungenauigkeit ist oft keine große Sache. Sie sollten jedoch nicht behaupten, es handele sich um eine von der Bildrate unabhängige Integration, da dies nicht der Fall ist (unabhängig von der Bildrate).
Sam Hocevar
3

Wenn Sie die Schwerkraft in einem etwas größeren Maßstab implementieren möchten, können Sie diese Art der Berechnung für jede Schleife verwenden:

for each object in the scene
  for each other_object in the scene not equal to object
    if object.mass * other_object.mass / object.distanceSquaredBetweenCenterOfMasses(other_object) < epsilon
      abort the calculation for this pair
    if object.mass is much, much bigger than other_object.mass
      abort the calculation for this pair
    force = gravitational_constant
            * object.mass * other_object.mass
            / object.distanceSquaredBetweenCenterOfMasses(other_object)
    object.addForceAtCenterOfMass(force * object.normalizedDirectionalVectorTo(other_object))
  end for loop
end for loop

Bei noch größeren (galaktischen) Maßstäben reicht die Schwerkraft jedoch nicht aus, um eine "echte" Bewegung zu erzeugen. Die Wechselwirkung von Sternensystemen wird in einem signifikanten und sehr sichtbaren Ausmaß durch Navier-Stokes-Gleichungen für die Fluiddynamik bestimmt, und Sie müssen die endliche Lichtgeschwindigkeit - und damit auch die Schwerkraft - berücksichtigen.

Martin Sojka
quelle
1

Der Code von Ilmari Karonen ist fast korrekt, aber es gibt einen kleinen Fehler. Sie berechnen die Beschleunigung tatsächlich 2 mal pro Tick, dies folgt nicht den Lehrbuchgleichungen.

acceleration = force(time, position) / mass; // Here
time += timestep;
position += timestep * (velocity + timestep * acceleration / 2);
newAcceleration = force(time, position) / mass;
velocity += timestep * (acceleration + newAcceleration) / 2;

Der folgende Mod ist korrekt:

time += timestep;
position += timestep * (velocity + timestep * acceleration / 2);
oldAcceletation = acceleration; // Store it
acceleration = force(time, position) / mass;
velocity += timestep * (acceleration + oldAcceleration) / 2;

Prost'

Satanfu
quelle
Ich denke, du liegst falsch, da die Beschleunigung von der Geschwindigkeit abhängt
Super
-4

Der Antwortende von Pecant ignorierte die Frame-Zeit und das macht Ihr physikalisches Verhalten von Zeit zu Zeit anders.

Wenn Sie ein sehr einfaches Spiel erstellen möchten, können Sie Ihre eigene kleine Physik-Engine erstellen. Weisen Sie jedem sich bewegenden Objekt Masse und alle Arten von Physik-Parametern zu, führen Sie eine Kollisionserkennung durch und aktualisieren Sie dann deren Position und Geschwindigkeit in jedem Frame. Um diesen Fortschritt zu beschleunigen, müssen Sie das Kollisionsnetz vereinfachen, Kollisionserkennungsaufrufe reduzieren usw. In den meisten Fällen ist dies ein Schmerz.

Es ist besser, eine Physik-Engine wie Physix, ODE und Bullet zu verwenden. Jeder von ihnen wird stabil und effizient genug für Sie sein.

http://www.nvidia.com/object/physx_new.html

http://bulletphysics.org/wordpress/

Raymond
quelle
4
-1 Nicht hilfreiche Antwort, die die Frage nicht beantwortet.
Doppelgreener
4
Nun, wenn Sie die Zeit anpassen möchten, können Sie die Geschwindigkeit einfach um die verstrichene Zeit seit der letzten Aktualisierung skalieren ().
Pecant