前言
不久之前在論壇上有人發貼,使用java編寫的超級馬里奧如何實現碰撞檢測,筆者自己以前
也做過Tank大戰。里面同樣涉及到碰撞檢測,翻翻U盤里的東西還在,什么時候也給共享出來。
這篇文章就簡單游戲中的碰撞檢測做一個簡單的總結。首先需聲明的是這里只是2D的碰撞檢測。
碰撞檢測
對於形狀之間如何來判斷是否是碰撞的這要根據具體的形狀來定。在新手練手的小游戲中,
物體形狀一般可以設定為矩形區域,這類規則圖形。它的碰撞檢測可以通過java API中的
Rectangle類來實現碰撞的檢測。
規則圖形碰撞檢測(Rectangle)
首先我們查看API關於Rectangle類的介紹:它就是指定坐標空間的一個區域,這個區域是通過
指定左上角x、y坐標和去高度和寬度來確定的。
接下來看起具體的方法public Rectangle intersection(Rectangle r),這個方法就是碰撞檢測的
關鍵了,如果兩個Rectangle對象有交集,那么他們就有碰撞了。而每個形狀我們都可以得到他們
的Rectangle對象,這樣圖形的碰撞檢測也就得以實現了。
看下具體實現源碼:
- /* 判斷子彈是否擊中障礙物 */
- public boolean isHit(com.Alex.map.Map map) {
- boolean flag = true;// 代表沒有撞到
- // 分類別的得到所有的障礙物
- List<Stuff> stuffList = new Vector<Stuff>();
- stuffList.addAll(map.getBricks());
- stuffList.addAll(map.getIrons());
- stuffList.addAll(map.getWaters());
- for (int i = 0; i < stuffList.size(); i++) {
- Stuff a = stuffList.get(i);
- Rectangle tankRectangle = new Rectangle(bullet2.getRec());
- Rectangle stuffRectangle = new Rectangle(a.getX(), a.getY(), 20, 20);
- if (stuffRectangle.intersects(tankRectangle)) {
- flag = false;// 撞到了
- break;
- }
- }
- return flag;
- }
上述這個例子就是判斷Tank發出的子彈是否對地圖中的障礙物有碰撞,如果有的話
就做相關的操作(子彈爆炸、障礙物消失)。上述代碼中子彈對象有一個getRec()方法就是
得到子彈圖形的Rectangle對象,具體實現就是根據其坐標和width、height來生成的。
采用此種方法進行碰撞檢測需要注意,對於圖片的實現處理應該盡量的去掉圖標邊角
的空白,不然實際效果可以產生肉眼可辨的誤差。也就是說Rectangle盡量的包住圖形
且Rectangle的區域盡量小。這種碰撞檢測的方法被稱之為多矩形碰撞。
一旦有一個矩形數組中的矩形與另外一個矩形數組的矩形發生碰撞就可認為發生了
多矩形碰撞。其中多圓形碰撞也是同樣的道理,只是包裹的圖形區域是圓形罷了。
不過仔細思考多矩形碰撞同樣會有誤差,雖然這種誤差十分小。
像素級別的碰撞檢測
像素級別的碰撞檢測算得上是最精確的碰撞檢測方法了。
首先遍歷算出一張位圖所有的像素點坐標,然后與另外一張位圖上的所有點坐標進行對比,
一旦有一個像素點的坐標相同,就立刻取出這兩個坐標相同的像素點,通過位運算取出這兩個
像素點的最高位(透明度)進行對比,如果兩個像素點都是非透明像素則判定這兩張位圖發生
碰撞。
介紹了像素碰撞之后可以得到兩個結論:
1、像素碰撞很精確,不論位圖之間是否帶有透明像素,都可以精確判斷;
2、正是因為像素碰撞的這種高精確判定,從而也會造成代碼效率明顯降低!
假設兩張100×100 大小的位圖利用像素級檢測碰撞,僅是遍歷兩張位圖的像素點就要循環
100×100×2=20000 句邏輯代碼;況且還要對篩選出來的相同坐標的像素點進行遍歷對比其
透明值!這種效率可想而知!
當然,這里的像素碰撞只是大致提供一種思路,肯定還可以進行代碼優化;但是不論再優的
代碼,使用像素級進行碰撞檢測終會導致整個程序的運行效率大大降低。因此像素級別的碰
撞檢測在游戲開發中是盡量避免使用的!
規則圖形碰撞檢測2
對於子彈和障礙物的碰撞檢測,采用上述第一種方法就可以簡單的實現了,不過子彈是圓形的
有沒有更加精確的碰撞檢測方法呢?也就是實現圓形和矩形的碰撞檢測嘛。
這里我們需要簡單的運行下幾何數學的知識,給個簡單的圖就會明白了。
小圓有個運動軌跡,軌跡的線如果和他對着的正方形的相對某一象限的邊有焦點,那么
就能碰撞,邊就是那一個象限的邊(還要把圓半徑算進去),具體代碼就不實現了,讀者可
自己嘗試着去實現。
不規則圖形碰撞檢測
這里直接轉載一篇文章:
http://www.cnblogs.com/Kurodo/archive/2012/08/08/2628688.html
兩個物體之間沒有發生碰撞,這個軸線叫做Separating Axis(紅色軸線)。
對於2D來說,紅色線就是垂直與多邊形邊的軸。
因此,如果我們要檢查兩多邊形是否碰撞,就去檢查兩多邊形在每個所有可能的軸上的投影
是否重疊。
- /// 檢測2個矩形是否發生碰撞
- /// </summary>
- /// <returns></returns>
- public static bool IsIntersect (Vector2[] A, Vector2[] B)
- {
- Vector2 AX, AY, BX, BY;
- AX = new Vector2();
- AY = new Vector2();
- BX = new Vector2();
- BY = new Vector2();
- AX.X = A[0].X - A[1].X;
- AX.Y = A[0].Y - A[1].Y;
- AY.X = A[0].X - A[3].X;
- AY.Y = A[0].Y - A[3].Y;
- BX.X = B[0].X - B[1].X;
- BX.Y = B[0].Y - B[1].Y;
- BY.X = B[0].X - B[3].X;
- BY.Y = B[0].Y - B[3].Y;
- //對於AX上:
- if (Tmp(AX, A, B)) return false;
- if (Tmp(AY, A, B)) return false;
- if (Tmp(BX, A, B)) return false;
- if (Tmp(BY, A, B)) return false;
- return true;
- }
- private static bool Tmp(Vector2 IS,Vector2[] A,Vector2[] B)
- {
- float[] v = new float[4];
- for (int i = 0; i < 4; i++)
- {
- float tmp = (IS.X * A[i].X + IS.Y * A[i].Y) / (IS.X * IS.X + IS.Y * IS.Y);
- v[i] = tmp * IS.X * IS.X + tmp * IS.Y * IS.Y;
- }
- float[] vv = new float[4];
- for (int i = 0; i < 4; i++)
- {
- float tmp = (IS.X * B[i].X + IS.Y * B[i].Y) / (IS.X * IS.X + IS.Y * IS.Y);
- vv[i] = tmp * IS.X * IS.X + tmp * IS.Y * IS.Y;
- }
- if (Math.Max(Math.Max(v[0], v[1]),Math.Max(v[2],v[3])) >Math.Min(Math.Min(vv[0],vv[1]),Math.Min(vv[2],vv[3])) && Math.Min(Math.Min(v[0],v[1]),Math.Min(v[2],v[3])) < Math.Max(Math.Max(vv[0],vv[1]),Math.Max(vv[2],vv[3]))) {
- return false;
- }//表示暫時不知道是否碰撞
- else return true;//表示知道未碰撞
- }