2D AABBと複数の衝突の解決


8

わかりました、これは私がかなり長い間理解しようとしてきた問題です。Mineは2Dプラットフォーマーゲームで、ワールドは(通常)不動のタイルとモバイルスプライトで構成され、どちらもAABBを使用してヒットボックスを表します。このゲームは、タイルのレイヤーの移動に伴う複雑さのため、グリッドベースではありません。

衝突を検出し、衝突の深さを簡単に把握できます。「最短軸メソッド」を使用して、スプライトとタイルの間の衝突を解決する方法を決定します。スプライトが垂直よりも水平に深い場合、解決する方向は上または下になります。スプライトが水平よりも垂直に深い場合、解決する方向は左または右です。

図#1

これは非常に単純で、かなりうまくいきます。つまり、スプライトが複数のタイルと衝突するまでです。その性質上、各衝突は個別にチェックする必要があるため、衝突が異なると解決する方向も異なる場合があります。たとえば、スプライトがタイルの行を横切って移動しようとしている場合、1つのフレームで次のタイルと交差します。水平深度が垂直深度よりも短いこと。衝突が「左に解決」と言っているので、それは押し戻され、コーナーにスタックします。

図#2

私はかなり長い間この問題を何度も何度も検討してきましたが、いくつかの解決策が思い付きましたが、すべてに欠陥があります。特定の側面を到達不能としてマークすることもできますが、グリッドベースのエンジンがないと、「到達不能」の判定は非常に複雑で、特にタイルのレイヤーの移動は常に可能です。

別の可能な方法は、衝突が発生する前に衝突を予測し、衝突のポイントまで移動を「ワークバック」することですが、私はその数学がどのように機能するのかわかりません。

特に80年代のゲームはすでにこの問題を解決しているので、私には信じられないほど明白なものが欠けているように感じます。


チェックで最初に来たタイルに基づいて、プレーヤーの場所を変更するだけです
Chachmu

回答:


6

問題

問題は、衝突解決の方法にあります。あなたの方法は次のようになります:

  1. プレイヤーを動かします。
  2. 衝突をチェックします。
  3. 最短の衝突深度を決定します。
  4. 衝突を解決します。

これの問題は、プレイヤーを簡単に間違った方向に動かすことができることです。これがどのように発生するかは、以下の画像で確認できます。

衝突バグ

プレーヤーは右下に移動しており、地面の上にいるため、プレーヤーは地面の上に(緑色のボックスで)着地することになります。しかし、代わりに地面から左に押し出されます(赤いボックスで表されます)。これは、あるプラットフォームから別のプラットフォームにジャンプしようとしている場合に問題になる可能性があります。これは、悪い衝突コードが原因で、プレーヤーが最終的に死に至る可能性があるためです。

ソリューション

この問題の解決策は実際にはかなり簡単です。上記の方法を使用する代わりに、衝突を次のように解決します。

  1. X軸に沿ってプレーヤーを移動します。
  2. 衝突するタイルを確認します。
  3. Xの衝突を解決します。
  4. プレーヤーをY軸に沿って移動します。
  5. 衝突するタイルを確認します。
  6. Yの衝突を解決します。

ステップ3と6で引き続き必要になるため、深度チェックコードを捨てないでください。

2つの軸のいずれかのタイル間の衝突を解決するには(プレーヤーを動かした後)、最初に衝突の深さを取得します。次に、衝突の深さを取得し、現在衝突をチェックしている軸からそれを差し引きます。プレーヤーが右方向に移動するように、左に移動している場合、深度は負である必要があることに注意してください。

この方法を使用すると、上の画像のシナリオにあるような衝突のバグを心配する必要がないだけでなく、この方法は複数のタイルとの衝突を処理することもできます。

コード例:

void move(velocity)
{
    top = player.y / TILE_HEIGHT;
    bottom = top + (player.height / TILE_HEIGHT);
    left = player.x / TILE_WIDTH;
    right = left + (player.width / TILE_WIDTH);

    // Check X

    player.x += velocity.x;
    player.updateAABB();
    for(int tx = left - 1; tx <= right + 1; tx++)
    {
        for(int ty = top - 1; ty <= bottom + 1; ty++)
        {
            aabb = world.getTileAABB(tx, ty);
            if(aabb.collidesWith(player.aabb))
            {
                depth = player.aabb.getXDepth(aabb);
                player.x -= depth;
            }
        }
    }

    // Now check Y

    player.y += velocity.y;
    player.updateAABB();
    for(int tx = left - 1; tx <= right + 1; tx++)
    {
        for(int ty = top - 1; ty <= bottom + 1; ty++)
        {
            aabb = world.getTileAABB(tx, ty);
            if(aabb.collidesWith(player.aabb))
            {
                depth = player.aabb.getYDepth(aabb);
                player.y -= depth;
            }
        }
    }

    player.updateAABB();
}

興味をそそられますが、まだ問題が発生します。2番目のシナリオでは、スプライトがタイルの行と衝突します。最初にXの衝突をチェックすると、そのシナリオでは誤った検出が1つあり、それでも左に不適切に解決されます。
Celarix 2014年

@Celarix 2番目のシナリオは、X軸を最初にチェックするだけではなく、最初にそれに沿って移動しているため、発生しません。前の動きのY衝突テストでは、このようなタイルの行との衝突を防ぐことができるため、スプライトはタイルの行には決して含まれません。衝突が常に適切に解決されることを確認してください。かつて、floatsを使用して座標を保存していることが原因で問題が発生しました。それが揺れの原因でした。解決策は、衝突の解決を終えたときに座標を丸めることでした。
Lysol 2014年

あなたは正しい、そして私はこれがほぼ2年間の私の問題の解決策であると思う (私は遅い開発者です。)どうもありがとう!
Celarix 2014年

私の回答にはいくつか問題があります。ほとんどの状況で機能しますが、X衝突を最初にチェックするか、Y衝突を最初にチェックするかによって結果が異なることに注意してください。また、トンネリングが問題であることにも注意してください。タイルをスキップするため、高速オブジェクトでは失敗します。
Lysol

私は最初にXを使ったので、スロープは正しく機能すると思います。タイルを通過するのに十分な速さで動くものは何もないので、通し紙は私のプラットフォーマーでは本当に問題ではありません。さらに入力をありがとう!
Celarix

0

あなたは問題を考えすぎて、いくつかの問題を融合させています。しかし、あなたが言ったように、これは非常に解決された問題であり、そこには多くのすばらしい答えがあります。

分解してみましょう:

  1. タイルマップ。最初の例は、水平に配置された一連のタイルを横切るスプライトです(または、垂直に配置されたタイルの壁を滑り降りると、同型になります)。これに対する非常に洗練された解決策の1つは、スプライトが到達できないことがわかっているタイルのエッジ(「地下」のエッジや別の完全に固いタイルに接するエッジなど)をチェックしないことです。

    重力によってスプライトが下降し、横方向に移動してから動けなくなるのは当然ですが、答えは、地下にあるタイルの左端または右端を気にないことです。このようにして、衝突解決ルーチンはスプライトを垂直方向にのみ移動します—そして、スプライトは陽気な方法で進むことができます。

    これの段階的な説明については、メタネットタイルのチュートリアルを確認してください。質問では、従来のタイルマップを使用していないと言っていますが、それでも問題ありません。静的タイルはタイルマップにあり、上記のように更新されますが、プラットフォームを移動し、以下の#2などの更新を行います。

  2. その他のAABB。1つのフレーム内で、スプライトがゲーム内のほとんどのAABBの幅/高さよりも大きい距離を移動できる場合にのみ、問題が発生します。それができない場合、あなたは黄金です。衝突を1つずつ解決すれば、問題なく機能します。

    AABB 1つのフレームで非常に速く移動できる場合は、衝突をチェックするときに移動を「スイープ」する必要があります。移動を細かい部分に分割し、各ステップで衝突をチェックします。

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