物理エンジンの更新の問題の勢いと順序


22

ここに画像の説明を入力してください

この質問は、衝突の検出と解決に関する以前の質問からの「フォローアップ」質問であり、こちらで見つけることができます


前の質問を読みたくない場合は、私の物理エンジンがどのように機能するかについて簡単に説明します。

すべての物理エンティティは、SSSPBodyと呼ばれるクラスに格納されます。

AABBのみがサポートされています。

すべてのSSSPBodyは、すべてのボディを更新し、重力を処理するSSSPWorldと呼ばれるクラスに格納されます。

すべてのフレームで、SSSPWorldはすべてのボディを更新します。

更新されたすべてのボディは、空間ハッシュで近くのボディを探し、それらとの衝突を検出する必要があるかどうかをチェックします。「はい」の場合、「衝突」イベントを呼び出し、それらとの衝突を解決する必要があるかどうかを確認します。はいの場合、侵入ベクトルと方向の重なりを計算し、侵入を解決するために位置を変更します。

ボディが他のボディと衝突すると、ボディの速度をそれ自体に設定するだけで、ボディの速度が他のボディに転送されます。

ボディの速度は、最後のフレームから位置を変更していない場合、0に設定されます。また、移動する物体(リフトや移動するプラットフォームなど)と衝突する場合、リフトの移動差を計算して、物体が最後の位置から移動していないかどうかを確認します。

また、ボディは、AABBのすべての角がフレーム内の何かと重なったときに「クラッシュ」イベントを呼び出します。

これは私のゲームの完全なソースコードです。3つのプロジェクトに分かれています。SFMLStartは、エンティティの入力、描画、更新を処理するシンプルなライブラリです。SFMLStartPhysicsは、SSSPBodyクラスとSSSPWorldクラスがある最も重要なものです。PlatformerPhysicsTestは、すべてのゲームロジックを含むゲームプロジェクトです。

そしてこれは、SSSPBodyクラスの「更新」メソッドであり、コメント化および簡略化されています。SFMLStartSimplePhysicsプロジェクト全体を見たくない場合にのみ、これを見ることができます。(そして、たとえ行ったとしても、コメントされているので、これを見てください。)


.gifは2つの問題を示しています。

  1. ボディが異なる順序で配置されている場合、異なる結果が発生します。左側のクレートは右側のクレートと同じで、逆順でのみ配置されます(エディターで)。
  2. 両方のクレートを画面の上部に向けて推進する必要があります。左側の状況では、木枠は推進されません。右側では、そのうちの1つだけです。両方の状況は意図していません。

最初の問題:更新の順序

これはかなり簡単に理解できます。左側の状況では、最上位のクレートが他のクレートの前に更新されます。下部のクレートが速度を他のクレートに「転送」しても、次のフレームが移動するのを待つ必要があります。移動しなかったため、下枠の速度は0に設定されます。

これを修正する方法がわかりません。物理エンジンの設計全体で何か間違ったことをしていると感じているため、更新リストの「ソート」に依存しないソリューションを好むでしょう。

主要な物理エンジン(Box2D、Bullet、Chipmunk)は更新順序をどのように処理しますか?


2番目の問題:1つのクレートのみが天井に向かって推進されます

私はまだこれが起こる理由を理解していません。「スプリング」エンティティが行うことは、ボディの速度を-4000に設定し、スプリング自体の上に再配置します。再配置コードを無効にしても、問題は引き続き発生します。

私の考えは、下のクレートが上のクレートと衝突すると、その速度は0に設定されるということです。これがなぜ起こるのかわかりません。


最初の問題をあきらめる誰かのように見える可能性にもかかわらず、私は上記のプロジェクトソースコード全体を投稿しました。私はそれを証明するものは何もありませんが、信じてください、私はこれを修正するために一生懸命努力しましたが、解決策を見つけることができず、物理学と衝突に関する以前の経験はありません。私はこれらの2つの問題を1週間以上解決しようとしてきましたが、今は必死です。

ゲームから多くの機能(速度転送やスプリングなど)を取り除くことなく、自分で解決策を見つけることはできないと思います。

この質問を読んでくれてありがとう。また、解決策や提案を考えてみてください。


ボックスをスタックするときはいつでも、物理を組み合わせて単一のオブジェクトと見なすことができますか?
CiscoIPPhone

回答:


12

実際、更新の問題の順序は通常のインパルス物理エンジンでは非常に一般的であり、Vigilが示唆するように力の適用を遅らせることはできません。通常、彼らは、更新の順序が異なると結果が大きく異なる場合でも、かなりリアルに見えるものを作成します。

いずれにせよ、インパルスシステムには十分なしゃっくりがありますので、代わりにマススプリングモデルを作成することをお勧めします。

基本的な考え方は、衝突を1ステップで解決しようとする代わりに、衝突するオブジェクトに力を適用することです。この力は、オブジェクト間のオーバーラップの量に等しくなければなりません。このシステムの素晴らしい点は、オブジェクトが前後に跳ね返ることなくオブジェクトを移動できることであり、更新の順序に関係なく完全に実行できることです。

オブジェクトが無期限に跳ね返るのではなく停止するためには、何らかの形のダンプニングを適用する必要があります。その方法によってゲームのスタイルと雰囲気に大きな影響を与えることができますが、非常に基本的なアプローチは内部の動きに相当する2つの接触オブジェクトに力を適用します。相互に向かって移動しているとき、または互いに離れるときにのみ適用することを選択できます。後者は、オブジェクトが跳ね返るのを完全に防ぐために使用できます地面に落ちたときに、少しべたつくようになります。

衝突の垂直方向にオブジェクトを制動することによって摩擦効果を作成することもできます。制動の量はオーバーラップの量に等しくなければなりません。

すべてのオブジェクトに同じ質量を持たせることで、質量の概念を簡単に回避できます。不動のオブジェクトは、単に加速を怠れば、無限の質量を持つように機能します。

上記が十分に明確でない場合に備えて、いくつかの擬似コード:

//Presuming that you have done collision checks between two objects and now have  
//numbers for how much they overlap in each direction.
overlapX
overlapY
if(overlapX<overlapY){ //Do collision in direction X
    if(obj1.X>obj2.X){
        swap(obj1,obj2)
    }
    //Spring effect:
    obj1.addXvelocity-=overlapX*0.1 //Constant, the lower this is set the softer the  
                                    //collision will be.
    obj2.addXvelocity+=overlapX*0.1
    //Dampener effect:
    velocityDifference=obj2.Xvelocity-obj1.Xvelocity
    //velocityDifference=min(velocityDifference,0) //Uncomment to only dampen when  
                                                   //objects move towards each other.
    obj1.addXvelocity+=velocityDifference*0.1 //Constant, higher for more dampening.
    obj2.addXvelocity-=velocityDifference*0.1
    //Friction effect:
    if(obj1.Yvelocity>obj2.Yvelocity){
        swap(obj1,obj2)
    }
    friction=overlapX*0.01
    if(2*friction>obj2.Yvelocity-obj1.Yvelocity){
        obj1.addYvelocity+=(obj2.Yvelocity-obj1.Yvelocity)/2
        obj2.addYvelocity-=(obj2.Yvelocity-obj1.Yvelocity)/2
    }
    else{
        obj1.addYvelocity+=friction
        obj2.addYvelocity-=friction
    }
}
else{ //Do collision in direction Y

}

addXvelocityプロパティとaddYvelocityプロパティのポイントは、すべての衝突処理が行われた後に、これらがオブジェクトの速度に追加されることです。

編集:
次の順序ですべての要素に対して各箇条書きを実行する必要がある次の順序で作業を行うことができます。

  • 衝突を検出します。検出されるとすぐに解決される場合があります。
  • addVelocity値を速度値に追加し、重力Yvelocityを追加し、addVelocity値を0にリセットし、速度に従ってオブジェクトを移動します。
  • シーンをレンダリングします。

また、私は最初の投稿で次のことが完全に明確ではないかもしれないことを理解しています。重力オブジェクトの影響下では、互いの上に載っているときに重なります。これは、重複を避けるために衝突ボックスがグラフィカル表現よりもわずかに高いことを示唆しています視覚的に。物理学がより高い更新レートで実行される場合、この問題はより少なくなります。CPU時間と物理的精度の妥当な妥協点を得るために、120Hzで実行することをお勧めします。

Edit2:
非常に基本的な物理エンジンフロー:

  • 衝突と重力は力/加速度を生み出します。 acceleration = [Complicated formulas]
  • 力/加速度が速度に追加されます。 velocity += acceleration
  • 速度が位置に追加されます。 position += velocity

よさそうだ、プラットフォーマーのための質量ばねについては考えなかった。啓発的な何かに
賛成

帰宅したら、数時間でこれを実装してみます。(位置+ =速度)ボディを同時に移動してから衝突をチェックするか、移動して衝突を1つずつチェックする必要がありますか?[また、衝突を解決するために手動で位置を変更する必要がありますか?または、速度を変更することで対応できますか?]
ヴィットリオロメオ

最初の質問の解釈方法が完全にはわかりません。衝突解決は速度を変更するため、間接的に位置にのみ影響します。
aaaaaaaaaaaa

事実、速度を手動で特定の値に設定することでエンティティを移動します。オーバーラップを解決するには、それらの位置からオーバーラップ距離を削除します。あなたの方法を使用する場合、力または他の何かを使用してエンティティを移動する必要がありますか?私は前にそれをやったことがありません。
ヴィットリオロミオ

技術的には、はい、力を使用する必要があります。ただし、コードの一部では、すべてのオブジェクトの重みが1になり、力が加速に等しいため、少し簡略化されています。
aaaaaaaaaaaa

14

まあ、あなたは明らかに簡単にgivesめる人ではありません、あなたは本当の鉄人です、このプロジェクトはケルプの森に強い類似性を持っているので、私はずっと早く空に投げていたでしょう:)

まず、位置と速度は、物理サブシステムの観点から、災害のレシピである場所全体に設定されます。また、さまざまなサブシステムによって不可欠なものを変更するときは、「ChangeVelocityByPhysicsEngine」、「ChangeVelocityBySpring」、「LimitVelocity」、「TransferVelocity」などのプライベートメソッドを作成します。ロジックの特定の部分によって行われた変更をチェックする機能を追加し、これらの速度の変更に追加の意味を提供します。そうすれば、デバッグが簡単になります。

最初の問題。

質問自体に。これで、外観とゲームロジックの順に「そのまま」位置と速度の修正を適用できます。複雑な相互作用の場合、各複雑な物の物理を慎重にハードコーディングしないと機能しません。その場合、別個の物理エンジンは必要ありません。

ハックせずに複雑な相互作用を行うには、初期速度によって変更された位置に基づいて衝突を検出し、「後速度」に基づいて位置を最終的に変更する間に追加のステップを追加する必要があります。私はそれがこのようになると想像します:

  • 体に作用するすべての力を使用して速度を統合します(速度修正を直接適用し、速度計算を物理エンジンに任せ代わりに力を使用して物事を移動します)、新しい速度を使用して位置を統合します。
  • 衝突を検出し、速度と位置を復元し、
  • その後、衝突を処理します(即時の位置更新なしのインパルスを使用して、ofc、速度のみが最終ステップまで変更されます)
  • 新しい速度を再度統合し、インパルスを使用してすべての衝突を再度処理します。ただし、衝突は非弾性になります。
  • 結果の速度を使用して位置の最終的な統合を行います。

ジャークに対処する、FPSが小さいときにスタックすることを拒否する、またはそのような他のものが準備されるなど、追加のものがポップアップする場合があります:)

第二の問題

これらの「デッドウェイト」クレートの両方の垂直速度はゼロから決して変化しません。奇妙なことに、PhysSpringの更新ループでは速度を割り当てますが、PhysCrateの更新ループでは既にゼロになっています。速度がおかしくなる行を見つけることは可能ですが、「Reap What You Sew」の状況であるため、ここでデバッグを停止しました。デバッグが困難になったら、コーディングを停止し、すべてを再考し始めましょう。しかし、コードの作成者でさえコードで何が起こっているのかを理解することが不可能になった場合、コードベースはあなたが気付かないうちにすでに死んでいます:)

第三の問題

単純なタイルベースのプラットフォーマーを実行するためにFarseerの一部を再作成する必要がある場合、何かがオフになっていると思います。個人的には、あなたの現在のエンジンは素晴らしい経験であると考え、それからもっと単純で簡単なタイルベースの物理学のために完全に捨てます。そうする間、Debug.Assertのようなもの、恐らくは恐ろしい単体テストのようなものを拾うのが賢明でしょう。というのは、予期しないものをより早くキャッ​​チすることができるからです。


私はその「昆布の森」の比較が好きでした。
デン

実際、私はそのような言葉を使うことを少し恥ずかしく思いますが、リファクタリングが1〜2回行われた場合、それは正当化されると感じました。
EnoughTea

tのテストが1つだけの場合、これが起こる可能性は常にありませんか?tで速度を統合し、速度を0に設定する前にt + 1で衝突をチェックする必要があると思いますか?
ジョナサン・コネル

そうです、tからt + dtまでの初期状態をRunge-Kuttaなどを使用して統合した後、前方の衝突を検出しています。
EnoughTea

「身体に作用するすべての力を使用して速度を統合する」「速度計算を物理エンジンに任せる」-あなたが言おうとしていることは理解していますが、これを行う方法についてはわかりません。あなたが私に示すことができる例/記事はありますか?
ヴィットリオロミオ

7

ボディが他のボディと衝突すると、ボディの速度をそれ自体に設定するだけで、ボディの速度が他のボディに転送されます。

あなたの問題は、これらが運動についての根本的に間違った仮定であるということです。そのため、あなたが得ているものは、あなたが慣れているように運動に似ていません。

身体が他の身体と衝突するとき、運動量は保存されます。これを「AヒットB」対「BヒットA」と考えることは、推移的な動詞を推移的でない状況に適用することです。AとBが衝突します。結果の運動量は初期運動量と等しくなければなりません。つまり、AとBが等しい質量である場合、それらは両方とも元の速度の平均で移動しています。

また、衝突スロップと反復ソルバーが必要になることもあります。そうしないと、安定性の問題が発生します。Erin CattoのGDCプレゼンテーションのいくつかを読んでください。


2
衝突が完全に非弾性である場合にのみ、元の速度の平均を取得します。たとえば、AとBは生地です。
ミカエルオーマン

「単に体の速度をそれ自身に設定することによって」。なぜ機能していないのかを明らかにするのは、このような声明です。一般的に、私は常に、経験の浅い人々が、関連する基本原則を理解せずに物理システムを書くことを発見しました。「速度を設定する」だけでなく、単に「...」も使用しないでください。身体の特性のすべての変更は、動力学の法則の直接適用である必要があります。運動量、エネルギーなどの保存を含みます。はい、常に不安定性を補うファッジファクターがありますが、身体の速度を魔法のように変更することはできません。
MrCranky

そもそもエンジンを動かそうとするとき、非弾性体を仮定するのが最も簡単です。複雑さは少ないほど問題解決に適しています。
パトリックヒューズ

4

あなたは本当に高貴な努力をしたと思いますが、コードの構造に根本的な問題があるようです。他の人が示唆したように、次のように、操作を個別の部分に分けると役立つ場合があります。

  1. 幅広いフェーズ:すべてのオブジェクトをループします-クイックテスト(たとえば、AABB)を実行して、衝突する可能性のあるオブジェクトを特定します-衝突しないオブジェクトを破棄します。
  2. 狭いフェーズ:すべての衝突オブジェクトをループします-衝突の貫通ベクトルを計算します(たとえば、SATを使用)。
  3. 衝突応答:衝突ベクトルのリストをループします-質量に基づいて力ベクトルを計算し、これを使用して加速度ベクトルを計算します。
  4. 統合:すべての加速ベクトルをループし、位置(および必要に応じて回転)を統合します。
  5. レンダリング:計算されたすべての位置をループして、各オブジェクトをレンダリングします。

フェーズを分離することにより、すべてのオブジェクトが徐々に同期して更新され、現在苦労している順序の依存関係がなくなります。また、コードは一般に、よりシンプルで簡単に変更できます。これらの各フェーズはかなり一般的なものであり、システムが正常に動作した後に、より優れたアルゴリズムを代用することができます。

とはいえ、これらの各部分はそれ自体が科学であり、最適な解決策を見つけるために多大な時間を費やすことができます。最も一般的に使用されるアルゴリズムのいくつかから始める方が良いかもしれません:

  • ブロードフェーズコリジョン検出空間ハッシング
  • 狭位相衝突の検出:単純なタイル物理学のために、Axis Aligned Bounding Box(AABB)交差テストを適用できます。より複雑な形状の場合は、軸分離定理を使用できます。使用するアルゴリズムが何であれ、2つのオブジェクト間の交点の方向と深さ(ペネトレーションベクトルと呼ばれる)を返す必要があります。
  • 衝突応答投影を使用して相互侵入を解決します。
  • 統合:積分器は、エンジンの安定性と速度の最大の決定要因です。2つの一般的なオプションは、Verlet(高速だがシンプル)またはRK4(正確だが低速)統合です。ほとんどの物理的動作(バウンス、回転)はあまり労力をかけずに機能するため、verlet統合を使用すると、非常にシンプルな設計になります。RK4統合を学習するために私が見た最高の参考文献の1つはゲームの物理学に関するグレンフィードラーのシリーズです。

開始するのに適切な(そして明らかな)場所は、ニュートンの運動の法則です


返信いただきありがとうございます。しかし、どうやって体の間で速度を転送するのですか?統合フェーズで発生しますか?
ヴィットリオロミオ

ある意味で、はい。速度の転送は、衝突応答フェーズから始まります。それが、体に作用する力を計算するときです。力は、加速=力/質量の式を使用して加速に変換されます。加速度は、位置を計算するために使用される速度を計算するために、統合フェーズで使用されます。積分フェーズの精度は、速度(およびその後の位置)が時間とともにどのくらい正確に変化するかを決定します。
ルークヴァン11年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.