您可以找到以下圓/矩形碰撞的解釋,但請注意,你需要這種類型的衝突可能沒有必要。例如,如果您的角色有矩形邊界框,則算法會更簡單快捷。即使你正在使用一個圓圈,很可能有一個更簡單的方法足以滿足你的需求。
我雖然有關編寫此代碼,但它的時間太長所以這裏只有一個解釋:
這是你的性格圓的例子運動,與上次(前)和當前位置。牆上的矩形顯示在上方。
下面是相同的移動,虛線表示在區域這招圓掃描。掃描區域是膠囊形狀的。
這將是很難計算這兩個物體的碰撞,所以我們需要以不同的方式做到這一點。如果您查看上一張圖像上的膠囊,您會發現它只是沿着圓的半徑向各個方向延伸的運動線。我們可以將這個「延伸」從運動線移動到牆上的矩形。這樣我們就可以在下面的圖像上得到一個圓角矩形。
移動線將與當且僅當所述膠囊與壁碰撞矩形此擴展(四捨五入)矩形碰撞,因此它們以某種方式等效和可互換的。由於這種碰撞計算仍然不平凡且相對昂貴,因此您可以首先在延長的牆體矩形(此時未舍入)和運動線的邊界矩形之間進行快速碰撞檢查。你可以在下面的圖片上看到這些矩形 - 它們都是虛線的。這是一個快速簡單的計算,而在玩遊戲時,可能不會與特定牆矩形重疊> 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