多物體碰撞檢測策略

當只有兩個物體時,碰撞只可能在 A - B 物體間發生。如果有三個物體,就有三種可能:A – B,B – C,C – A。如果是四個物體就有六種可能,五個物體就有十種可能。

如果物體多達 20 個,就需要分別進行判斷 190 次。這就意味着在我們的 enterFrame 函數中,需要調用 190 次 hitTest 方法或距離計算。

如果使用這種方法,那麼就會多用出必要判斷的兩倍!比如說 20 個物體就執行了 380次 if 語句(20 個影片每個判斷 19 次,20 * 19 = 380)。大家現在知道學習本節內容的重要性了吧。

看一下問題,審視一下平常的做法。假設我們有六個Sprite,分別爲 sprite0,sprite1,sprite2,sprite3,sprite4,sprite5。讓它們運動並執行反彈,我們想要知道它們之間何時會發生碰撞。思考一下,依次獲得每個Sprite的引用,然後再執行循環,再去和其它的Sprite進行比較。下面是一段僞代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
numSprites = 6;
for (i = 0; i < numSprites; i++)
{
    spriteA = sprites[i]; 
    for (j = 0; j < numSprites; j++)
    {
        spriteB = sprites[j]; 
        if (spriteA.hitTestObject(spriteB))
        {
            // 執行代碼
        }
    }
}

六個Sprite執行了 36 次判斷。看上去很合理,對嗎?其實,這段代碼存在着兩大問題。首先,來看第一次循環,變量 i 和 j 都等於 0。因此 spriteA 所持的引用是 sprite0,而 spriteB 的引用也是一樣。嗨,我們原來是在判斷這個影片是否和自己發生碰撞!無語了。所以要在 hitTest 之前確認一下 spriteA != sprieB,或者可以簡單地寫成 i != j。代碼就應該是這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
numSprites = 6;
for (i = 0; i < numSprites; i++)
{
    spriteA = sprites[i];
    for (j = 0; j < numSprites; j++)
    {
        spriteB = sprites[j];
        if (i != j && spriteA.hitTestObject(spriteB))
        {
            // do whatever
        }
    }
}

OK,現在已經排除了六次判斷,判斷次數降到了 30 次,但還是太多。下面列出每次比較的過程:

  • sprite0 與 sprite1, sprite2, sprite3, sprite4, sprite5 進行比較
  • sprite1 與 sprite0, sprite2, sprite3, sprite4, sprite5 進行比較
  • sprite2 與 sprite0, sprite1, sprite3, sprite4, sprite5 進行比較
  • sprite3 與 sprite0, sprite1, sprite2, sprite4, sprite5 進行比較
  • sprite4 與 sprite0, sprite1, sprite2, sprite3, sprite5 進行比較
  • sprite5 與 sprite0, sprite1, sprite2, sprite3, sprite4 進行比較

請看第一次判斷: 用 sprite0 與 sprite1 進行比較。再看第二行: sprite1 與 sprite0 進行比較。它倆是一回事,對吧?如果 sprite0 沒有與 sprite1 碰撞,那麼 sprite1 也肯定不會與 sprite0 碰撞。或者說,如果一個物體與另一個碰撞,那麼另一個也肯定與這個物體發生碰撞。所以可以排除兩次重複判斷。如果刪掉重複判斷,列表內容應該是這樣的:

  • sprite0 與 sprite1, sprite2, sprite3, sprite4, sprite5 進行比較
  • sprite1 與 sprite2, sprite3, sprite4, sprite5 進行比較
  • sprite2 與 sprite3, sprite4, sprite5 進行比較
  • sprite3 與 sprite4, sprite5 進行比較
  • sprite4 與 sprite5 進行比較
  • sprite5 沒有可比較的對象!

我們看到第一輪判斷,用 sprite0 與每個影片進行比較。隨後,再沒有其它影片與 sprite0 進行比較。把 sprite0 放下不管,再用 sprite1 與剩下的影片進行比較。當執行到最後一個影片 sprite5 時,所有的Sprite都已經和它進行過比較了,因此 sprite5 不需要再與任何影片進行比較了。結果,比較次數降到了 15 次,現在大家明白我爲什麼說初始方案通常執行了實際需要的兩倍了吧。

那麼接下來如果寫代碼呢?仍然需要雙重嵌套循環,代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
numSprites = 6
for (i = 0; i < numSprites - 1; i++) 
{
    spriteA = sprites[i];
    for (j = i + 1; j < numSprites; j++)
    {
        spriteB = sprites[j];
        if (spriteA.hitTestObject(spriteB)) 
        
            // do whatever
        }
    }
}

請注意,外層循環執次數比Sprite總數少一次。就像我們在最後的列表中看到的,不需要讓最後一個Sprite與其它Sprite比較,因爲它已經被所有Sprite比較過了。內層循環的索引以外循環索引加一作爲起始。這是因爲上一層的內容已經比較過了,而且不需要和相同的索引進行比較。這樣一來執行的效率達到了 100%。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章