U3D動作游戲開發讀書筆記--3.1 物理系統詳解(上)
第三章 物理系統詳解
3.1 物理系統的基本梳理
3.1.1 系統參數設置
了解物理配置:

打開Project Settings設置


- Gravity:重力,常用范圍是60~80
- Queries Hit Backfaces :進行背面查詢,如果需要查詢MeshCollider背面的情況,可開啟
- Layer Collision Matrix :物理相交矩陣,確定多個Layer層級之間的相交關系,不勾選則表示二者Layer層的物體不產生碰撞關系。
3.1.2 Fixed Update 更新頻率
與Update每幀執行一次的輪詢周期函數不同,Fixed Update函數的更新頻率是固定的,按照設置好的時間間隔來執行。

3.1.3 Rigidbody 參數簡介


- Mass :剛體的質量,作用類似真實物理世界中的質量
- Drag:阻尼 (不建議設置為0)
- Angular Drag:角阻尼 旋轉類型的阻尼
- Use Gravity:是否使用重力
- Is Kinematic :(是否為運動的物體)開啟后物體將不受到物理特性的影響
- Interpolate:插值方式
- Interpolate內插值會落后后邊一些,但比外插值平滑。
- Extrapolate外插值會基于速度預測剛體位置,但可能會導致某一幀出現錯誤預測。
- 對于需要物理表現的物體,建議選擇內插值。
- Collision Detection:碰撞檢測方式,
- Discrete 關閉連續碰撞檢測
- Continues 連續的碰撞檢測,對于游戲中快速移動的物體,設置后可以防止穿墻
- 對于次重要的物體,比如一些特效生成物,建議設置為ContinuousDynamic或者Continuous Speculative,以提升性能。
- Constraints:剛體約束,勾選后會凍結某個軸上的移動或者旋轉;

3.1.4 物理材質:
新建一個材質,一般只需要配置兩種物理材質最大摩擦力和最小摩擦力類型即可

3.2 常見的問題
書中介紹了幾個常見的問題,這里我們一起來跟隨作者介紹來了解下。
3.2.1 物理步的理解
Unity3D中的物理更新時序是按照時間來進行的,每一個物理更新稱之為物理步,依賴此的觸發事件有OnTrigger、OnCollision系列和FixedUpdate等

主要理解物理刷新是固定的步進時序,和每幀更新調用的Update函數不同。
因此,若將輸入監測邏輯或者需要每幀監測的邏輯放入物理步進相關函數中判斷則會出錯。
3.2.2 重疊與擠出問題
來看一個擠出問題:
當一個剛體對象A在另一個碰撞器B中時候,會發生擠出現象。
如果B對象也有剛體組件并且質量與A相當,那么會有相互的斥力;
如果B對象沒有剛體組件或者剛體組件質量比A對象大很多,那么A對象便會被擠出。
如果被擠出的對象A在彈出的過程中遇到了其他非剛體碰撞體或者質量較大的剛體碰撞體,會驟停,卡在原地。

問題分析解決思路:
重疊造成的擠出位移不是一幀內就執行完成的,而是分多步完成,首先擠出持續發生,直到完全不發生重疊為止。由于擠出的方向并不能由用戶自定義,所以課程產生朝外擠出的情況,也就是游戲中的穿墻問題。
穿墻問題一般都是由于一些特殊腳本控制瞬移操作造成的,所以要首先保證角色的碰撞檢測為連續的,這樣可以讓剛體驅動的物體位移在高速移動下不會產生穿墻現象。
其次我們可以將一個比較大的場景碰撞拆分成多份,并將一些MeshCollider碰撞勾選Convex轉換為凸包,以保證碰撞檢測的結果正確性。
3.2.3 地面檢測優化
地面檢測(Ground Detection)是 Unity 游戲開發中一項非常基礎且關鍵的技術,尤其在動作、平臺跳躍或角色控制類游戲中。它主要用于判斷游戲對象(尤其是角色)是否與地面接觸,以及獲取接觸面的相關信息。
U3D自帶的角色控制器(CharacterController)組件可以通過isGrounded字段來進行判斷,但不夠靈活。
也可以通過膠囊體碰撞體和地面之間的碰撞體之間的物理碰撞來檢測,但可能受物理引擎更新頻率影響,且在復雜邏輯中處理起來可能不如射線檢測靈活。
另一種方法是在角色的底部發射一身射線去檢測并保證每幀的執行。但是對于較為復雜的地面碰撞,一根攝像并不能很好的完成對地面的檢測。可以使用多根射線投射分方式來進行檢測。
void IsOnGroundUpdate(Transform[] groundPoints,LayerMask laterMask,
float length,out bool isOnFround,out RaycastHit cacheRaycastHit)
{
isOnFround = false;
cacheRaycastHit = new RaycastHit();
foreach (Transform groundPoint in groundPoints)
{
if (Physics.Raycast(groundPoint.position, Vector3.down, out RaycastHit hit, length, laterMask))
{
isOnFround = true;
cacheRaycastHit = hit;
break;
}
}
}

一般設置使用三個檢測點即可。
3.2.4 Dash與瞬移問題的處理
進行瞬移或者重逢類技能時候,需要嚴謹地考慮會產生的物理問題,所以不能隨意地改變坐標來實現需求。
在沖鋒或者瞬移之前可以使用SweepTest函數來對瞬移的目標點做測試,提前預判是否可以沖鋒:
/// <summary>
/// 實現物體的瞬間移動(閃爍)功能,同時避免碰撞穿透
/// </summary>
/// <param name="trans">需要進行瞬移的目標物體的Transform組件</param>
/// <param name="targetPoint">期望瞬移到的目標位置坐標</param>
/// <param name="testTrans">用于碰撞檢測計算的參考Transform,通常是自身或相關物體</param>
void BlinkTo(Transform trans, Vector3 targetPoint, Transform testTrans)
{
// 計算從當前位置到目標位置的向量
var diff = (targetPoint - transform.position);
// 計算移動距離
var length = diff.magnitude;
// 計算標準化的移動方向
var dir = diff.normalized;
// 碰撞信息存儲變量
var hit = default(RaycastHit);
// 獲取需要移動物體的Rigidbody組件
var selfRigidbody = trans.GetComponent<Rigidbody>();
// 使用SweepTest模擬物體沿著移動路徑移動,檢測是否會與其他碰撞體發生碰撞
if (selfRigidbody.SweepTest(dir, out hit, length))
{
// 如果檢測到碰撞,計算碰撞體上距離參考點最近的邊界點
var targetClosestPoint = hit.collider.ClosestPointOnBounds(testTrans.position);
// 計算自身碰撞體上距離目標最近點的最近邊界點
var selfClosestPoint = selfRigidbody.ClosestPointOnBounds(targetClosestPoint);
// 計算從當前位置到自身最近點的偏移量
var offset = selfClosestPoint - transform.position;
// 將物體放置在剛好不發生碰撞穿透的位置
trans.position = targetClosestPoint - offset;
}
else
{
// 沒有碰到目標點 說明可以瞬移 進行瞬移
trans.position = targetPoint;
}
}
對于沖擊這種非一次性閃現的多幀操作,需要考慮是否存在空中的因素,這里以空中沖擊到Dash為例:
IEnumerator DashTo(Transform trans, Vector3 targetPosition)
{
//準備瞬移的對象
var startPos = trans.position;
//此處可以對目標點進行在地面位置的修正
targetPosition = GetGroundPosition(targetPosition);
var waitForFixedUpdate = new WaitForFixedUpdate();
var beginTime = Time.fixedTime;
//按照物理更新時序
for (var duration = 0.15f; Time.fixedTime - beginTime <= duration;)
{
var t = (Time.fixedTime - beginTime) / duration;
t = t * t;
trans.position = Vector3.Lerp(startPos,targetPosition,t);
yield return waitForFixedUpdate;
}
}
要點:


【有了AI助手,分析起代碼很容易,如果不好好思考和使用,真實說不過去!嘿嘿】

浙公網安備 33010602011771號