Ball Physics:ボールが止まったときに最終的な跳ね返りを滑らかにする


12

私は小さなバウンドするボールゲームで別の問題に直面しました。

ボールが止まりそうな最後の瞬間を除いて、ボールはうまく跳ね返ります。ボールの動きは主要部分では滑らかですが、最後に向かって、画面の下部に落ち着くとしばらくボールが動きます。

なぜこれが起こっているのかは理解できますが、それをスムーズにすることはできません。

提供できるアドバイスに感謝します。

私の更新コードは次のとおりです。

public void Update()
    {
        // Apply gravity if we're not already on the ground
        if(Position.Y < GraphicsViewport.Height - Texture.Height)
        {
            Velocity += Physics.Gravity.Force;
        }            
        Velocity *= Physics.Air.Resistance;
        Position += Velocity;

        if (Position.X < 0 || Position.X > GraphicsViewport.Width - Texture.Width)
        {
            // We've hit a vertical (side) boundary
            // Apply friction
            Velocity *= Physics.Surfaces.Concrete;

            // Invert velocity
            Velocity.X = -Velocity.X;
            Position.X = Position.X + Velocity.X;
        }

        if (Position.Y < 0 || Position.Y > GraphicsViewport.Height - Texture.Height)
        {
            // We've hit a horizontal boundary
            // Apply friction
            Velocity *= Physics.Surfaces.Grass;

            // Invert Velocity
            Velocity.Y = -Velocity.Y;
            Position.Y = Position.Y + Velocity.Y;
        }
    }

おそらく、私もそれを指摘しなければならないGravityResistance GrassConcreteタイプのすべてをしていますVector2


これを確認するために、ボールが表面に衝突したときの「摩擦」の値は1未満であり、これは基本的に正しい反発係数ですか?
ホルヘLeitao

@JCLeitão-正しい。
サント

賞金と正解を授与するとき、票を守ることを誓わないでください。あなたを助けたものを探してください。
aaaaaaaaaaaa

それは賞金を処理するための悪い方法です、基本的にあなたはあなたが自分自身を判断できないのであなたが賛成票を決定させると言っています...とにかく、あなたが経験しているのは一般的な衝突ジッターです。最大相互侵入量、最小速度、または一度到達した他の「制限」の形式を設定することで解決できます。これにより、ルーチンは動きを停止し、オブジェクトを静止させます。無駄なチェックを避けるために、オブジェクトに静止ステータスを追加することもできます。
ダークウィングス

@Darkwings-このシナリオのコミュニティは、何が最良の答えであるかを私よりよく知っていると思います。それが、賛成票が私の決定に影響する理由です。明らかに、私が最も多くの賛成票で解決策を試みてもそれ役に立たなかった場合、その答えには賞を与えません。
サント

回答:


19

ここでは、物理シミュレーションループを改善するために必要な手順を説明します。

1.タイムステップ

コードで見られる主な問題は、物理ステップ時間を考慮していないことです。Position += Velocity;単位が一致しないため、何か問題があることは明らかです。どちらかVelocity実際の速度ではないか、何かが欠けています。

あなたの速度と重力の値は、各フレームは、時間単位で行われるようにスケーリングされていても1(つまり例えば、 Velocity実際に意味の距離、1秒で移動)時間がどこに表示されなければならないのいずれかで、コード内で暗黙のように変数を固定することにより(それらの名前は、実際に保存されているものを反映している)、または明示的に(タイムステップを導入することにより)。最も簡単なことは、時間単位を宣言することだと思います。

float TimeStep = 1.0;

そして、その値を必要なすべての場所で使用します。

Velocity += Physics.Gravity.Force * TimeStep;
Position += Velocity * TimeStep;
...

適切なコンパイラーは、による乗算を単純化する1.0ので、その部分が遅くなることはありません。

Position += Velocity * TimeStepはまだ完全に正確ではありません(理由を理解するためにこの質問を参照してください)が、おそらく今のところはそうでしょう。

また、これには時間を考慮する必要があります。

Velocity *= Physics.Air.Resistance;

修正するのは少し難しいです。可能な方法の1つは次のとおりです。

Velocity -= Vector2(Math.Pow(Physics.Air.Resistance.X, TimeStep),
                    Math.Pow(Physics.Air.Resistance.Y, TimeStep))
          * Velocity;

2.二重更新

次に、バウンス時に何をするかを確認します(関連するコードのみが表示されます)。

Position += Velocity * TimeStep;
if (Position.Y < 0)
{
    Velocity.Y = -Velocity.Y * Physics.Surfaces.Grass;
    Position.Y = Position.Y + Velocity.Y * TimeStep;
}

TimeStepバウンス中に2回使用されることがわかります。これは基本的に、ボールを更新する時間を2倍にしています。これは代わりに起こるべきことです:

Position += Velocity * TimeStep;
if (Position.Y < 0)
{
    /* First, stop at Y = 0 and count how much time is left */
    float RemainingTime = -Position.Y / Velocity.Y;
    Position.Y = 0;

    /* Then, start from Y = 0 and only use how much time was left */
    Velocity.Y = -Velocity.Y * Physics.Surfaces.Grass;
    Position.Y = Velocity.Y * RemainingTime;
}

3.重力

コードのこの部分を今すぐ確認してください。

if(Position.Y < GraphicsViewport.Height - Texture.Height)
{
    Velocity += Physics.Gravity.Force * TimeStep;
}            

フレーム全体に重力を追加します。しかし、そのフレームの間にボールが実際に跳ね返った場合はどうでしょうか?その後、速度は反転しますが、追加された重力により、ボールは地面から加速されます!そのため、跳ねるときに余分な重力を取り除く必要があります、正しい方向に再度追加する必要があります。

重力を正しい方向に再度追加しても、速度が過度に加速することがあります。これを回避するには、重力の追加をスキップするか(結局、それほど大きくなく、フレームのみ続く)、速度をゼロに固定することができます。

4.修正されたコード

完全に更新されたコードは次のとおりです。

public void Update()
{
    float TimeStep = 1.0;
    Update(TimeStep);
}

public void Update(float TimeStep)
{
    float RemainingTime;

    // Apply gravity if we're not already on the ground
    if(Position.Y < GraphicsViewport.Height - Texture.Height)
    {
        Velocity += Physics.Gravity.Force * TimeStep;
    }
    Velocity -= Vector2(Math.Pow(Physics.Air.Resistance.X, RemainingTime),
                        Math.Pow(Physics.Air.Resistance.Y, RemainingTime))
              * Velocity;
    Position += Velocity * TimeStep;

    if (Position.X < 0 || Position.X > GraphicsViewport.Width - Texture.Width)
    {
        // We've hit a vertical (side) boundary
        if (Position.X < 0)
        {
            RemainingTime = -Position.X / Velocity.X;
            Position.X = 0;
        }
        else
        {
            RemainingTime = (Position.X - (GraphicsViewport.Width - Texture.Width)) / Velocity.X;
            Position.X = GraphicsViewport.Width - Texture.Width;
        }

        // Apply friction
        Velocity -= Vector2(Math.Pow(Physics.Surfaces.Concrete.X, RemainingTime),
                            Math.Pow(Physics.Surfaces.Concrete.Y, RemainingTime))
                  * Velocity;

        // Invert velocity
        Velocity.X = -Velocity.X;
        Position.X = Position.X + Velocity.X * RemainingTime;
    }

    if (Position.Y < 0 || Position.Y > GraphicsViewport.Height - Texture.Height)
    {
        // We've hit a horizontal boundary
        if (Position.Y < 0)
        {
            RemainingTime = -Position.Y / Velocity.Y;
            Position.Y = 0;
        }
        else
        {
            RemainingTime = (Position.Y - (GraphicsViewport.Height - Texture.Height)) / Velocity.Y;
            Position.Y = GraphicsViewport.Height - Texture.Height;
        }

        // Remove excess gravity
        Velocity.Y -= RemainingTime * Physics.Gravity.Force;

        // Apply friction
        Velocity -= Vector2(Math.Pow(Physics.Surfaces.Grass.X, RemainingTime),
                            Math.Pow(Physics.Surfaces.Grass.Y, RemainingTime))
                  * Velocity;

        // Invert velocity
        Velocity.Y = -Velocity.Y;

        // Re-add excess gravity
        float OldVelocityY = Velocity.Y;
        Velocity.Y += RemainingTime * Physics.Gravity.Force;
        // If velocity changed sign again, clamp it to zero
        if (Velocity.Y * OldVelocityY <= 0)
            Velocity.Y = 0;

        Position.Y = Position.Y + Velocity.Y * RemainingTime;
    }
}

5.さらに追加

シミュレーションの安定性をさらに向上させるために、より高い周波数で物理シミュレーションを実行することを決定できます。これは、上記の変更によって簡単になります。これはTimeStep、必要なだけチャンクにフレームを分割する必要があるためです。例えば:

public void Update()
{
    float TimeStep = 1.0;
    Update(TimeStep / 4);
    Update(TimeStep / 4);
    Update(TimeStep / 4);
    Update(TimeStep / 4);
}

「コードのどこかに時間が必要です。」あなたは、あちこちで1を掛けるのは良いアイデアではなく、必須だと宣伝していますか?調整可能なタイムステップは素晴らしい機能ですが、それは間違いなく必須ではありません。
aaaaaaaaaaaa

@eBusiness:私の主張は、調整可能なタイムステップよりも一貫性とエラーの検出に関するものです。私は1を掛ける必要があると言っvelocity += gravityているのではなくvelocity += gravity * timestep、間違っていると言っているだけで意味があります。最終的には同じ結果になるかもしれませんが、「ここで何をしているのか知っています」というコメントなしでは、コーディングエラー、ずさんなプログラマー、物理学に関する知識の欠如、または単に必要なプロトタイプコードを意味します改善されます。
サムホセバー

あなたはそれが間違っていると言いますが、あなたが言いたいことはそれが悪い習慣だということです。問題に対するあなたの主観的な意見であり、あなたがそれを表現することは問題ありませんが、この点に関するコードが意図したとおりに正確に行うため、主観的です。私が尋ねるのは、投稿で主観と客観の違いを明確にすることだけです。
aaaaaaaaaaaa

2
@eBusiness:正直なところ、健全な標準で間違っています。1)速度と重力を追加しても実際には何も意味がないため、コードは「意図したとおりに動作しません」。2)妥当な結果が得られるのは、格納されている値gravityが実際には... 重力ではないためです。しかし、私はポストでそれをより明確にすることができます。
サムホセバー

それどころか、それを間違って呼び出すことは、健全な標準によって間違っています。重力は、gravityという名前の変数に保存されず、代わりに数字があり、それがすべてであり、想像する関係を超えて物理学とは関係がなく、それを掛けることは正しい別の数字はそれを変えません。一見変化するのは、コードと物理学の間に精神的なつながりを作るあなたの能力および/または意欲です。ところで、かなり興味深い心理学的観察。
aaaaaaaaaaaa

6

最小の垂直速度を使用して、バウンスを停止するチェックを追加します。そして、最小限のバウンスが得られたら、ボールを地面に置きます。

MIN_BOUNCE = <0.01 e.g>;

if( Velocity.Y < MIN_BOUNCE ){
    Velocity.Y = 0;
    Position.Y = <ground position Y>;
}

3
このソリューションは気に入っていますが、バウンスをY軸に制限しません。衝突点でコライダーの法線を計算し、衝突速度の大きさが跳ね返りのしきい値より大きいかどうかを確認します。OPの世界でYバウンスのみが許可されている場合でも、他のユーザーはより一般的なソリューションが役立つと感じるかもしれません。(不明確な場合は、2つの球体をランダムなポイントでバウンドさせることを考えてください)
ブランドン

@brandon、素晴らしい、それは通常の方がうまくいくはずです。
ジェン

1
@Zhen、サーフェスの法線を使用すると、重力の方向と平行ではない法線を持つサーフェスにボールが付着する可能性があります。可能であれば、重力を計算に含めてみます。
ニックフォスター

これらのソリューションでは、速度を0に設定しないでください。バウンスのしきい値に応じて、ベクトルの法線全体の反射のみを制限します
ブランドン

1

だから、これがなぜ起こるかという問題は、あなたのボールが限界に近づいていることだと思います。数学的には、ボールは表面で止まることはなく、表面に近づきます。

ただし、ゲームは連続時間を使用していません。これは、微分方程式の近似を使用しているマップです。そして、この限定的な状況ではその近似は有効ではありません(できますが、より小さくより小さな時間ステップを取る必要がありますが、これは実行不可能だと思います。

物理的に言えば、起こることは、ボールが表面に非常に近いとき、総力が所定の閾値を下回ると、ボールにくっつくことです

システムが均質であれば、@ Zhenの答えは問題ありませんが、そうではありません。y軸に重力があります。

したがって、解決策は、速度が所定のしきい値を下回る必要があるということではなく、更新後にボールに適用される合計力が所定のしきい値を下回る必要があるということです。

この力は、壁に加えられたボールの力+重力の寄与です。

条件は次のようになります

if(newVelocity + Physics.Gravity.Force <threshold)

バウンスがボタンの壁にある場合、newVelocity.yは正の量であり、重力は負の量であることに注意してください。

また、newVelocityとPhysics.Gravity.Forceは、あなたが書いたように同じ次元を持っていないことに注意してください。

Velocity += Physics.Gravity.Force;

つまり、あなたと同じように、delta_time = 1およびballMass = 1と仮定しています。

お役に立てれば


1

衝突チェック内で位置の更新がありますが、それは冗長であり、間違っています。そして、ボールにエネルギーを加えて、ボールが永久に動くのを潜在的に助けます。重力がいくつかのフレームに適用されないことに加えて、これはあなたの奇妙な動きを与えます。それを除く。

ここで、ボールが指定された領域の外側で「スタック」し、永久に前後にバウンドするという別の問題が表示される場合があります。

この問題を解決する簡単な方法は、ボールを変更する前にボールが正しい方向に動くことを確認することです。

したがって、以下を作成する必要があります。

if (Position.X < 0 || Position.X > GraphicsViewport.Width - Texture.Width)

に:

if ((Position.X < 0 && Velocity.X < 0) || (Position.X > GraphicsViewport.Width - Texture.Width && Velocity.X > 0))

Y方向についても同様です。

ボールがうまく停止するためには、ある時点で重力を止める必要があります。現在の実装では、ボールが地下にある限り重力がブレーキをかけないため、ボールは常に再浮上します。常に重力を適用するように変更する必要があります。ただし、これにより、沈降後にボールがゆっくりと地面に沈みます。これを簡単に修正するには、重力を適用した後、ボールが表面レベルを下回り、下方に移動している場合、停止します。

Velocity += Physics.Gravity.Force;
if(Position.Y > GraphicsViewport.Height - Texture.Height && Velocity.Y > 0)
{
    Velocity.Y = 0;
}

これらの合計の変更により、適切なシミュレーションが得られます。しかし、それはまだ非常に単純なシミュレーションであることに注意してください。


0

あらゆる速度変化に対するミューテーターメソッドがあり、そのメソッド内で、更新された速度をチェックして、静止するのに十分な速度で移動しているかどうかを判断できます。私が知っているほとんどの物理システムは、この「回復」と呼ばれます。

public Vector3 Velocity
{
    public get { return velocity; }
    public set
    {
        velocity = value;

        // We get the direction that gravity pulls in
        Vector3 GravityDirection = gravity;
        GravityDirection.Normalize();

        Vector3 VelocityDirection = velocity;
        VelocityDirection.Normalize();

        if ((velocity * GravityDirection).SquaredLength() < 0.25f)
        {
            velocity.Y = 0.0f;
        }            
    }
}
private Vector3 velocity;

上記の方法では、重力と同じ軸に沿っているときは常に跳ね返りを制限します。

他に考慮すべきことは、ボールが地面に衝突するたびに検出することであり、衝突時にボールの動きがかなり遅い場合は、重力軸に沿った速度をゼロに設定します。


これは有効であるため、私はダウン投票しませんが、質問は速度のしきい値ではなく、バウンスのしきい値について尋ねています。私の経験では、これらはほとんど常に分離しています。バウンス中のジッターの影響は、通常、視覚的に静止した状態で速度を計算し続ける影響とは異なるためです。
ブランドン

それらは同じものです。HavokやPhysXなどの物理エンジン、およびJigLibXは、線速度(および角速度)に基づいて復元します。この方法は、跳ね返りを含め、ボールのあらゆる動きに対して機能するはずです。実際、私が最後に行ったプロジェクト(LEGOユニバース)では、これとほぼ同じ方法を使用して、コインの速度が低下した後にコインの跳ね返りを停止しました。その場合、動的物理学を使用していなかったため、Havokに処理を任せるのではなく、手動で行う必要がありました。
ニックフォスター

@NicFoster:私の頭の中では、オブジェクトが非常に高速に水平方向に動き、垂直方向にはほとんど動きません。その場合、メソッドはトリガーされません。OPは、速度の長さが高いにもかかわらず、垂直距離をゼロに設定することを望んでいると思います。
ジョージダケット

@GeorgeDuckett:ああありがとう、元の質問を誤解した。OPはボールの動きを止めたくないので、垂直方向の動きを止めてください。跳ね返る速度のみを考慮して回答を更新しました。
ニックフォスター

0

別のこと:あなたは摩擦定数を掛けています。それを変更します-摩擦定数を下げますが、バウンスに固定エネルギー吸収を追加します。これにより、最後のバウンスがはるかに速く減衰します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.