Wie kann ich die Schwerkraft implementieren? Nicht für eine bestimmte Sprache, nur Pseudocode ...
Wie kann ich die Schwerkraft implementieren? Nicht für eine bestimmte Sprache, nur Pseudocode ...
Antworten:
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 newAcceleration
einbüßten, indem sie den mit der geschätzten Geschwindigkeit berechneten Wert speicherten und es als das acceleration
für den nächsten Zeitschritt wiederverwenden. Sie führen auch einen Parameter 0 ≤ λ ≤ 1 ein, der mit acceleration
der 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.
force(time, position, velocity)
in meiner Antwort oben nur für „die Kraft auf ein Objekt wirkt auf ist eine Abkürzung position
zu bewegen velocity
bei 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.
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;
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 .
position += velocity * timestep
obigen Angaben durch ersetzen position += (velocity - acceleration * timestep / 2) * timestep
(wobei velocity - acceleration * timestep / 2
einfach 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 .
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.
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'
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.