为什么我的碰撞响应不能阻止我的玩家穿过墙壁(SDL2,C ++)?

时间:2018-04-22 17:58:04

标签: c++ collision-detection collision sdl-2 tile

虽然我发现了许多相似的问题,但是没有一个解决方案解决了我的问题。

我一直在用C ++(Visual C ++)和实体组件系统(ECS)试验SDL2。但我无法弄清楚碰撞响应中的错误。

所以这就是:当我遇到像岩石(简单的灰色瓷砖)之类的东西时,我的玩家有时会回到原点。但有时它会直接通过并卡在另一边或者最终结束。

Collision is detected sometimes, but not always.

我只能假设它与帧之间更改的数据有关,所以它并不总是被捕获。但是对于我的生活,我无法理解。

这是我的矩形检测方法:

bool Collision::RectIntersect(const SDL_Rect& a, const SDL_Rect& b, SDL_Rect& intersect)
{
    intersect = { 0, 0, 0, 0 };

    int leftX = std::max(a.x, b.x);
    int rightX = std::min(a.x + a.w, b.x + b.w);
    int topY = std::max(a.y, b.y);
    int bottomY = std::min(a.y + a.h, b.y + b.h);

    if (leftX < rightX && topY < bottomY)
    {
        intersect = { leftX, topY, rightX - leftX, bottomY - topY };
        return true;
    }

    return false;
}

这是我的代码片段,处理我的输入,然后在代码实际移动任何内容之前解决任何碰撞检测:

void InputComponent::handleEvents(SDL_Event* e)
{
    const Uint8 *keyboardState = SDL_GetKeyboardState(NULL);
    if (e != nullptr)
    {
        /*
            keyHeld: array of 4 for each direction (+/- x, +/- y (WASD))
            hold value true, if pressed down, otherwise false
        */
        if (keyboardState[SDL_SCANCODE_A])
        {
            keyHeld[0] = true;
        }
        else
        {
            keyHeld[0] = false;
        }

        if (keyboardState[SDL_SCANCODE_D])
        {
            keyHeld[1] = true;
        }
        else
        {
            keyHeld[1] = false;
        }

        if (keyboardState[SDL_SCANCODE_W])
        {
            keyHeld[2] = true;
        }
        else
        {
            keyHeld[2] = false;
        }

        if (keyboardState[SDL_SCANCODE_S])
        {
            keyHeld[3] = true;
        }
        else
        {
            keyHeld[3] = false;
        }
    }

    /*
        tmpVel: Vector to store the assumed velocity in x- and y-direction
    */
    Vector2D tmpVel(0.0f, 0.0f);
    // left and right (A and D)
    if (keyHeld[0] && !keyHeld[1])  // left
    {
        tmpVel.x = -1.0f;
    }
    else if (!keyHeld[0] && keyHeld[1])  // right
    {
        tmpVel.x = 1.0f;
    }
    else
    {
        tmpVel.x = 0.0f; // left and right cancel each other out
    }

    // up and down (W and S)
    if (keyHeld[2] && !keyHeld[3]) // up
    {
        tmpVel.y = -1.0f;
    }
    else if (!keyHeld[2] && keyHeld[3]) // down
    {
        tmpVel.y = 1.0f;
    }
    else
    {
        tmpVel.y = 0.0f; // up and down cancel each other out
    }

    /*
        check for collision with presumed direction according to tmpVel
    */
    SDL_Rect intersection;

    // get current player position
    SDL_Rect movedPlayer = entity->getComponent<CollisionComponent>().getCollider();

    // add trajectory of theoretical movement
    movedPlayer.x += static_cast<int>(tmpVel.x * vel_->getSpeed());
    movedPlayer.y += static_cast<int>(tmpVel.y * vel_->getSpeed());

    bool hasCollided = false;

    // collect all collidable objects
    for (auto& c : manager_->getGroup(GroupLabel::GR_COLLIDERS))
    {

        // check player against each collidable tile
        //if (SDL_IntersectRect(&movedPlayer, &c->getComponent<CollisionComponent>().getCollider(), &intersection))
        if (Collision::RectIntersect(movedPlayer, c->getComponent<CollisionComponent>().getCollider(), intersection))
        {
            // collision on x-axis
            if (intersection.w > 0)
            {
                // set velocity on x-axis to 0
                vel_->setVelocityX(0.0f);
                // reset player position back according to width of intersected rectangle
                pos_->setPosX(pos_->getPos().x + (static_cast<float>(intersection.w) * (-tmpVel.x)));
            }

            // collision on y-axis
            if (intersection.h > 0)
            {
                // set velocity on y-axis to 0
                vel_->setVelocityY(0.0f);
                // reset player position back according to height of intersected rectangle
                pos_->setPosY(pos_->getPos().y + (static_cast<float>(intersection.h) * (-tmpVel.y)));
            }

            hasCollided = true;
        }
    }

    if (!hasCollided)
    {
        vel_->setVelocity(tmpVel);
    }
}

任何人都可以让我朝着正确的方向前进吗?

2 个答案:

答案 0 :(得分:0)

当玩家的右边缘恰好等于岩石的左边缘时会发生什么?看起来没有检测到碰撞,因为测试是针对(leftX < rightX)的。因此,速度会更新,玩家会被速度移动。 (奇怪的是,您只需更新速度,然后移动播放器,而不是将它们移动到新计算的位置。)如果您将支票更改为(leftX <= rightX),问题是否仍然存在?

答案 1 :(得分:0)

据我所知,碰撞检测有两个问题。首先,您应该在测试(leftX < rightX && topY < bottomY)时测试(leftX <= rightX && topY <= bottomY)。如果您修复此问题,您的代码将在大多数情况下运行。

你所遇到的第二个问题,可能不会立即显现,就是你正在对谨慎的时间点进行碰撞检测。如果您的播放器具有足够大的速度矢量,您可能会遇到这种情况:

  • 更新1:玩家在墙前行驶。 AABB测试显示没有碰撞。
  • 更新2:玩家在墙后面远离它。 AABB测试显示没有碰撞。

您的AABB测试是正确的,但玩家已通过隔离墙。解决这个问题的天真方法是更频繁地测试(更新1.5可能已经显示出碰撞),或限制玩家的速度。这两种方法都需要进行大量的微调,尤其是在处理可以以不同速度和不同厚度的墙壁移动的物体时。

更稳健的方法是考虑测试中的速度。由于您知道AABB的速度,因此您可以沿其速度矢量投影此形状。如果你对两个AABB都这样做,你最终会得到两个细长的形状,你可以相互测试。如果它们重叠,那么您就知道它们的路径交叉并且可能是一个碰撞。

enter image description here

当然,知道可能发生碰撞并没有多大帮助。问题是一个AABB可能移动得非常慢而另一个非常快,所以即使它们都通过相同的空间(它们的细长形状相交),它们也不会同时通过它。

弄清楚它们是否同时通过同一个空间很难,所以我们作弊。如果从B的速度中减去A的速度,然后使用此修改的速度投影A的细长形状,则可以有效地将B视为静止对象仍然得到正确的结果。知道了这一点,你的测试现在“BA的细长形状重叠了吗?”。这只是一个简单的AABB与Ngon问题。

虽然上面的内容会给你一个关于两个移动AABB是否发生碰撞的布尔值,但它不会告诉你何时碰撞,这对计算篮板等事情也很有用。

我非常推荐这本书Real Time Collision Detection by Christer Ericson,这本书几乎可以预订任何有抱负的游戏开发者的碰撞检测。

以下是随本书附带的CD-ROM的代码片段。它测试移动的AABB对抗另一个移动的AABB,并提供首次接触的时间。

// Intersect AABBs ‘a’ and ‘b’ moving with constant velocities va and vb.
// On intersection, return time of first and last contact in tfirst and tlast
int IntersectMovingAABBAABB(AABB a, AABB b, Vector va, Vector vb, float &tfirst, float &tlast)
{
    // Exit early if ‘a’ and ‘b’ initially overlapping
    if (TestAABBAABB(a, b)) {
        tfirst = tlast = 0.0f;
        return 1;
    }

    // Use relative velocity; effectively treating ’a’ as stationary
    Vector v = vb - va;

    // Initialize times of first and last contact
    tfirst = 0.0f;
    tlast = 1.0f;

    // For each axis, determine times of first and last contact, if any
    for (int i = 0; i < 3; i++) {
        if (v[i] < 0.0f) {
            if (b.max[i] < a.min[i]) return 0;
            // Nonintersecting and moving apart
            if (a.max[i] < b.min[i]) tfirst = Max((a.max[i] - b.min[i]) / v[i], tfirst);
            if (b.max[i] > a.min[i]) tlast = Min((a.min[i] - b.max[i]) / v[i], tlast);
        }

        if (v[i] > 0.0f) {
            if (b.min[i] > a.max[i]) return 0;
            // Nonintersecting and moving apart
            if (b.max[i] < a.min[i]) tfirst = Max((a.min[i] - b.max[i]) / v[i], tfirst);
            if (a.max[i] > b.min[i]) tlast = Min((a.max[i] - b.min[i]) / v[i], tlast);
        }

        // No overlap possible if time of first contact occurs after time of last contact
        if (tfirst > tlast) return 0;
    }
    return 1;
}

Elsevier的软件许可协议要求以下归属:

  

“来自Christer Ericson的Real-Time Collision Detection,由Morgan Kaufmann出版社出版,©2005 Elsevier Inc”