2016年7月11日 星期一

物理系統最佳實踐 - Best Practices系列

作者:Ricardo Aguiar 原文

本文是Unity官方Best Practices系列文章之一,說明在遊戲中使用物理時的最佳實踐,並透過一些案例來說明為何要這麼做。

層(Layer)與碰撞矩陣(Collision Matrix)


所有的遊戲物件在建立時如果沒有設定層,預設會放在Default層,這代表可以和任何其他物件發生碰撞反應,這樣會導致效能不太好。我們應該為每一層設定好能夠發生碰撞的物件。因此我們得幫不同類型的物件設定不同的層。每新增加一個層級,碰撞矩陣裡就會自動加一行和一列,可以用來定義不同層之間的互動關係。

在預設情況下,新加的層在碰撞矩陣中被設定為可以與其它層發生碰撞,所以開發者需要手動設定層和層之間的互動關係。正確設定好層和碰撞矩陣,可以避免很多不必要的碰撞和碰撞檢查。為了展示我們建立了一個Demo,在一個盒子容器中實例化產生了2000個物件(1000個紅色,1000個綠色)。物體只會與相同顏色的物體以及牆壁產生互動。

其中一個測試是把所有物體的層都設為Default,然後從碰撞監聽(Collision listener)比對不同遊戲物件的標籤(Tag)來判斷是否進行互動。而在另一個測試每種顏色的物體都分為單獨的層,然後設定碰撞矩陣來定義層和層之間的互動關係。這樣就不需要用程式來比對了,碰撞只會發生在正確的層上。


圖1:碰撞矩陣配置

下面是Demo執行時的抓圖。用一個簡單的管理機制來統計所有的碰撞次數,並於5秒後自動暫停。結果相當的難以置信,使用第一種方法把所有物件都放在Default層會產生大量不必要的碰撞。

圖2:計時五秒所產生的碰撞次數(左:沒有設定矩陣,右:有設定矩陣)

我還用了Profiler擷取Physics區塊中的資料。

圖3:相同層和不同層的物理效能在Profiler裡的比較

正如在Profiler裡所觀察到資料情況,兩個測試對於物理計算上的CPU消耗截然不同。左邊使用相同層平均消耗約27.7毫秒,右邊不同層平均消耗約17.6毫秒。 



射線(Raycast)


射線是物理引擎中一個非常有用的工具。它可以向特定方向發射一條特定長度的射線,透過這個射線就能知道它是否擊中物體。然而,這樣的功能會產生很大的效能消耗。它的效能會受到射線的長度和碰撞體的類型很大的影響。

以下列出一些要點協助你更好的使用。
  • 使用最少的射線來完成任務。
  • 按需設定射線長度,不要太長。射線越長,需要檢查的碰撞物體就越多。
  • 不要在FixedUpdate()函數中使用射線,有時即使是在Update()函數中,射線也會導致過多的消耗。
  • 注意使用碰撞體的類型,射線與網格碰撞體(Mesh Collider)計算的效能消耗是相當大的。
    - 比較好的解決方法是使用多個幾何碰撞體來組合出物體的形狀。可以將父物件Rigidbody下所有的子碰撞體作為混合一起使用。
    - 如果非得使用網格碰撞體,至少將它設定為Convex打勾。
  • 為了確保射線會射到哪些物體,應在Raycast函式中設定層遮罩(Layer Mask)。
    - 官方文件有詳細的說明,要注意的是在Raycast函式指定的是bitmask而非不是layer ID。
    - 所以
    如果你希望射線到一個物件,它的位在layer ID = 10,在Raycast函式中應設定參數為1 << 10(將' 1 '左移10位)而非直接設定10。
    - 如果你希望射線擊中除了layer 10以外的所有物件,可以使用位元運算子(~)對bitmask反轉每一位元。

下面的例子顯示射線只與綠色盒子發生碰撞反應。

圖4:簡單的射線投射場景

透過射線的數量和長度來驗證之前提到的資料分析。可以從下圖中發現,射線的數量和長度對效能影響極大。

圖5:射線數量對效能的影響


圖6:射線長度對效能的影響

下面展示使用網格碰撞體代替幾何碰撞體的效果。

圖7:網格碰撞體場景(每個碰撞體有110個頂點) 


圖8:幾何碰撞體和網格碰撞體在Profiler中的情況

正如Profiler結果所示,射線與網格碰撞體的碰撞檢測在每一幀負擔都很重。


2D物理 VS 3D物理


Unity有2D和3D的物理引擎模式,為專案選擇最適合的物理引擎,如果只開發2D遊戲或2.5D遊戲(2D遊戲看起來有3D效果),用3D物理引擎就太浪費了。這樣會為專案帶來許多不必要的CPU消耗。作者推薦可以使用下面文章中提到的方法來檢查使用不同物理引擎所帶來的消耗:



剛體(Rigidbody)


剛體是為物件提供物理互動必需的元件。即使將碰撞體作為觸發器(Trigger)使用,也必需為物件加上剛體元件,好讓OnTrigger 事件能運作。沒有剛體元件的物件會被視為靜態碰撞體。這點非常重要,因為嘗試移動靜態碰撞體會帶來很大的效能消耗,它會迫使物理引擎重新計算整個物理世界的資料。還好當靜態碰撞體被移動時,Profiler會在CPU分析器上加一個警告標籤提示。為了展示移動靜態碰撞體所帶來的影響,我把之前第一個Demo中所有移動物件的Rigidbody元件移除了,然後透過Profiler分析器重新獲取資料。 


圖9:移動靜態碰撞體的警告(紅框)

如圖所示,總計產成了2000個警告,每次移動遊戲物件都會產生警告。CPU在物理計算上所耗費的平均時間約從17.6毫秒增加到35.85毫秒,這個消耗很驚人。因此確保當一個物件會被移動時,務必加上剛體元件。如果想直接控制物體的移動,只需簡單的勾選Rigidbody元件的的Is Kinematic屬性即可。


Fixed Timestep


調整Time Manager中的 Fixed Timestep值,會直接影響FixedUpdate()函數 和物理引擎之間的更新率。調的好可以在物理系統的精確度和CPU消耗之間達到一個平衡。 


總結


本文所討論的內容在設定上或製作上都不是很困難,而且會幫助你的專案帶來顯著的效能提升,因為幾乎所有專案都會用到物理引擎,即時只是碰撞檢測也會有所區別。


論壇作家 Ricardo Aguiar

Ricardo Aguiar是來自葡萄牙Azore群島的一個開發者,在遊戲行業擁有超過7年的專業經驗。他從完成碩士論文(使用跟蹤和視覺化設備開發遊戲)開始了遊戲開發之旅。隨後加入Seed Studios,致力於開發NDS,iOS然後在PS3上開發第一款葡萄牙語遊戲Under Siege。之後加入X-Team工作,並使用Unity引擎開發手游。

沒有留言:

張貼留言

著作人