两个图块之间的Tilemap冲突解决方案

时间:2014-07-21 01:55:14

标签: c++ algorithm collision-detection

我的tilemap与碰撞解决有问题。我有一个球从瓷砖上碰撞出来。碰撞工作正常,除了两个瓷砖之间发生碰撞时。然后我的碰撞分辨率变得很脆弱,球飞向错误的方向。我的球是一个矩形,瓷砖是矩形。

这是一个演示问题的.gif文件:

enter image description here

球应该一次一个地反射出来。

算法的工作原理如下:

  1. 应用移动,然后检查并解决碰撞。
  2. 找出球重叠的瓷砖。
  3. 如果要检查的磁贴不可通过:
    • 找出球与X和Y轴重叠的程度。
    • 通过仅在浅轴上将球移出瓷砖来解决碰撞。 (无论哪个轴穿透最少)。
    • 转到下一个图块。
  4. 如果要检查的图块可以通过,则不执行任何操作。
  5. 我已经读到这种最小位移技术的问题在于,在解决碰撞后,球可能会移动到另一次碰撞。修复应该是,只是第二次运行算法。我试过这个,但没有用。这是我在C ++中的碰撞处理代码:

    void PlayState::handleCollisions() {
    
    // Get ball bounding box.
    sf::FloatRect ballBounds = ball.sprite.getGlobalBounds();
    
    // Find out the nearby tiles.
    int leftTile = (int)floor((float)ballBounds.left / level1.TILE_WIDTH);
    int rightTile = (int)ceil(((float)(ballBounds.left + ballBounds.width) / level1.TILE_WIDTH)) - 1;
    int topTile = (int)floor((float)ballBounds.top / level1.TILE_HEIGHT);
    int bottomTile = (int)ceil(((float)(ballBounds.top + ballBounds.height) / level1.TILE_HEIGHT)) - 1;
    
    // For each potentially colliding tile,
    for(int y = topTile; y <= bottomTile; ++y) {
        for(int x = leftTile; x <= rightTile; ++x) {
    
            // If this tile is collidable,
            TileCollision collision = getCollision(x, y);
            if(collision == Impassable) {
    
                // Determine collision depth (with direction) and magnitude.
                sf::FloatRect tileBounds = getTileBounds(x, y);
                sf::Vector2f depth = Collision::getIntersectionDepth(ballBounds, tileBounds);
    
                if(depth != sf::Vector2f(0, 0)) {
                    float absDepthX = std::abs(depth.x);
                    float absDepthY = std::abs(depth.y);
    
                    // Resolve the collision along the shallow axis.
                    if(absDepthY < absDepthX) {
    
                        // Resolve the collision along the Y axis.
                        ball.sprite.setPosition(ball.sprite.getPosition().x, ball.sprite.getPosition().y + depth.y);
    
                        // Perform further collisions with the new bounds.
                        sf::FloatRect ballBounds = ball.sprite.getGlobalBounds();
    
                        // Y-distance to the tile center.
                        if(distanceY < 0) {
                            std::cout << "Collided from TOP." << std::endl;
                            ball.velocity.y = -ball.velocity.y;
                        }
                        else {
                            std::cout << "Collided from BOTTOM." << std::endl;
                            ball.velocity.y = -ball.velocity.y;
                        }
                    }
                    else {
                        // Resolve the collision along the X axis.
                        ball.sprite.setPosition(ball.sprite.getPosition().x + depth.x, ball.sprite.getPosition().y);
    
                        // Perform further collisions with the new bounds.
                        sf::FloatRect ballBounds = ball.sprite.getGlobalBounds();
    
                        // X-distance to the tile center.
                        if(distanceX < 0) {
                            std::cout << "Collided from LEFT." << std::endl;
                            ball.velocity.x = -ball.velocity.x;
                        }
                        else {
                            std::cout << "Collided from RIGHT." << std::endl;
                            ball.velocity.x = -ball.velocity.x;
                        }
                    }
                }
            }
        }
    }
    }  
    

    我尝试第二次运行算法,如下所示:

    void PlayState::update(sf::Time deltaTime) {
    
        ball.update(deltaTime);
    
        handleCollisions();
        handleCollisions();
    }
    

    我该怎么做才能解决这个问题?

2 个答案:

答案 0 :(得分:1)

我已经从头开始在flash游戏中实现了类似的碰撞检测。我发现将球模型化为单点并将其作为矢量的路径更容易,更准确。为了允许所需的球尺寸 - 半径r填充被添加到框的边缘。

该问题变为检测球的线段(ptFrom..ptTo)与附近区块的交点。如果有多个交叉点,请使用最近的ptFrom。使用线段的反射剩余部分重复碰撞检测,直到不再发生碰撞。

答案 1 :(得分:0)

进行碰撞检测的唯一正确方法是在计算中使用速度矢量。

您不仅要测试离散位置,还应测试它们之间的整线。至少你将能够检测到所有碰撞。 E. g。当您仅测试当前位置(标记为1和2)时,无法检测到下图中的一个

" " "|2
 " " /<- second 'collision' point
" " /|
___/_|
  /^--collision point
 1

您应该至少知道当前和之前的坐标(当前减去速度减去delta t)。因此,您必须计算可能的碰撞点的坐标,找到哪一个更接近开始,然后在碰撞点后重新计算路径。

ADD:如何查找和计算碰撞点。

首先,你必须找到你的球穿过的边缘。对于矩形网格,它并不太难:如果前一个和当前坐标位于不同的行(列)中,则行(列)边缘交叉。

  |     |      |     |      |   1 |     |     | 1
--+-----+--  --+-----+--  --+-----+-- --+-----+--
  |   1 |      |     | 1    |     |     |     |
  | 0   |      |  0  |      |  0  |     | 0   |
--+-----+--  --+-----+--  --+-----+-- --+-----+--
  |     |      |     |      |     |     |     |
none crossed    column        row         both

然后你应该根据碰撞边缘方程(x = xc - 交叉垂直边坐标)求解球轨迹方程(x0,y0 - 前一点,vx,vy - 速度):

x = x0 + t⋅vx    y = y0 + t⋅vy   x = xc

和方程x = xc的垂直墙,找到你需要求解方程的yc = y(y坐标)和tc = t(时间)

t = (xc - x0)/vx   y = y0 +t⋅vy (time and y coord of collision)

对于水平边缘,同样适用于x和y交换。

当交叉x和y两条边时,选择一条早先交叉的(具有较少的t)

           |  1
           | /
           |/                   Here, point a is a row collision
          b*<---                            b is a column collision
          /|                     t[a] should be less than t[b]
        a/ |                       because is closer to trajectory start
   -----*--+-------- xc[row]
       /   |
      0    |
           |
          yc[col]

当然,当你计算一次碰撞并更新坐标和速度时,你应该这样做 重新计算碰撞,因为可能有更多的碰撞:

 ----*--+
    / \ |
   0   \|
        *
       /|
      1