两个瓦片之间的瓦片图碰撞分辨率

Posted

技术标签:

【中文标题】两个瓦片之间的瓦片图碰撞分辨率【英文标题】:Tilemap collision resolution between two tiles 【发布时间】:2014-07-21 01:55:13 【问题描述】:

我的 tilemap 冲突解决有问题。我有一个球撞到瓷砖上。碰撞工作正常,除非碰撞发生在两个瓷砖之间。然后我的碰撞解决方案出现故障,球飞向错误的方向。我的球是长方形的,瓷砖是长方形的。

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

球应该一次一个地从瓷砖上反射出来。

算法是这样工作的:

    应用移动,然后检查并解决冲突。 找出球重叠的瓷砖。 如果正在检查的图块无法通过: 找出球与瓷砖的 X 轴和 Y 轴重叠的程度。 仅在浅轴上将球移出瓷砖来解决碰撞问题。 (哪个轴渗透最少)。 转到下一个图块。 如果正在检查的图块可以通过,则什么也不做。

我已经读到这种最小位移技术的问题在于,在解决碰撞后,球可能会被移动到另一个碰撞中。修复应该是,第二次运行算法。我试过这个,但没有帮助。这是我的 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();

我能做些什么来解决这个问题?

【问题讨论】:

【参考方案1】:

我从头开始在 Flash 游戏中实现了类似的碰撞检测。我发现将球建模为单点并将其路径建模为矢量更容易、更准确。为了获得所需的球大小——在盒子的边缘添加了半径 r 填充。

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

【讨论】:

您好,请问您的碰撞检测方法的代码还有吗,让我看看? 当我在家用电脑上时,我可以为您发布重要信息。其中两个重要功能是检测球矢量与垂直或水平砖边缘之间的交点。如果您想创建一个通用函数来处理所有情况,您可以在游戏中使用有角度的积木。【参考方案2】:

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

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

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

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

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

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

  |     |      |     |      |   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

【讨论】:

如何计算可能的碰撞点的坐标?

以上是关于两个瓦片之间的瓦片图碰撞分辨率的主要内容,如果未能解决你的问题,请参考以下文章

百度地图的瓦片规则参数

百度与谷歌地图瓦片组织方式对比

MAPZONE GIS SDK接入Openlayers3之三——瓦片数据集接入

GDAL库学习笔记:无缝拼接Google卫星图

瓦片切图算法以及并发切图实践(上篇)

瓦片切图算法以及并发切图实践