重力を実装するにはどうすればよいですか?特定の言語ではなく、単なる擬似コード...
重力を実装するにはどうすればよいですか?特定の言語ではなく、単なる擬似コード...
回答:
他の人がコメントで指摘したように、tenpnの答えで説明されている基本的なオイラー積分法には、いくつかの問題があります。
一定の重力の下での弾道ジャンプのような単純な動きであっても、体系的な誤差が生じます。
エラーはタイムステップに依存します。つまり、タイムステップを変更すると、ゲームが可変タイムステップを使用している場合にプレーヤーが気付く可能性のある体系的な方法でオブジェクトの軌跡が変更されます。物理タイムステップが固定されているゲームでも、開発中にタイムステップを変更すると、特定の力で起動されたオブジェクトが飛ぶ距離など、ゲームの物理に著しく影響を与え、以前に設計されたレベルを破壊する可能性があります。
基礎となる物理学がそうであっても、それはエネルギーを節約しません。具体的には、そのオブジェクトべき離れて、システム全体が吹くまで、着実に(例えば振り子、スプリング、旋回惑星、等)を発振するおそれが着実に蓄積エネルギーを。
幸いなことに、オイラー積分をほぼ単純なものに置き換えることは難しくありませんが、これらの問題はまったくありません。具体的には、リープフロッグ積分や密接に関連する速度Verlet法などの2次シンプレクティック積分です。特に、基本的なオイラー統合が速度と位置を更新する場合:
加速度=力(時間、位置)/質量; time + = timestep; 位置+ =タイムステップ*速度; 速度+ =タイムステップ*加速度。
速度Verletメソッドは次のように処理します。
加速度=力(時間、位置)/質量; time + = timestep; 位置+ =タイムステップ* (速度+タイムステップ*加速度/ 2) ; newAcceleration = force(時間、位置)/質量; 速度+ =タイムステップ* (加速+ newAcceleration)/ 2 ;
複数の相互作用するオブジェクトがある場合、力を再計算して速度を更新する前に、すべての位置を更新する必要があります。次に、新しい加速度を保存して、次のタイムステップで位置を更新するために使用force()
し、オイラーメソッドと同様に、タイムステップごとに(オブジェクトごとに)呼び出し回数を減らします。
また、加速度が通常一定である場合(弾道ジャンプ中の重力など)、上記を単純化して次のようにすることができます。
time + = timestep; 位置+ =タイムステップ* (速度+タイムステップ*加速度/ 2) ; 速度+ =タイムステップ*加速度。
ここで、太字の追加の用語は、基本的なオイラー統合と比較した唯一の変更点です。
オイラー積分と比較して、速度Verletおよびleapfrogメソッドにはいくつかの素晴らしい特性があります。
一定の加速では、正確な結果が得られます(とにかく、浮動小数点の丸め誤差まで)。つまり、タイムステップが変更されても、弾道ジャンプの軌跡は同じままです。
これらは二次積分器であり、加速度が変化しても、平均積分誤差はタイムステップの二乗にのみ比例することを意味します。これにより、精度を損なうことなく、より大きなタイムステップを実現できます。
それらはシンプレクティックです。つまり、基礎となる物理学が(少なくともタイムステップが一定である限り)エネルギーを節約します。特に、これは、惑星が自発的に軌道から飛び出したり、ばねで互いに結び付けられた物体が全体が爆発するまで次第にぐらついたりするようなものを取得しないことを意味します。
しかし、速度Verlet / leapfrog法は、基本的なオイラー積分とほぼ同じくらい簡単で高速であり、4次のRunge-Kutta積分(一般的に非常に素晴らしい積分器であるが、シンプレクティック特性がなく、4つの評価が必要です)force()
タイムステップごとの関数の)。したがって、あるプラットフォームから別のプラットフォームにジャンプするのと同じくらい簡単であっても、あらゆる種類のゲーム物理コードを作成する人には強くお勧めします。
編集:速度Verletメソッドの正式な導出は、力が速度に依存しない場合にのみ有効ですが、実際には、流体抵抗などの速度依存の力でも問題なく使用できます。最良の結果を得るには、次のようにforce()
、2回目の呼び出しの新しい速度を推定するために初期加速度値を使用する必要があります。
加速度=力(時間、位置、速度)/質量; time + = timestep; 位置+ =タイムステップ* (速度+タイムステップ*加速度/ 2) ; 速度+ =タイムステップ*加速度。 newAcceleration = force(時間、位置、速度)/質量; 速度+ =タイムステップ*(newAcceleration-加速)/ 2 ;
この速度Verletメソッドの特定のバリアントに特定の名前があるかどうかはわかりませんが、テストを行ったところ、非常にうまく機能しているようです。2次のルンゲクッタほど正確ではありませんが(2次の方法から予想されるように)、中間の速度推定なしでオイラーまたは単純な速度Verletよりもはるかに優れており、通常のシンプレクティックプロパティを保持しています保守的で速度に依存しない力の速度Verlet。
編集2:非常によく似たアルゴリズムは、例えばGroot&Warren(J. Chem。Phys。1997)によって説明されていますが、行間を読むとnewAcceleration
、推定速度を使用して計算された値を保存することで余分な速度の精度を犠牲にしているようですacceleration
次のタイムステップとして再利用します。彼らはまた、Aパラメーター0≤導入λが乗算され≤1をacceleration
初期速度推定値に、何らかの理由で、彼らはお勧めλをすべてにもかかわらず、= 0.5 私のテストはその示唆λを= 1(これは上で実際に使用したものです)は、アクセラレーションの再利用の有無にかかわらず、同様に機能します。たぶんそれは彼らの力が確率的ブラウン運動成分を含んでいるという事実と関係があるのでしょう。
force(time, position, velocity)
上記の私の答えでは単に「のオブジェクトに作用する力の省略形ですposition
で移動velocity
でtime
」。通常、力は、オブジェクトが自由落下状態にあるか、固体表面に座っているか、近くの他のオブジェクトが力を及ぼしているか、表面上を移動する速度(摩擦)および/または液体を通過するかなどに依存しますまたはガス(ドラッグ)など
ゲームのすべての更新ループ、これを行います:
if (collidingBelow())
gravity = 0;
else gravity = [insert gravity value here];
velocity.y += gravity;
たとえば、プラットフォーマーでは、ジャンプすると重力が有効になり(collidingBelowは真下に地面があるかどうかを示します)、地面に着くと無効になります。
これに加えて、ジャンプを実装するには、次を実行します。
if (pressingJumpButton() && collidingBelow())
velocity.y = [insert jump speed here]; // the jump speed should be negative
そして、明らかに、更新ループでは、位置も更新する必要があります。
position += velocity;
適切なフレームレートに依存しない*ニュートン物理学の統合:
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;
それが正しいと感じるまで、重力定数、移動定数、質量定数を微調整します。それは直感的なもので、気分が良くなるまで時間がかかります。
フォースベクトルを拡張して新しいゲームプレイを追加するのは簡単です。たとえば、近くの爆発から離れる、またはブラックホールに向かってフォースを追加します。
*編集:これらの結果は時間の経過とともに間違ってしまいますが、忠実度や適性にとっては「十分」な場合があります。詳細については、このリンクhttp://lol.zoy.org/blog/2011/12/14/understanding-motion-in-gamesを参照してください。
position += velocity * timestep
上記を置き換えるだけで修正できますposition += (velocity - acceleration * timestep / 2) * timestep
(ここvelocity - acceleration * timestep / 2
で、単に古い速度と新しい速度の平均です)。特に、この積分器は、加速度が一定である場合、通常は重力の場合に正確な結果を提供します。さまざまな加速度の下でより良い精度を得るには、速度の更新に同様の修正を追加して、速度Verlet統合を取得できます。
少し大きなスケールで重力を実装する場合は、ループごとにこの種の計算を使用できます。
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
さらに大きい(銀河系)スケールの場合、重力だけでは「実際の」モーションを作成するのに十分ではありません。星系の相互作用は、流体力学のNavier-Stokes方程式によって決定される非常に目に見える程度であり、光の有限速度、したがって重力も念頭に置く必要があります。
Ilmari Karonenが提供するコードはほぼ正しいですが、わずかな不具合があります。実際にティックごとに2回加速度を計算しますが、これは教科書の式に従っていません。
acceleration = force(time, position) / mass; // Here
time += timestep;
position += timestep * (velocity + timestep * acceleration / 2);
newAcceleration = force(time, position) / mass;
velocity += timestep * (acceleration + newAcceleration) / 2;
次のmodが正しい:
time += timestep;
position += timestep * (velocity + timestep * acceleration / 2);
oldAcceletation = acceleration; // Store it
acceleration = force(time, position) / mass;
velocity += timestep * (acceleration + oldAcceleration) / 2;
乾杯'
Pecantのanswserはフレーム時間を無視していたため、物理動作は時々異なります。
非常に単純なゲームを作成する場合は、独自の小さな物理エンジンを作成します-移動するすべてのオブジェクトに質量とすべての種類の物理パラメーターを割り当て、衝突検出を行い、フレームごとに位置と速度を更新します。この進歩を加速するためには、衝突メッシュを単純化し、衝突検出の呼び出しを減らすなどする必要があります。ほとんどの場合、それは苦痛です。
physix、ODE、およびbulletなどの物理エンジンを使用することをお勧めします。それらのいずれもあなたにとって十分に安定して効率的です。