2014-01-17 48 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