您可以找到以下圆/矩形碰撞的解释,但请注意,你需要这种类型的冲突可能没有必要。例如,如果您的角色有矩形边界框,则算法会更简单快捷。即使你正在使用一个圆圈,很可能有一个更简单的方法足以满足你的需求。
我虽然有关编写此代码,但它的时间太长所以这里只有一个解释:
这是你的性格圆的例子运动,与上次(前)和当前位置。墙上的矩形显示在上方。
下面是相同的移动,虚线表示在区域这招圆扫描。扫描区域是胶囊形状的。
这将是很难计算这两个物体的碰撞,所以我们需要以不同的方式做到这一点。如果您查看上一张图像上的胶囊,您会发现它只是沿着圆的半径向各个方向延伸的运动线。我们可以将这个“延伸”从运动线移动到墙上的矩形。这样我们就可以在下面的图像上得到一个圆角矩形。
移动线将与当且仅当所述胶囊与壁碰撞矩形此扩展(四舍五入)矩形碰撞,因此它们以某种方式等效和可互换的。由于这种碰撞计算仍然不平凡且相对昂贵,因此您可以首先在延长的墙体矩形(此时未舍入)和运动线的边界矩形之间进行快速碰撞检查。你可以在下面的图片上看到这些矩形 - 它们都是虚线的。这是一个快速简单的计算,而在玩游戏时,可能不会与特定墙矩形重叠> 99%的时间和碰撞计算将停在此处。
然而,如果存在重叠,则可能是该字符圆圈壁矩形的碰撞,但它并不是一定如稍后将证明。
现在您需要计算移动线本身(不是它的边界框)和延长墙矩形之间的交点。您可能可以找到一种算法,如何在线执行此操作,搜索线/矩形交叉点或线/ aabb交点(aabb =轴对齐边界框)。矩形是轴对齐的,这使得计算更简单。该算法可以为您提供一个或多个交点,因为有可能有两个交点 - 在这种情况下,您可以选择距离该线起点最近的交点。下面是这个相交/碰撞的例子。
当你得到一个交叉点,应该是很容易计算的扩展矩形此交汇所在的哪个部分。你可以在上面的图片中看到这些部分,用红线分开,并用一个或两个字母(左 - 右,右 - 右,下 - 上,左上 - 右上 - 左上等)标记。
如果交叉点位于部件l,r,b或t(单个字母的中间部分),那么就完成了。字符圆与墙矩形之间肯定存在碰撞,并且您知道哪一边。在上面的例子中,它在底部。你应该使用4个变量,叫做isLeftCollision
,isRightCollision
,isBottomCollsion
和isTopCollision
。在这种情况下,您将设置isBottomCollision
为true,而其他3将保持为false。
但是,如果交叉点位于两个字母部分的拐角处,则需要额外的计算来确定字符圆与墙矩形之间是否存在实际碰撞。下图显示了角落中的3个这样的交叉点,但其中只有2个实际上发生了圆形矩形碰撞。
要确定是否有碰撞,你需要找到移动直线原始非扩展墙矩形的最近角落为中心的圆的交点。这个圆的半径等于字符圆的半径。再次,你可以谷歌的线/圆交点算法(甚至可能是libgdx有一个),它并不复杂,不应该很难找到。
bl部分没有线/圆交点(也没有圆/矩形碰撞),br和tr部分有交点/碰撞。
在案例中,您将isRightCollision
,isBottomCollsion
设置为true,并在tr案例中将isRightCollision
和isTopCollision
都设置为true。
您还需要注意一个边缘情况,您可以在下面的图像上看到它。
如果在前步骤的移动而在所述扩展矩形的角端,但内部矩形拐角的半径外侧(没有冲突)就会发生这种情况。
要确定是否属于这种情况,只需检查运动起始点是否位于扩展矩形内。
如果是,在初始矩形重叠测试之后(在延长墙矩形和运动线的边界矩形之间),应该跳过线/矩形交叉测试(因为在这种情况下可能没有任何交点并且仍然是圆形/矩形碰撞),还可以根据运动说明点确定您所在的角落,然后仅检查与该角的圆形的线/圆交点。如果有交叉口,则会出现字符圈/墙矩形碰撞,否则不会。
这一切后,碰撞代码应该很简单:
// 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设备上它可能会慢很多,但我相信还是绰绰有余。
我只在表面上测试过,所以如果你发现任何错误,请告诉我。
如果你使用这个算法,我相信你不需要特殊的处理,因为在这种情况下,你将在行开始时得到交点,碰撞检测程序将继续进行下一步(线圆碰撞)。
如果您使用的是libgdx,您可以使用box2d API为您完成所有这些工作吗? – FuzzyBunnySlippers
随着box2d我需要从开始或我可以使用box2d演员? – Springrbua
你将不得不做一些研究。但是如果没有物理引擎,你会面临其他问题,这会导致你放慢速度。我的建议是做一些阅读,看看这里的一些问题/答案。然后做出决定... – FuzzyBunnySlippers