两个瓦片之间的瓦片图碰撞分辨率
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
【讨论】:
如何计算可能的碰撞点的坐标?以上是关于两个瓦片之间的瓦片图碰撞分辨率的主要内容,如果未能解决你的问题,请参考以下文章