2014-01-17 50 views
8

我花了好几个小时寻找解决方案:我正在开发一个有libgdx的自上而下的游戏(可能它关系到我使用的是什么引擎)。现在我必须实现我的角色(圆圈)和墙(矩形)之间的碰撞检测。如果可能滑动,我希望角色在碰撞时沿着墙壁滑动。 让我解释一下:libgdx中Circle-Rectangle碰撞端检测

  • 如果我移动45度,直到我可以用下来, 左或墙角发生碰撞。
  • 如果我与左边碰撞,我想停止x移动并只移动。如果我离开墙,那么我想继续前进。同样 与倒边(停止Y移动)
  • 如果我和我想停止运动角碰撞(滑动是不可能的)。

我在做的实际上是检查矩形的左边线是否与我的圆相交。然后我检查墙的左边线和我的圆以及墙的底线和我的圆的交点。取决于哪个交点occuret我设置回我的圆的x/y的possition并设置X/Y速度为0的问题是,大多数时间不是一个碰撞 BT的重叠 occures。所以底部检查返回true,即使实际上这个圆圈只会与右边碰撞。在这种情况下,两个相交测试都会返回true,并且我会重置这两个速度,如角落碰撞。 我该如何解决这个问题?还有更好的方法来检测碰撞和碰撞面或角落吗? 我不需要确切的碰撞点,只是矩形的一侧。

编辑: 我不得不说,rects不是旋转只是平行于X轴。

+0

如果您使用的是libgdx,您可以使用box2d API为您完成所有这些工作吗? – FuzzyBunnySlippers

+0

随着box2d我需要从开始或我可以使用box2d演员? – Springrbua

+0

你将不得不做一些研究。但是如果没有物理引擎,你会面临其他问题,这会导致你放慢速度。我的建议是做一些阅读,看看这里的一些问题/答案。然后做出决定... – FuzzyBunnySlippers

回答

18

您可以找到以下圆/矩形碰撞的解释,但请注意,你需要这种类型的冲突可能没有必要。例如,如果您的角色有矩形边界框,则算法会更简单快捷。即使你正在使用一个圆圈,很可能有一个更简单的方法足以满足你的需求。

我虽然有关编写此代码,但它的时间太长所以这里只有一个解释:

这是你的性格圆的例子运动,与上次(前)和当前位置。墙上的矩形显示在上方。


enter image description here


下面是相同的移动,虚线表示在区域这招圆扫描。扫描区域是胶囊形状的。


enter image description here

这将是很难计算这两个物体的碰撞,所以我们需要以不同的方式做到这一点。如果您查看上一张图像上的胶囊,您会发现它只是沿着圆的半径向各个方向延伸的运动线。我们可以将这个“延伸”从运动线移动到墙上的矩形。这样我们就可以在下面的图像上得到一个圆角矩形。


enter image description here

移动线将与当且仅当所述胶囊与壁碰撞矩形此扩展(四舍五入)矩形碰撞,因此它们以某种方式等效和可互换的。由于这种碰撞计算仍然不平凡且相对昂贵,因此您可以首先在延长的墙体矩形(此时未舍入)和运动线的边界矩形之间进行快速碰撞检查。你可以在下面的图片上看到这些矩形 - 它们都是虚线的。这是一个快速简单的计算,而在玩游戏时,可能不会与特定墙矩形重叠> 99%的时间和碰撞计算将停在此处。


enter image description here

然而,如果存在重叠,则可能是该字符圆圈壁矩形的碰撞,但它并不是一定如稍后将证明。

现在您需要计算移动线本身(不是它的边界框)和延长墙矩形之间的交点。您可能可以找到一种算法,如何在线执行此操作,搜索线/矩形交叉点或线/ aabb交点(aabb =轴对齐边界框)。矩形是轴对齐的,这使得计算更简单。该算法可以为您提供一个或多个交点,因为有可能有两个交点 - 在这种情况下,您可以选择距离该线起点最近的交点。下面是这个相交/碰撞的例子。


enter image description here

当你得到一个交叉点,应该是很容易计算的扩展矩形此交汇所在的哪个部分。你可以在上面的图片中看到这些部分,用红线分开,并用一个或两个字母(左 - 右,右 - 右,下 - 上,左上 - 右上 - 左上等)标记。
如果交叉点位于部件l,r,b或t(单个字母的中间部分),那么就完成了。字符圆与墙矩形之间肯定存在碰撞,并且您知道哪一边。在上面的例子中,它在底部。你应该使用4个变量,叫做isLeftCollision,isRightCollision,isBottomCollsionisTopCollision。在这种情况下,您将设置isBottomCollision为true,而其他3将保持为false。

但是,如果交叉点位于两个字母部分的拐角处,则需要额外的计算来确定字符圆与墙矩形之间是否存在实际碰撞。下图显示了角落中的3个这样的交叉点,但其中只有2个实际上发生了圆形矩形碰撞。


enter image description here

要确定是否有碰撞,你需要找到移动直线原始非扩展墙矩形的最近角落为中心的圆的交点。这个圆的半径等于字符圆的半径。再次,你可以谷歌的线/圆交点算法(甚至可能是libgdx有一个),它并不复杂,不应该很难找到。
bl部分没有线/圆交点(也没有圆/矩形碰撞),br和tr部分有交点/碰撞。
在案例中,您将isRightCollision,isBottomCollsion设置为true,并在tr案例中将isRightCollisionisTopCollision都设置为true。

您还需要注意一个边缘情况,您可以在下面的图像上看到它。


enter image description here

如果在前步骤的移动而在所述扩展矩形的角端,但内部矩形拐角的半径外侧(没有冲突)就会发生这种情况。
要确定是否属于这种情况,只需检查运动起始点是否位于扩展矩形内。
如果是,在初始矩形重叠测试之后(在延长墙矩形和运动线的边界矩形之间),应该跳过线/矩形交叉测试(因为在这种情况下可能没有任何交点并且仍然是圆形/矩形碰撞),还可以根据运动说明点确定您所在的角落,然后仅检查与该角的圆形的线/圆交点。如果有交叉口,则会出现字符圈/墙矩形碰撞,否则不会。

这一切后,碰撞代码应该很简单:

// x, y - character coordinates 
// r - character circle radius 
// speedX, speedY - character speed 
// intersectionX, intersectionY - intersection coordinates 
// left, right, bottom, top - wall rect positions 

// I strongly recomment using a const "EPSILON" value 
// set it to something like 1e-5 or 1e-4 
// floats can be tricky and you could find yourself on the inside of the wall 
// or something similar if you don't use it :) 

if (isLeftCollision) { 
    x = intersectionX - EPSILON; 
    if (speedX > 0) { 
     speedX = 0; 
    } 
} else if (isRightCollision) { 
    x = intersectionX + EPSILON; 
    if (speedX < 0) { 
     speedX = 0; 
    } 
} 

if (isBottomCollision) { 
    y = intersectionY - EPSILON; 
    if (speedY > 0) { 
     speedY = 0; 
    } 
} else if (isTopCollision) { 
    y = intersectionY + EPSILON; 
    if (speedY < 0) { 
     speedY = 0; 
    } 
} 

[更新]

下面是一个简单的,我相信高效的执行段的AABB相交的,应该是不错的足够你的目的。这是一个稍作修改的Cohen-Sutherland algorithm。你也可以看看this answer的第二部分。

public final class SegmentAabbIntersector { 

    private static final int INSIDE = 0x0000; 
    private static final int LEFT = 0x0001; 
    private static final int RIGHT = 0x0010; 
    private static final int BOTTOM = 0x0100; 
    private static final int TOP = 0x1000; 

    // Cohen–Sutherland clipping algorithm (adjusted for our needs) 
    public static boolean cohenSutherlandIntersection(float x1, float y1, float x2, float y2, Rectangle r, Vector2 intersection) { 

     int regionCode1 = calculateRegionCode(x1, y1, r); 
     int regionCode2 = calculateRegionCode(x2, y2, r); 

     float xMin = r.x; 
     float xMax = r.x + r.width; 
     float yMin = r.y; 
     float yMax = r.y + r.height; 

     while (true) { 
      if (regionCode1 == INSIDE) { 
       intersection.x = x1; 
       intersection.y = y1; 
       return true; 
      } else if ((regionCode1 & regionCode2) != 0) { 
       return false; 
      } else { 
       float x = 0.0f; 
       float y = 0.0f; 

       if ((regionCode1 & TOP) != 0) { 
        x = x1 + (x2 - x1)/(y2 - y1) * (yMax - y1); 
        y = yMax; 
       } else if ((regionCode1 & BOTTOM) != 0) { 
        x = x1 + (x2 - x1)/(y2 - y1) * (yMin - y1); 
        y = yMin; 
       } else if ((regionCode1 & RIGHT) != 0) { 
        y = y1 + (y2 - y1)/(x2 - x1) * (xMax - x1); 
        x = xMax; 
       } else if ((regionCode1 & LEFT) != 0) { 
        y = y1 + (y2 - y1)/(x2 - x1) * (xMin - x1); 
        x = xMin; 
       } 

       x1 = x; 
       y1 = y; 
       regionCode1 = calculateRegionCode(x1, y1, r); 
      } 
     } 
    } 

    private static int calculateRegionCode(double x, double y, Rectangle r) { 
     int code = INSIDE; 

     if (x < r.x) { 
      code |= LEFT; 
     } else if (x > r.x + r.width) { 
      code |= RIGHT; 
     } 

     if (y < r.y) { 
      code |= BOTTOM; 
     } else if (y > r.y + r.height) { 
      code |= TOP; 
     } 

     return code; 
    } 
} 

下面是一些代码示例用法:

public final class Program { 

    public static void main(String[] args) { 

     float radius = 5.0f; 

     float x1 = -10.0f; 
     float y1 = -10.0f; 
     float x2 = 31.0f; 
     float y2 = 13.0f; 

     Rectangle r = new Rectangle(3.0f, 3.0f, 20.0f, 10.0f); 
     Rectangle expandedR = new Rectangle(r.x - radius, r.y - radius, r.width + 2.0f * radius, r.height + 2.0f * radius); 

     Vector2 intersection = new Vector2(); 

     boolean isIntersection = SegmentAabbIntersector.cohenSutherlandIntersection(x1, y1, x2, y2, expandedR, intersection); 
     if (isIntersection) { 
      boolean isLeft = intersection.x < r.x; 
      boolean isRight = intersection.x > r.x + r.width; 
      boolean isBottom = intersection.y < r.y; 
      boolean isTop = intersection.y > r.y + r.height; 

      String message = String.format("Intersection point: %s; isLeft: %b; isRight: %b; isBottom: %b, isTop: %b", 
        intersection, isLeft, isRight, isBottom, isTop); 
      System.out.println(message); 
     } 

     long startTime = System.nanoTime(); 
     int numCalls = 10000000; 
     for (int i = 0; i < numCalls; i++) { 
      SegmentAabbIntersector.cohenSutherlandIntersection(x1, y1, x2, y2, expandedR, intersection); 
     } 
     long endTime = System.nanoTime(); 
     double durationMs = (endTime - startTime)/1e6; 

     System.out.println(String.format("Duration of %d calls: %f ms", numCalls, durationMs)); 
    } 
} 

这是结果我从执行此得到:

Intersection point: [4.26087:-2.0]; isLeft: false; isRight: false; isBottom: true, isTop: false 
Duration of 10000000 calls: 279,932343 ms 

请注意,这是台式机的性能,在一个i5- 2400 CPU。在Android设备上它可能会慢很多,但我相信还是绰绰有余。
我只在表面上测试过,所以如果你发现任何错误,请告诉我。

如果你使用这个算法,我相信你不需要特殊的处理,因为在这种情况下,你将在行开始时得到交点,碰撞检测程序将继续进行下一步(线圆碰撞)。

+0

只是一个问题:我不应该先检查方向?如果xSpeed> 0检查墙左侧的碰撞。另一个问题: 不应该公式是x + r <左边(没有碰撞)?或者我总是错了吗?我的大脑有可能在下面的瞬间:P – Springrbua

+0

@Springrbua我可能误解了你的角色在里面(例如:矩形边代表墙壁一个房间)或矩形外(矩形是一个单一的墙壁和字符与它碰撞)?我认为它是内部的,如果不是我的答案是错误的,我将需要调整它。是否有任何特殊的逻辑被执行时人物撞到墙上(比如你得到一些分数或者失去健康或者其他什么),或者只是他的速度被调整了?如果你回答我这个问题,我可以更好地回答你的问题 – mrzli

+0

Actualy only the Speed is adjusted。添加da法师给墙壁。但是这不应该很难实现。问题是正确的速度调整。而且,我在矩形之外,因为这些矩形是基于我平铺游戏世界的障碍。 – Springrbua

0

我想你通过计算圆心与直线的距离来确定碰撞。 我们可以简化案例,并告诉如果两个距离都相等并且小于半径,圆与圆角碰撞。当然,平等应该有宽容。

更多 - 可能没有必要 - 现实的方法是考虑x,y的速度,并在平等检查中考虑因素。

+0

我想使用这个解决方案。但是我的移动角度可以是0到360度之间的每个角度。所以有可能,两条线具有相同的距离,但该圆只会与一侧而不是角相撞。任何解决方案? – Springrbua

+0

我不明白这怎么可能发生,你能解释一下吗? – Arash

+0

如果我以5度的角度移动,并且我正好在矩形的角下,但仍然没有凝结,距离也是一样的。在接下来的一段时间里,我碰到了下面。 但是在碰撞的瞬间,距离不再一样了,你是对的!xD – Springrbua