最も簡単な解決策は、衝突を解決する前に世界のすべてのオブジェクトに対して両方の衝突方向を確認し、2つの結果の「複合衝突」のうち小さい方を解決することです。これは、常に最初にxを解決する代わりに、または常に最初にyを解決するのではなく、可能な限り最小量で解決することを意味します。
コードは次のようになります。
// This loop repeats, until our object has been fully pushed outside of all
// collision objects
while ( StillCollidingWithSomething(object) )
{
float xDistanceToResolve = XDistanceToMoveToResolveCollisions( object );
float yDistanceToResolve = YDistanceToMoveToResolveCollisions( object );
bool xIsColliding = (xDistanceToResolve != 0.f);
// if we aren't colliding on x (not possible for normal solid collision
// shapes, but can happen for unidirectional collision objects, such as
// platforms which can be jumped up through, but support the player from
// above), or if a correction along y would simply require a smaller move
// than one along x, then resolve our collision by moving along y.
if ( !xIsColliding || fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) )
{
object->Move( 0.f, yDistanceToResolve );
}
else // otherwise, resolve the collision by moving along x
{
object->Move( xDistanceToResolve, 0.f );
}
}
大きな修正:他の答えの解説を読んで、私は最終的にこのアプローチが機能しない原因となる無言の仮定に気づいたと思います(そして、すべてではないが人々はこのアプローチで見ました)。詳しく説明するために、これまでに参照した関数が実際に実行することになっていることをより明確に示す、いくつかの擬似コードを示します。
bool StillCollidingWithSomething( MovingObject object )
{
// loop over every collision object in the world. (Implementation detail:
// don't test 'object' against itself!)
for( int i = 0; i < collisionObjectCount; i++ )
{
// if the moving object overlaps any collision object in the world, then
// it's colliding
if ( Overlaps( collisionObject[i], object ) )
return true;
}
return false;
}
float XDistanceToMoveToResolveCollisions( MovingObject object )
{
// check how far we'd have to move left or right to stop colliding with anything
// return whichever move is smaller
float moveOutLeft = FindDistanceToEmptySpaceAlongNegativeX(object->GetPosition());
float moveOutRight = FindDistanceToEmptySpaceAlongX(object->GetPosition());
float bestMoveOut = min( fabs(moveOutLeft), fabs(moveOutRight) );
return minimumMove;
}
float FindDistanceToEmptySpaceAlongX( Vector2D position )
{
Vector2D cursor = position;
bool colliding = true;
// until we stop colliding...
while ( colliding )
{
colliding = false;
// loop over all collision objects...
for( int i = 0; i < collisionObjectCount; i++ )
{
// and if we hit an object...
if ( Overlaps( collisionObject[i], cursor ) )
{
// move outside of the object, and repeat.
cursor.x = collisionObject[i].rightSide;
colliding = true;
// break back to the 'while' loop, to re-test collisions with
// our new cursor position
break;
}
}
}
// return how far we had to move, to reach empty space
return cursor.x - position.x;
}
これは「オブジェクトペアごと」のテストではありません。移動するオブジェクトを世界地図の各タイルに対して個別にテストおよび解決しても機能しません(そのアプローチは確実に機能せず、タイルのサイズが小さくなると壊滅的な方法で失敗します)。代わりに、世界地図内のすべてのオブジェクトに対して移動オブジェクトを同時にテストし、世界地図全体との衝突に基づいて解決します。
これにより、(たとえば)壁内の個々の壁タイルが2つの隣接するタイル間でプレーヤーを上下にバウンドさせないようにします。その結果、プレーヤーはそれらの間に存在しないスペースに閉じ込められます。衝突解決距離は常に、その上に別のソリッドタイルがある単一のタイルの境界までではなく、ワールド内の空のスペースまでずっと計算されます。