本文主要討論,在Unity中使用物理引擎Physics2D的三個方面的內容:
- 如何讓Rigidbody2D物理模擬看起來更加的順滑。
- 介紹幾個造成運動卡頓的原因和解決方法。
- 針對Rigidbody2D的重心旋轉模擬。
讓運動更加順滑
最直接的方法,就是設置Rigidbody2D的Interpolate屬性,面板屬性提供了下拉選項,有None,Interpolate,Extrapolate。官方文檔介紹的很清晰,這是物理模擬在Update過程中的運動插值計算,用來解決運動生澀(卡頓)的問題。
- None 不進行平滑插值。
- Interpolate 運動插值基於前幾幀的位置。在一個相對平穩的運動環境裏是有很好效果的。但如果是顛簸,崎嶇的運動環境,前幾幀的插值並不能很好的反應未來的趨勢。
- Extrapolate 運動插值基於後幾幀的預測位置。根據物理公式和地形預測,可以很好的估算出未來幾幀的情況,但遇到突然的碰撞或外力影響,就無法得到很好的效果。
經過測試發現,如果Rigidbody2D的所有的父節點是固定不動的(沒有手動控制),Interpolate 和 Extrapolate 一樣順滑,並且要比None來的流暢。如果Rigidbody2D的父節點有運動(手動控制),Interpolate會有明顯的卡頓,Extrapolate 和 None 差不多,但是依然是不流暢的。
卡頓問題
拋開性能問題,我遇到的卡頓來自於兩個原因。第一,就是上面所說的Rigidbody2D的父節點發生了運動,造成了物理運動有明顯的生澀感。第二,就是手動設置了Rigidbody2D的transform屬性,如Position,Rotation,Scale等。
其實,這兩個問題是同一個原因,就是不要手動設置和Rigidbody2D相關的transform屬性,包括父類鏈的所有節點。物理引擎會接管一切運動模擬,引擎會有自己的算法預測,手動設置會造成衝突。
經過測試發現,哪怕使用了Rigidbody2D的Constraints約束,Freeze Position 或 Freeze Rotation 之後,依然不能去手動設置鎖住的屬性,否則依舊會造成卡頓。
那如果遇到了必須要手動設置的需求,應該怎麼辦呢 ? 這時候可以使用Rigidbody2D暴露的接口去控制。
Rigidbody2D.position
Rigidbody2D.rotation
Rigidbody2D.MovePosition
Rigidbody2D.MoveRotation
Rigidbody2D 暴露了兩個屬性和兩個方法,用來設置position和rotation。經過測試發現,設置屬性依然卡頓,但使用Move方法就消除了卡頓的現象。所以,手動改變Rigidbody2D的position和rotation使用其暴露的Move方法是最好的辦法。
重心旋轉
在使用Rigidbody2D的過程中,期望能夠實現,因爲重心在運動中產生自發旋轉的效果,比如弓箭的飛行模擬。
開始使用了centerOfMass,強行把剛體的質心移動到物體邊緣或外部,期望質心能夠在重力作用下,形成旋轉力,結果不盡人意,自由落體的時候作用很小,反而碰撞接觸了產生了很大的效用。
後來,使用了Rigidbody2D的angularVelocity角速度設置,並且Freeze Rotation鎖住了剛體本身的旋轉模擬,讓angularVelocity無干擾的發揮作用。其結果是可以工作,但是效果不夠真實,angularVelocity並沒有衰減而且是一個固定的速率,難道還需要每幀去模擬angularVelocity的變化,太過繁瑣。
最終,使用了每幀去計算rotation的數值,並Freeze Rotation鎖住旋轉,得到了很好的效果。大概實現如下:
private void FixedUpdate()
{
if (this.body2D.bodyType == RigidbodyType2D.Dynamic)
{
var deltaTime = Time.fixedDeltaTime * 0.5f * this.transform.localScale.x > 0 ? 1.0f : -1.0f;
var v2 = this.body2D.velocity;
// s = (v0 + v1) * t / 2
this.body2D.MoveRotation(Mathf.Atan2(v2.y * deltaTime, v2.x * deltaTime) * Mathf.Rad2Deg);
}
}
根據當前剛體的速度,計算一幀的xy方向的運動距離,通過Atan2來計算出旋轉角度,在FixedUpdate中手動設置給剛體,當然要使用MoveRotation來得到流暢的旋轉效果。
「Extrapolate」