对您而言,最简单的解决方案是在解决任何冲突之前针对世界上的每个对象检查两个碰撞方向,然后解决两个由此产生的“复合碰撞”中较小的一个。这意味着您将以最小的量进行解析,而不是始终先解析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;
}
这不是“每个对象对”测试;它无法通过分别针对世界地图的每个图块测试和解析运动对象而起作用(该方法永远无法可靠地起作用,并且随着图块大小的减小,其灾难性方式将日益失败)。相反,它是同时针对世界地图中的每个对象测试移动的对象,然后根据针对整个世界地图的碰撞进行解析。
这样,您可以确保(例如)一堵墙内的单个墙砖永远不会在两个相邻砖之间使玩家弹跳,从而导致玩家被困在它们之间“不存在”的空间中;碰撞分辨率距离始终是一直计算到世界上的空白,而不仅仅是到单个图块的边界,该图块上可能再有另一个实心图块。