重力を実装するにはどうすればよいですか?


回答:


52

他の人がコメントで指摘したように、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(これは上で実際に使用したものです)は、アクセラレーションの再利用の有無にかかわらず、同様に機能します。たぶんそれは彼らの力が確率的ブラウン運動成分を含んでいるという事実と関係があるのでしょう。


Velocity Verletは素晴らしいですが、速度に依存する可能性がないため、摩擦を実装できません。Runge-Kutta 2が私の目的には最適だと思う;)
ピチラーニレオナルド

1
@PizziraniLeonardo:速度に依存する力に対しても、速度Verlet(の変形)を使用できます。上記の私の編集を参照してください。
イルマリカロネン

1
文学では、Velocity Verletのこの解釈に別の名前を付けていません。また、この論文の中で述べたように、それは、予測子・修正戦略に依存しているfire.nist.gov/bfrlpubs/build99/PDF/b99014.pdf
テオドロン

3
@ Unit978:それはゲーム、具体的には実装する物理モデルに依存します。force(time, position, velocity)上記の私の答えでは単に「のオブジェクトに作用する力の省略形ですpositionで移動velocitytime」。通常、力は、オブジェクトが自由落下状態にあるか、固体表面に座っているか、近くの他のオブジェクトが力を及ぼしているか、表面上を移動する速度(摩擦)および/または液体を通過するかなどに依存しますまたはガス(ドラッグ)など
Ilmari Karonen 14年

1
これはすばらしい答えですが、固定の時間ステップについて語らずには不完全です(gafferongames.com/game-physics/fix-your-timestep)。別の回答を追加しますが、ほとんどの人は受け入れられた回答で停止します。特に、ここにあるように、このような大きなマージンで最も多くの票がある場合はそうです。コミュニティは、このコミュニティを強化することでより良く機能すると思います。
ジブスマート

13

ゲームのすべての更新ループ、これを行います:

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;

6
どういう意味ですか?自分の重力値を選択するだけで、位置だけでなく速度も変化するため、自然に見えます。
ペカン

1
重力をオフにするのは嫌いです。重力は一定でなければならないと思います。変更する必要があるもの(imho)は、ジャンプする能力です。
-ultifinitus

2
それが役立つ場合は、実際に「重力」ではなく「落下」と考えてください。この機能は全体として、オブジェクトが重力により落下しているかどうかを制御します。重力自体はそれと同じように存在します[ここに重力値を挿入]。その意味で、重力一定であり、オブジェクトが空中にない限り、何にも使用しないでください。
ジェイソンピネオ

2
このコードはフレームレートに依存しますが、それは素晴らしいことではありませんが、常に更新されている場合は笑っています。
-tenpn

1
-1、ごめんなさい。速度と重力、または位置と速度を追加することは、意味がありません。私はあなたがしているショートカットを理解していますが、それは間違っています。私が見つけることができる最大の手がかりで、それをしている学生、研修生、または同僚を襲うでしょう。ユニットの一貫性は重要です。
サムホセバー

8

適切なフレームレートに依存しない*ニュートン物理学の統合:

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を参照してください。


4
オイラー統合を使用しないでください。Glenn Fiedlerによるこの記事を参照してください。この記事では、私ができる以上に問題と解決策について説明しています。:)
マーティン・ソイカ

1
オイラーが時間の経過とともにどのように不正確であるかは理解していますが、実際には重要ではないシナリオがあると思います。ルールがすべての人に一貫しており、それが「適切」である限り、それは問題ありません。そして、物理学について学んでいるだけなら、覚えて実装するのは非常に簡単です。
-tenpn

...良いリンク。;)
tenpn

4
オイラー統合に関する問題のほとんどは、position += velocity * timestep上記を置き換えるだけで修正できますposition += (velocity - acceleration * timestep / 2) * timestep(ここvelocity - acceleration * timestep / 2で、単に古い速度と新しい速度の平均です)。特に、この積分器は、加速度が一定である場合、通常は重力の場合に正確な結果を提供します。さまざまな加速度の下でより良い精度を得るには、速度の更新に同様の修正を追加して、速度Verlet統合を取得できます。
イルマリカロネン

あなたの議論は理にかなっており、不正確さはしばしば大したことではありません。ただし、「適切なフレームレートに依存しない」統合であると主張するべきではありません。
サムホセバル

3

少し大きなスケールで重力を実装する場合は、ループごとにこの種の計算を使用できます。

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方程式によって決定される非常に目に見える程度であり、光の有限速度、したがって重力も念頭に置く必要があります。


1

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;

乾杯'


加速度は、速度に依存し、私は、あなただ間違っていると思います
スーパー

-4

Pecantのanswserはフレーム時間を無視していたため、物理動作は時々異なります。

非常に単純なゲームを作成する場合は、独自の小さな物理エンジンを作成します-移動するすべてのオブジェクトに質量とすべての種類の物理パラメーターを割り当て、衝突検出を行い、フレームごとに位置と速度を更新します。この進歩を加速するためには、衝突メッシュを単純化し、衝突検出の呼び出しを減らすなどする必要があります。ほとんどの場合、それは苦痛です。

physix、ODE、およびbulletなどの物理エンジンを使用することをお勧めします。それらのいずれもあなたにとって十分に安定して効率的です。

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

http://bulletphysics.org/wordpress/


4
-1質問に答えない、役に立たない応答。
-doppelgreener

4
時間に合わせて調整する場合は、最後のupdate()からの経過時間で速度をスケーリングするだけです。
ペカン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.