我正在尝试实施碰撞检测系统,它在大多数情况下工作,没有重叠(或最多很少重叠)的角色和墙碰撞。问题是我有一堆角色跟随一个玩家并且碰到它,当有大约15-20个角色都在推动玩家时,它可能导致玩家或其他物体被推到墙上。
我的代码如下工作,首先我更新所有字符,然后检查彼此之间的冲突,然后检查与墙壁的任何字符冲突。我觉得问题是所有角色的最终推动导致一个或多个角色被推到很远的距离,但我不知道如何解决问题。如有必要,下面的代码,如何解决这个问题的详细解释也是足够的。
字符更新/碰撞:
void CharacterManager::updateAll(float elapsedTime)
{
for(std::vector<std::shared_ptr<Character>>::iterator i = _characters.begin(); i != _characters.end(); i++) {
(*i)->update(elapsedTime);
}
collisions();
}
void CharacterManager::collisions()
{
for(std::vector<std::shared_ptr<Character>>::iterator i = _characters.begin(); i != _characters.end(); i++) {
for(std::vector<std::shared_ptr<Character>>::iterator j = _characters.begin(); j != _characters.end(); j++) {
if(i == j) continue;
float xi = (*i)->position().x;
float yi = (*i)->position().y;
float xj = (*j)->position().x;
float yj = (*j)->position().y;
float dx = xi - xj;
float dy = yi - yj;
float distSquared = dx * dx + dy * dy;
float ri = (*i)->xRect().width/2;
float rj = (*j)->xRect().width/2;
if(distSquared < (ri + rj) * (ri + rj)) {
// fix collisions
float angle = atan2f(dy,dx);
float overlap = (ri + rj) - sqrt(distSquared);
if(xi < xj) {
if(yi < yj) {
(*i)->position(xi - cosf(angle) * overlap/2, yi - sinf(angle) * overlap/2);
(*j)->position(xj + cosf(angle) * overlap/2, yj + sinf(angle) * overlap/2);
} else {
(*i)->position(xi - cosf(angle) * overlap/2, yi + sinf(angle) * overlap/2);
(*j)->position(xj + cosf(angle) * overlap/2, yj - sinf(angle) * overlap/2);
}
} else {
if(yi < yj) {
(*i)->position(xi + cosf(angle) * overlap/2, yi - sinf(angle) * overlap/2);
(*j)->position(xj - cosf(angle) * overlap/2, yj + sinf(angle) * overlap/2);
} else {
(*i)->position(xi + cosf(angle) * overlap/2, yi + sinf(angle) * overlap/2);
(*j)->position(xj - cosf(angle) * overlap/2, yj - sinf(angle) * overlap/2);
}
}
// calc new velocities
float vxi = (*i)->velocity().x;
float vyi = (*i)->velocity().y;
float vxj = (*j)->velocity().x;
float vyj = (*j)->velocity().y;
float vx = vxj - vxi;
float vy = vyj - vyi;
float dotProduct = dx * vx + dy * vy;
if(dotProduct >= 0) {
float collisionScale = dotProduct / distSquared;
float xCollision = dx * collisionScale;
float yCollision = dy * collisionScale;
float combinedMass = (*i)->weight() + (*j)->weight();
float collisionWeightA = 2 * (*j)->weight() / combinedMass;
float collisionWeightB = 2 * (*i)->weight() / combinedMass;
(*i)->velocity(vxi + collisionWeightA * xCollision, vyi + collisionWeightA * yCollision);
(*j)->velocity(vxj - collisionWeightB * xCollision, vyj - collisionWeightB * yCollision);
}
}
}
}
}
墙碰撞:
void Stage::characterCrossCollisions(std::shared_ptr<Character> character)
{
for(std::vector<std::shared_ptr<Tile>>::iterator tile = tiles.begin(); tile != tiles.end(); tile++) {
if(!(*tile)->walkable) {
sf::Rect<float> cxr = character->xRect();
sf::Rect<float> cyr = character->yRect();
sf::Rect<float> tr = (*tile)->getRect();
if(!(cxr.left > tr.left + tr.width ||
cxr.left + cxr.width < tr.left ||
cxr.top > tr.top + tr.height ||
cxr.top + cxr.height < tr.top)) {
float ox = 0;
if(character->position().x > (*tile)->position().x) {
ox = cxr.left - (tr.left + tr.width);
}
else {
ox = cxr.left + cxr.width - tr.left;
}
character->position(character->position().x - ox, character->position().y);
}
if(!(cyr.left > tr.left + tr.width ||
cyr.left + cyr.width < tr.left ||
cyr.top > tr.top + tr.height ||
cyr.top + cyr.height < tr.top)) {
float oy = 0;
if(character->position().y > (*tile)->position().y) {
oy = cyr.top - (tr.top + tr.height);
}
else {
oy = cyr.top + cyr.height - tr.top;
}
character->position(character->position().x, character->position().y - oy);
}
}
}
}
答案 0 :(得分:3)
通常,当两个对象相互交叉时,会运行两个对象的碰撞代码。如果两个物体在空间中共享至少一个点,则它们彼此相交。但问题是如果对象相交,则意味着过去发生了碰撞而不是现在发生碰撞。
理想的碰撞代码应计算能量传递,并在物体相互接触时在精确时刻处修改物体的速度。良好的碰撞代码会回滚时间并试图找出碰撞发生的时刻,根据该时刻计算新的速度并滚动时间。然而,这些都很难做到,对于一个简单的电脑游戏来说可能有些过分。
我可以向您推荐的简单而强大的解决方案是:
您还可以使用像这样的约束,对象永远不会与墙相交并通过检查移动字符时新位置是否有效来应用此约束。并且只有在新位置有效时才移动角色。
这个小例子应该举例说明验证。使用MoveTo()
方法使位置仅可更新,并且在MoveTo()
方法内,您可以验证新位置并返回移动是否成功。如果移动不成功,呼叫者可能会想要采取不同的行动。 (将物体移动到接触位置之前,这将是处理碰撞的绝佳机会)
class Character{
bool MoveTo(float x, float y)
{
if (this.isValidPosition(x,y))
{
this.x = x;
this.y = y;
return true;
}
return false;
}
void Update(float deltaTime)
{
float new_x = x + velocity_x*deltaTime;
float new_y = y + velocity_y*deltaTime;
if (!this.MoveTo(new_x, new_y))
{
Console.Write("cannot move " + this + " to the new position, something is already there\n");
}
}
}