【问题标题】:How can I detect and resolve collision in my 2D tilebased game?如何检测和解决基于 2D 瓦片的游戏中的碰撞?
【发布时间】:2014-05-23 03:36:42
【问题描述】:

我正在尝试为我用 SFML 2.1 用 c++ 制作的 2D 超级马里奥兄弟克隆解决冲突。我已经尝试了很多不同的解决方案和想法,但我无法让任何东西正常工作。
我有一个播放器,它目前在它自己的 Update() 中检查碰撞(如下所示)

目前:
1. 我的碰撞检测到碰撞略微偏移。如果您能发现任何明显的错误,我很乐意知道。
2. 我不确定这是否是一个足够好的解决方案,因为我需要检测 4 个方向的碰撞(跳跃、撞到我的头在块上。跌落到块上,以及左/右碰撞)
3. 当我检测到碰撞时,我不知道如何将玩家纠正到正确的方向。

我已经清理了所有不必要的代码并删除了下面不起作用的代码。

void Player::Update(Camera& camera, Level& level, sf::RenderWindow& window)
{
    input.Update(*this); // Read input logic
    physics.update(*this, level, camera, window); // Update physics (movement, gravity)
    drawing.update(*this); // Update animation ( does NOT draw player )

    if(Collision(level, camera, getPosition())) // Check for collision
    {
        CollisionResponse(); // If we had collision, do something about it
    }
}

Collision() 函数接受三个参数:
- 级别:是地图 - 相机:返回地图上显示的图块,在屏幕上 - getPosition(): 只是玩家在窗口中的位置

函数如下所示:

bool Player::Collision(Level& level, const Camera& camera, const sf::Vector2f& position)
{
    // Rectangle surrounding player
    sf::FloatRect playerRectangle = sprite.getGlobalBounds();

    int tileSize = 32;
    Tile* tile;

    // smallBounds is the area on the screen where we check collision.
    // We dont check the whole screen, only tiles that CAN collide with player.
    sf::IntRect smallBounds = sf::IntRect(
        (position.x / tileSize) + camera.GetTileBounds(tileSize).left,
        position.y / tileSize,
        ((position.x + tileSize) / tileSize) + camera.GetTileBounds(tileSize).left,
        (position.y + tileSize) / tileSize);

    // Loop through all tiles
    for (int y = smallBounds.top; y < smallBounds.height; y++)
    {
        for (int x = smallBounds.left; x < smallBounds.width; x++)
        {
            // Get the tile we are drawing
            tile = level.GetTile(x, y);
                if(tile && !tile->GetWalkable())
            {
                sf::FloatRect tileRectangle(tile->GetSprite().getPosition().x,
                                            tile->GetSprite().getPosition().y,
                                            tileSize,
                                            tileSize);
                tileRect = tileRectangle;
                if(Intersect(playerRectangle, tileRectangle))
                {
                                    // Woah, collision detected!
                    return true;
                }
            }
        }
    }
    return false;
}

和 CollisionResponse 函数:

void Player::CollisionResponse(void)
{

}

如果我错过了任何有用的信息,请在下方留言。

地图由不同的图块组成,坐标保存的不是它们实际所在的位置,而是它们在数组中的位置,因此示例地图如下所示:
0,0 0,1 0,2
1,0 1,1 1,2
2,0 2,1 2,2
然后,碰撞函数会检查与玩家周围的瓷砖相关的碰撞。

我尝试过的:
很多不同的碰撞方法可以同时解决 x 和 y 碰撞。
将玩家移回上一个位置的碰撞响应。
碰撞响应将玩家向后移动相交矩形的大小(这在 y 轴上有效,但在 x 轴上无效)。 当我的头感觉不像融化的奶酪时,我会写更多。

【问题讨论】:

  • 我没有阅读所有内容,尤其是代码(c++ 不是我最喜欢的语言^^),但我有几个问题:您想要检测碰撞的字符是什么形状,制成的?他们可以高速移动(1 次更新中超过 1 个图块)吗?
  • 我使用的是矩形(我相信是 AABB),它们的移动速度不够快,无法解决“子弹穿纸”问题。

标签: c++ collision-detection sfml


【解决方案1】:

将播放器移回最后一个已知的良好位置会导致您遇到的行为;玩家将与即将碰撞的物体保持一定距离。举个例子:如果你的角色以 300 像素/秒的速度移动,并且距离墙壁 5 像素,那么下一次碰撞发生在 1/60 秒内。如果您的帧率为 60FPS,则在下一帧中将检测到碰撞,并且角色将被移回距离碰撞发生前他所在的墙壁 5 个像素的位置。当然不受欢迎。

只要您正确计算了数量,您遇到的方法应该可以工作(将播放器向后移动相交矩形的大小)。 Separating Axis theorem 对此很有用(在那篇文章中,具体参见第 7 节“寻找 MTV”)。

您可以尝试另一种不涉及计算的方法:在发生碰撞时,以较小的时间步长(甚至可能是逐个像素)向后移动玩家,直到他停止与墙壁碰撞。然后,您会知道一个更靠近墙壁的位置,可以安全地放置他。效率较低,但更易于实施。

另一种方法是连续碰撞检测。它的性质要复杂一些。你应该可以通过简单地搜索“连续碰撞”来找到它的一些信息,但基本思想是:

  1. 找出下一次碰撞发生的时间和地点。
  2. 如果下一次碰撞将在当前时间步内发生,请将对象移动到发生碰撞的位置。
  3. 如果在当前时间步内没有发生下一次碰撞,只需将对象向前移动其当前移动速度。

【讨论】:

  • 我将尝试实现它们中的每一个。我可能只是学到一些东西。但是在第二种“低效”的方法中,我怎么知道他是从哪个方向被击中的? x,-x,y,-y?此外,第三个选择听起来非常好学,但对于这个游戏来说它可能太先进了。当我取得进展时,我会再次联系。谢谢:D
  • re: 第二种方法,只是向与角色移动方向相反的方向移动。如果他正在移动+x,则将他移回-x。如果他移动+y,移动他-y。如果他移动 -x 和 +y,移动他 +x 和 -y。
【解决方案2】:

对于我的游戏(在带有 Libgdx 的 java 中)我也使用了 AABB 碰撞检测。有两种不同的方法可以检测和处理碰撞:

  • 在移动之前计算新位置。如果有物体不要移动。要在 x 方向进行滑翔运动检查碰撞,如果发生碰撞,请不要移动。如果没有碰撞,检查 y 是否相同。如果发生碰撞,不要向 y 方向移动。仍然没有碰撞检查 x 和 y (你真正的新位置)上的碰撞。如果发生碰撞,则停止两个运动。
  • 移动到新位置。如果在那里发生碰撞,在 x 方向重置 x 运动,如果在 y 方向碰撞重置 y 运动,两个方向的碰撞,都重置。

由于您使用的是 TileBased 地图,因此您不需要使用“交集”测试。
相反,您可以存储角色的 4 个角点,四舍五入到他所在的 Tile(如果每个 Tile 都是一个单位大并且角色脚在 P(0.5;0.5) 上,则需要检查 @ 987654324@ (0;0)).
Player 的位置是他的左下角(通常),所以他的其他 3 个点是 P2(player.position.x.+player.width;player.position.y)P2(player.position.x.+player.width;player.position.y+player.height)P3(player.position.x;player.position.y+player.height),总是向下舍入到 Tiles 的位置。

希望对你有帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-05
    相关资源
    最近更新 更多