Unity在3D物理使用的是Nvidia的PhysX,2D物理使用的是開源項目Box2D,雖然3D和2D項目使用了不同的物理引擎,但是Unity在實現上對它們進行了高度抽象,即從Unity引擎配置的更高級別Unity API來看兩個物理引擎解決方案以功能相同的方式運行。
物理和時間
無論哪個物理引擎都是在時間按固定值前進的前提下運行的
固定更新時間步長
物理引擎使用特定的時間值來處理每個時間步長,與渲染上一幀用的時間無關,該時間步長在unity中被稱為Fixed Update TimeStep,默認設置為0.02秒/20毫秒,即每秒進行50次的固定更新和物理引擎更新
由於每台客戶端體系結構的不同以及客戶端之間的延遲關系,如果物理引擎使用可變的時間步長,就很難在兩台不同的計算機上產生一致的碰撞和力的結果
固定更新和物理引擎更新
由上圖可以看出物理引擎內部的具體執行順序,當經過了距離上次固定更新后足夠固定時間步長的時間,就開始該次固定更新和物理引擎更新,執行順序如下:
- 固定更新的處理將調用在場景中所有激活的MonoBehaviour類中定義的FixedUpdate()回調
- 內部動畫更新
- 內部物理更新,並調用任何需要觸發的觸發器和碰撞器回調
- 處理與固定更新相關的協程WaitForFixedUpdate()
當距離上次固定更新后經過不足一個固定時間步長的時間,則會跳過該次固定更新和物理引擎更新
最大允許的時間步長
假設我們在Project Settings中設置固定時間步長為0.01秒/10毫秒,即每秒進行100次固定更新和物理引擎更新
當我們的游戲以60fps運行時,每幀大約0.01666秒/16.67毫秒,由於我們設置的固定時間步長為0.01秒/10毫秒,可以知道此時進行一次固定更新和物理引擎更新大概10毫秒,即此時每幀畫面都調用一次固定更新和物理引擎更新
當我們的游戲以30fps運行時,每幀大約0.03333秒/33.33毫秒,由於我們設置的固定時間步長0.01秒/10毫秒,可以知道此時,進行一次固定更新和物理引擎更新大概10毫秒,即此時每幀畫面要調用三次的固定更新和物理引擎更新
當我們的游戲以20fps運行時,每幀大約0.05秒/50毫秒,由於我們設置的固定時間步長0.01秒/10毫秒,可以知道此時,進行一次固定更新和物理引擎更新大概10毫秒,即此時每幀畫面要調用五次的固定更新和物理引擎更新
當幀率繼續降低時,每一幀需要的固定更新和物理引擎更新次數將會越來越多
因此,我們需要最大允許的時間步長Maximum Allowed Timestep,一般設置為0.3333秒/333.33毫秒,即每幀渲染超過333.33毫秒之后不再進行固定更新和物理引擎更新,來為其他的處理省下時間
一般情況下FixedUpdate()回調會有很多時間來完成工作,並且物理引擎的工作也很少,但是某些游戲中,物理引擎需要在更新期間執行大量的計算,從而造成瓶頸,由於物理引擎需要在完全處理一次更新之前盡早退出時間步長,因此剛體的運動會突然減速或者停止;同時渲染管線在幀結束之前會幾乎沒有時間來生成當前畫面,從而導致畫面停頓
動態碰撞器和靜態碰撞器
動態碰撞器
動態碰撞器指GameObject上包含Collider組件和Rigidbody組件的碰撞器,將Rigidbody組件添加到Collider所附加的對象上時,物理引擎就會將該碰撞器視為帶有包圍物理對象的立體,從而使它會對外部的力(例如重力)以及其他Rigidbody的碰撞作出反應
靜態碰撞器
靜態碰撞器指GameObject只包含Collider組件的碰撞器,這種碰撞器可以作為無影屏障或者其他不能移動的障礙物,動態碰撞器可以碰到靜態碰撞器,但是靜態碰撞器並不會作出反應
物理引擎會為動態碰撞器和靜態碰撞器生成兩個單獨的數據結構,對於所有的靜態碰撞器會進行似於靜態批處理的處理,如果在運行時將新對象加入靜態碰撞器的數據結構,則需要重新生成它們,會導致顯著的CPU峰值,所以在游戲運行時要避免去實例化靜態碰撞器,如果要在游戲中實現一個運動的靜態碰撞器,正確做法應該是為該碰撞器Collider添加一個Rigidbody組件,開啟Rigidbody組件的IsKinematic屬性,IsKinematic標識符用來防止對對象間的碰撞作出反應,就像靜態碰撞器一樣,但是,添加了Rigidbody組件並開啟IsKinemetic屬性的碰撞器則可以通過移動其Transform組件或者施加在其Rigidbody上力的作用下進行移動,此時如果碰到其他動態碰撞器,該碰撞器的運動並不會收到影響,但是其他的動態碰撞器則會對碰撞作出反應
觸發體積
碰撞器組件包含一個IsTrigger屬性,勾選后它們將被視為非物理對象,稱之為觸發體積,當其他碰撞器進入、保持或者離開它們時可以調用相應的物理事件。
-
當一個碰撞器接觸、保持接觸、停止接觸時,會分別調用另一個碰撞器的OnCollisionEnter()、OnCollisionStay()和OnCollisionExit()回調;當接觸的是觸發體積時,會分別調用觸發體積的OnTriggerEnter()、OnTriggerStay()和OnTriggerExit()回調
-
碰撞器的OnCollisionXXX()回調與觸發體積的OnTriggerXXX()的回調,區別是OnCollisionXXX()回調提供一個Collision對象最為回調參數,其中包括精確的碰撞位置、接觸法線等信息,而OnTriggerXXX()類的回調則沒有這些信息,因此,觸發體積不應該用於對碰撞做出反應,而適合用於一些觸發某一功能的場景
碰撞檢測
Unity中的碰撞檢測有三種設置,可以在Rigidbody組件中的Collision Detection屬性進行設置,離散Discrete、連續Continuous、連續動態ContinuousDynamic
- 離散Discrete
根據物體的速度,每個時間步長物體移動一小段距離,物體移動后物理引擎會檢查物體之間是否有重疊部分並對重疊部分進行邊界立體檢查,經過檢查如果它們被視為碰撞,之后就會根據物體的物理屬性以及重疊方式來處理它們的碰撞。離散式碰撞檢測在小對象物體移動速度較快的情況下會有丟失碰撞的風險
- 連續Continuous
連續碰撞檢測和連續動態碰撞檢測是在時間步長的開始位置和結束位置之間檢查是否有重疊碰撞,因為碰撞有可能發生在時間步長的過程中,多以連續的碰撞檢測降低了丟失碰撞的風險,但是也提高了CPU的開銷。連續式碰撞檢測僅在給定碰撞器與靜態碰撞器之間啟用
- 連續動態ContinuousDynamics
連續動態碰撞檢測使碰撞器與所有靜態碰撞器和動態碰撞器之間進行連續碰撞檢測,這種碰撞檢測資源消耗最高
射線投射
射線投射指將射線從一個點投射到另一個點,與路徑中的一個或者多個對象發生碰撞信息
物理性能優化
場景設置
- 盡可能使游戲中的所有物理物體的縮放接近(1, 1, 1),地球表面重力為9.8m/s2,因此Unity世界空間中1個單位等於1米,中默認重力為-9.81,負號意味着會把物體向下拉;
- 保持所有對象在世界空間的位置接近(0, 0, 0),這樣會具有更好的浮點數精度;
- 物理物體的質量保存在剛體組件下的質量屬性中,我們可以自由的選擇使用1.0為基准,然后對其他物體進行縮放
優化碰撞矩陣
物理引擎的碰撞矩陣定義了指定層的對象是否可以與另一個層的對象進行碰撞,使物理引擎可以有效的忽略其他層的對象,減少了每次固定更新需要檢查的邊界體積的數量,有效的減輕物理引擎的工作負載
假如游戲中有Player層對象1個、PlayerMissiles層對象7個、Powerups層對象2個、Enemy層對象10個、EnemyMissiles層對象20個,一共有40個對象,在不設置碰撞矩陣的情況下,每個對象都會與其他層的對象進行碰撞,所以一共會有40 * 39 / 2 = 780 個碰撞對,使用碰撞矩陣之后(按按行對每個層對於其他層的碰撞對,如果在上一行已經處理了當前兩個碰撞對層,則當前行就不用再次勾選了)就可以將碰撞對的數量降低至不到100個,可有效釋放一些CPU周期
REF
文檔
https://docs.unity3d.com/Manual/ExecutionOrder.html
書籍
Unity游戲優化