AABBを使用した衝突検出の問題


8

メインゲームスプライトとさまざまなプラットフォームの間にAABBを使用して、単純な衝突検出ルーチンを実装しました(以下のコードを参照してください)。それは素晴らしい働きをしますが、私は今、私のキャラクターを倒すために重力を導入しています、そしてそれは私のCDルーチンでいくつかの問題を示しています。

問題の核心は、私のCDルーチンがスプライトを他のスプライトにほとんど浸透していない軸に沿って戻すことです。したがって、XよりY軸の方が大きい場合は、X軸に沿って移動します。

しかし、今私の(ヒーロー)スプライトはフレームあたり30ピクセルの速度で落ちています(それは1504の高画面です-「通常の」重力をシミュレートしたいので、これだけ速く落ちる必要があります。 )これらの問題が発生しています。私は何が起こっているのか(そして私がそれを引き起こしていると考えていること-よくわかりません)をいくつかの写真で示します(写真の下のコード)。

この問題を回避する方法についていくつかの提案をいただければ幸いです。

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

明確にするために、上の右の図で、位置が「誤って」修正されていると言うと、少し誤解を招く可能性があります。コード自体は、それがどのように書かれているのか、つまり別の言い方をすると、アルゴリズム自体が正しく機能します。期待どおりに動作する場合はアルゴリズム自体ですが、この問題の発生を防ぐために動作を変更する必要があります。画像内のコメントを明確にしてください。

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

私のコード

public boolean heroWithPlatforms(){

    //Set Hero center for this frame
    heroCenterX  = hero.xScreen+(hero.quadWidth/2);
    heroCenterY  = hero.yScreen+(hero.quadHeight/2);

    //Iterate through all platforms to check for collisions
    for(x=0;x<platformCoords.length;x+=2){

    //Set platform Center for this iteration
    platformCenterX = platformCoords[x]+(platforms.quadWidth/2);
    platformCenterY = platformCoords[x+1]+(platforms.quadHeight/2);

    // the Dif variables are the difference (absolute value)
    // of the center of the two sprites being compared (one for X axis difference
    //and on for the Y axis difference)
    difX = Math.abs(heroCenterX-platformCenterX);
    difY = Math.abs(heroCenterY-platformCenterY);

    //If 1
    //Check the difference between the 2 centers and compare it to the vector (the sum of
    //the two sprite's widths/2.  If it is smaller, then the sprites are pverlapping along
    //the X axis and we can now proceed to check the Y axis 
    if (difX<vecXPlatform){

    //If 2
    //Same as above but for the Y axis, if this is also true, then we have a collision
    if(difY<vecYPlatform){
        //we now know sprites are colliding, so we now need to know exactly by how much
        //penX will hold the value equal to the amount one sprite (hero, in this case)
        //has overlapped with the other sprite (the platform)
        penX = vecXPlatform-difX;
        penY = vecYPlatform-difY;
        //One sprite has penetrated into the other, mostly in the Y axis, so move sprite
        //back on the X Axis
        if (penX < penY){hero.xScreen-=penX*(heroCenterX-platformCenterX>=0 ? -1 : 1);}
        //Sprite has penetrated into the other, mostly in the X asis, so move sprite
        //back on the Y Axis
        else if (penY < penX) {hero.yScreen-=penY*(heroCenterY-platformCenterY>=0 ? -1 : 1);}
        return true;
        }//Close 'If' 2
        } //Close 'If' 1
    }
    //Otherwise, no collision

    return false;
    }

詳述すると、なぜそれはあなたが戻って、他の軸上のスプライトを移動している: //One sprite has penetrated into the other, mostly in the Y axis, so move sprite //back on the X Axis
トム・「青」Piddock

こんにちは、@ Blue。スプライトは最小貫通軸に沿って修正する必要があるため、スプライトがY軸よりもX軸でプラットフォームを貫通している場合、2つのうち小さい方なのでY軸を修正する必要があります。
BungleBonce 2013年


私は@Ankoの前にその質問を読みました-それは私に必要な答えを与えません。したがって、私は自分の質問を作成しました(確認するのは難しいですが、質問者がまったく同じことを尋ねているとは確信していません):-)
BungleBonce

回答:


1

HTML5キャンバスとAABBを使った実験中に、まさにあなたが経験していることを見つけました。これは、32x32ピクセルの隣接するボックスからプラットフォームを作成しようとしたときに発生しました。

個人的な好みの順序で試みた解決策

1-移動を軸で分割

私の現在のもの、そして私はそれで私のゲームを続けると思います。ただし、現在のデザインを大幅に変更することなく簡単な解決策が必要な場合は、Phantom AABBと呼ばれるものを確認してください。

私のゲームの世界は非常に単純な物理学を持っています。力の概念は(まだ)なく、変位のみです。重力は、下への一定の変位です。加速なし(まだ)。

変位は軸によって適用されます。そしてこれには、この最初のソリューションが含まれます。

各ゲームフレーム:

player.moves.y += gravity;
player.moves.x += move_x;

move_xは入力処理に従って設定され、矢印キーが押されていない場合はmove_x = 0です。ゼロ未満の値は左を意味し、それ以外の場合は右を意味します。

他のすべての移動スプライトを処理し、それらの「moves」コンポーネントの値を決定します。これらには「moves」コンポーネントもあります。

注:衝突の検出と応答の後、次のティックが重力を再び追加するため、移動コンポーネントはxプロパティとyプロパティの両方でゼロに設定する必要があります。リセットしない場合は、希望の重力の2倍のmoves.yで終了し、次のティックで3倍になります。

次に、変位適用および衝突検出コードを入力します。

moveコンポーネントは、実際にはスプライトの現在の位置ではありません。move.xまたはyが変更されても、スプライトはまだ移動していません。「moves」コンポーネントは、ゲームループの正しい部分でスプライトの位置に適用される変位を保存する方法です。

最初にy軸を処理します(moves.y)。move.yをsprite.position.yに適用します。次に、AABBメソッドを適用して、処理中の移動するスプライトがプラットフォームのAABBとオーバーラップしているかどうかを確認します(これを行う方法はすでに理解しています)。重なっている場合は、penetration.yを適用してスプライトをy軸に戻します。はい、私の他の回答とは対照的に、この移動進化メソッドは、プロセスのこのステップでは、penetration.xを無視します。そして、penetration.yは、penetration.xより大きい場合でも使用します。

次に、x軸(moves.x)を処理します。move.xをsprite.position.xに適用します。以前とは逆のことを行います。今回は、penetration.xを適用して、スプライトを横軸のみに移動します。以前と同様に、penetration.xがpenetration.yよりも小さいか大きいかは問題ではありません。

ここでの概念は、スプライトが移動しておらず、最初は衝突していなかった場合、次のフレームでもそのように維持されます(sprite.moves.xとyはゼロです)。別のゲームオブジェクトが処理を開始するスプライトと重なる位置に魔法のようにテレポートするという特別な場合は、後で処理します。

スプライトが動き始めるとき。それがx軸のみで移動する場合は、何かが左または右を貫通するかどうかにのみ関心があります。スプライトは下に移動しておらず、moveコンポーネントのyプロパティがゼロであるためこれがわかっているため、penetration.yの計算値もチェックしません。penetration.xでのみ確認します。動きの軸に侵入が存在する場合は修正を適用し、そうでない場合は単にスプライトを移動させます。

必ずしも45度の角度ではなく、対角変位についてはどうでしょうか?

提案されたシステムはそれを扱うことができます。各軸を個別に処理するだけで済みます。シミュレーション中。スプライトにはさまざまな力がかかります。エンジンがまだ力を理解していない場合でも。これらの力は、2D平面の任意の変位に変換されます。この変位は、任意の方向および任意の長さの2Dベクトルです。このベクトルから、y軸とx軸を抽出できます。

変位ベクトルが大きすぎる場合はどうすればよいですか?

あなたはこれを望まない:

|
|
|
|
|
|
|
|_ _ _ _ _ _ _ _ _

ロジックを適用する新しい動きは、最初に軸を処理し、次に他の軸を処理するため、大きなLのような衝突コードによって大きな変位が検出されなくなる可能性があり、変位ベクトルと交差するオブジェクトとの衝突を正しく検出できません。

ソリューションは、大きな変位を小さなステップで分割することです。理想的には、スプライトの幅または高さ、またはスプライトの幅または高さの半分です。このようなものを取得するには:

|_
  |_
    |_
      |_
        |_
          |_
            |_
              |_

スプライトのテレポートについてはどうですか?

テレポートを大きな変位として表す場合、通常の移動と同じ移動コンポーネントを使用すると、問題ありません。そうでない場合は、テレポートスプライトを衝突コードで処理するようにマークする方法を考える必要があります(この目的専用のリストに追加しますか?)テレポーテーションが発生した方法。

2-ファントムAABB

スプライトの下部から始まる重力の影響を受ける各スプライトのセカンダリAABBがあり、スプライトAABBと同じ幅で、高さが1ピクセルです。

ここでの考え方は、このファントムAABBは常にスプライトの下のプラットフォームと衝突するということです。このファントムAABBが重なっていない場合のみ、重力をスプライトに適用できます。

ファントムAABBとプラットフォームの浸透ベクトルを計算する必要はありません。単純な衝突テストのみに関心があり、プラットフォームとオーバーラップする場合、重力を適用できません。スプライトには、プラットフォーム上か空中かを示すブールプロパティを設定できます。ファントムAABBがプレーヤーの下のプラットフォームに衝突していないときは、空中にいます。

この:

player.position.y += gravity;

このようなものになります:

if (player.onGround == false) player.position.y += gravity;

非常にシンプルで確実です。

AABB実装はそのままにします。

ファントムAABBには、それらを処理する専用ループがあり、単純な衝突のみをチェックし、侵入ベクトルの計算に時間を浪費しません。このループは、通常の衝突および応答コードの前にある必要があります。空気中のこれらのスプライトが重力を適用するため、このループ中に重力を適用できます。ファントムAABB自体がクラスまたは構造である場合、それが属するスプライトへの参照を持っている可能性があります。

これは、プレーヤーがジャンプしているかどうかを確認する他の提案されたソリューションと非常に似ています。プレイヤーがプラットフォームの上に足を置いているかどうかを検出する方法を提供します。


ここではPhantom AABBソリューションが好きです。私は問題を回避するために同様の何かをすでに適用しました。(ただし、ファントムAABBは使用しません)-上記の最初の方法について詳しく知りたいのですが、少し混乱しています。Moves.x(および.y)コンポーネントは、基本的にスプライトの移動量(移動が許可されている場合)ですか?また、それが私の状況にどのように直接役立つかは不明です。回答を編集して、私の状況にどのように役立つかをグラフィックで示すことができれば幸いです(上記の私のグラフィックのように)-ありがとう!
BungleBonce 2013年

moveコンポーネントについて。はい、将来の変位を表しますが、移動を許可するかどうかは確認せず、実際に軸、最初にy、次にxで移動を適用し、次に何かと衝突したかどうかを確認します。戻るベクトル。後の処理のためにディスプレイスメントを保存して、処理するユーザー入力があるときやゲームの世界で何かが起こったときに実際にディスプレイスメントを適用するのと異なるのはなぜですか?回答を編集するときに、これを画像に配置するようにします。一方、他の誰かが代替案を持っているかどうかを確認しましょう。
そうとうはる2013年

Ah OK-私はあなたが今言っていることを理解していると思います。したがって、xとyを移動して衝突をチェックする代わりに、メソッドを使用して、最初にXを移動してから衝突をチェックし、衝突した場合はX軸に沿って修正します。次に、Yを移動して衝突を確認します。衝突がある場合は、X軸に沿って移動しますか?このようにして、常に正しい影響の軸を知っていますか?これは正しいですか?ありがとう!
BungleBonce 2013年

警告:あなたは「それからYを動かして衝突をチェックし、衝突があればX軸に沿って動かしますか?」と書いた。そのY軸。おそらくタイプミス。はい、私の現在のアプローチ。非常に大きな変位の場合、スプライトの幅または高さよりも大きいため、小さなステップで分割する必要があります。これが、Lのアナロジーで意味するところです。y軸に大きな変位を適用し、次に大きなxを適用しないことに注意してください。正しい動作を得るには、小さなステップで分割し、挿入する必要があります。ゲームで大きな変位が許可されている場合にのみ気を付けること。私の場合、それらが発生することはわかっています。それで準備をしました。
そうとうはる2013年

素晴らしい@HatoruHansou、私はこのようにすることを考えたことはありません。私は確かにそれを試してみます-あなたの助けに感謝します、私はあなたの答えを受け入れられたものとしてマークします。:-)
BungleBonce 2013年

5

プラットフォームタイルを1つのプラットフォームエンティティに結合します。

たとえば、写真から3つのタイルを取り、それらを1つのエンティティに結合すると、エンティティには次のような衝突ボックスがあります。

Left   = box1.left
Right  = box3.right
Top    = box1.top
Bottom = box1.bottom

これを衝突に使用すると、プラットフォーム内の特定のタイルを使用できるようにしながら、プラットフォームのほとんど全体でyが最も貫通しにくい軸になります。


こんにちは@UnderscoreZero-これは1つの方法です。唯一のことは、現在、タイルを左端のタイル用、中央のタイル用、右端のタイル用の3つの別々の配列に格納していることです。それらをすべてCDの目的で1つの配列に結合しますが、それを行うためのコードをどのように書くことができるかはわかりません。
BungleBonce 2013年

多くの物理エンジンが使用するもう1つのオプションは、オブジェクトが平面に着陸すると、オブジェクトの重力を無効にして、オブジェクトが落ちないようにすることです。オブジェクトが平面から引き離すのに十分な力を受けるか、平面の側面から落ちると、重力を再び有効にし、通常どおりに物理を実行します。
UnderscoreZero 2013年

0

このような問題は、新しい衝突検出方法では一般的です。

現在の衝突設定についてはあまり知りませんが、次のようにしてください。

まず、これらの変数を作成します。

  1. XとYの速度を作る
  2. 重力を作る
  3. ジャンプスピードを作る
  4. isJumpingを作成する

次に、オブジェクトの速度に影響を与えるデルタを計算するタイマーを作成します。

3番目に、これを行います。

  1. プレーヤーがジャンプボタンを押してジャンプしていないかどうかを確認します。そうでない場合は、Y速度にjumpSpeedを追加します。
  2. 重力をシミュレートするために、更新ごとにY速度から重力を差し引く
  3. プレーヤーのy +高さがプラットフォームのy以下である場合は、Y速度を0に設定し、プレーヤーのYをプラットフォームのy +プレーヤーの高さに設定します。

これを行えば、何の問題もないはずです。お役に立てれば!


こんにちは@ user1870398、ありがとうございます。しかし、私のゲームには現時点でジャンプさえありません。スプライトの位置を修正する「最小貫通軸」メソッドの使用によって発生する問題に関連する質問です。問題は、重力が私のキャラクターをX軸に重なるよりもY軸に沿って私のプラットフォームにさらに引き込んでいるため、私のルーチン(このタイプのアルゴリズムでは正しい)がX軸を修正しているため、できませんプラットフォームに沿って移動します(CDルーチンが個々のタイルを確認し、1つのプラットフォームとして表示されている場合でも、タイルをそのように扱います)。
BungleBonce 2013年

私はあなたの問題を取得します。それはあなたがあなたの動きを適切に修正していないというだけです。isJumping変数が必要です。ジャンプボタンを押してジャンプしていない場合はtrueに設定し、地面にぶつかった場合はfalseに設定し、プレーヤーのYを正しい位置に移動し(platform.y + player.height)、isJumpingをfalseに設定します。次に、これらのことを確認した直後に、(left_key)player.velocity.x-=(player.isJumping?player.speed:player.speed-player.groundFriction)を実行します。まだジャンプを実装していないので、常にisJumpingをfalseに設定します。
LiquidFeline 2013年

ジャンプはありませんが、 'ifJumping'変数が常にfalseに設定されている場合、これがないのとどう違うのですか?(ジャンプのないゲームで言ってください)「ジャンプする」ブール値が私の状況でどのように役立つかについて少し混乱しています-もう少し説明してもらえればありがたいです-ありがとう!
BungleBonce 2013年

必要ありません。あなたがまだそれをプログラミングしている間にそれを実装するほうが良いです。とにかく、isJumpingの理由はコードを整理するためであり、プレーヤーがジャンプしている間だけ重力を追加することで問題を防ぎます。私がお勧めしたことをしてください。この方法は、ほとんどの人が使用しています。Minecraftでも使用しています!これが重力を効果的にシミュレートする方法です!:)
LiquidFeline 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.