文章目錄
- 前言
- 一、AABB(軸對齊包圍盒)
- 1、基本概念
- 2、數學表示
- 3、Unity中的實現
- 4、實際應用示例
- 二、OBB(有向包圍盒)
- 1、Physics.ComputePenetration (Unity 物理引擎)
- 1.1 基本概念
- 1.2 Unity中的實現
- 1.3 實際應用示例
- 2、OBB (SAT) 手動實現
- 2.1 基本概念
- 2.2 數學表示
- 2.3 Unity中的實現
- 2.4 實際應用示例
- 3、如何選擇?
- 三、選擇建議:
- 專欄推薦
- 完結
前言
在Unity游戲開發中,碰撞檢測是至關重要的功能。為了優化性能,開發者通常使用包圍盒(Bounding Box)技術作為初步的碰撞檢測手段。本文將深入探討兩種核心包圍盒技術:AABB(軸對齊包圍盒)和OBB(有向包圍盒)。
一、AABB(軸對齊包圍盒)
1、基本概念
AABB(Axis-Aligned Bounding Box)是最簡單的包圍盒形式,其特點是:
-
始終與世界坐標系的X/Y/Z軸對齊
-
不隨物體旋轉而改變方向
-
計算簡單,性能高效
2、數學表示
一個AABB可以用兩個點表示:
-
最小點(min):包含X/Y/Z的最小值
-
最大點(max):包含X/Y/Z的最大值
或者用中心點和半尺寸表示:
-
中心點(center)
-
半尺寸(extents):從中心到各邊界的距離
3、Unity中的實現
Unity內置了Bounds結構體來處理AABB:
// 創建AABB
Bounds bounds = new Bounds(center, size);// 檢測AABB相交
bool isIntersecting = bounds.Intersects(otherBounds);
4、實際應用示例
// 獲取預制體的AABB包圍盒
Bounds GetPrefabBounds(GameObject prefab)
{// 優先使用渲染器包圍盒Renderer renderer = prefab.GetComponentInChildren<Renderer>();if (renderer != null) return renderer.bounds;// 其次嘗試碰撞器Collider collider = prefab.GetComponentInChildren<Collider>();if (collider != null) return collider.bounds;// 默認返回單位大小return new Bounds(Vector3.zero, Vector3.one);
}/// <summary>
/// AABB碰撞檢測,檢查新物體是否與已放置物體發生重疊
/// </summary>
/// <param name="position">新物體的中心位置</param>
/// <param name="halfExtents">新物體的半尺寸(從中心到各邊界的距離)</param>
/// <param name="existingBounds">已放置物體的包圍盒列表</param>
/// <returns>true表示有重疊,false表示無重疊</returns>
bool CheckAABBOverlap(Vector3 position, Vector3 halfExtents, List<Bounds> existingBounds)
{// 1. 創建新物體的包圍盒// Bounds構造函數需要中心點和完整尺寸,所以將halfExtents乘以2Bounds newBounds = new Bounds(position, halfExtents * 2);// 2. 遍歷所有已放置物體的包圍盒foreach (var bounds in existingBounds){// 3. 檢查新物體與當前已放置物體是否相交if (bounds.Intersects(newBounds)){// 4. 如果相交則立即返回true(發生重疊)return true;}}// 5. 遍歷結束未發現重疊,返回falsereturn false;
}
調用
// 存儲已放置物體的位置和大小(用于碰撞檢測)
List<Bounds> placedBounds = new List<Bounds>();
//預制體
public GameObiect prefab;Vector3 position = 預制體準備放置的位置;
// 獲取預制體大小
Bounds bounds = GetPrefabBounds(prefab);//獲取包圍盒(Bounds)的半尺寸
halfExtents = bounds.extents;// 檢查是否與其他物體重疊
if (CheckOverlap(position, halfExtents, placedBounds)) return;// 實例化預制體
GameObject propInstance = Instantiate(prefab, position, Quaternion.identity);// 添加到已放置列表
placedBounds.Add(new Bounds(position, halfExtents * 2));
它沒有考慮物體的旋轉。當物體旋轉后,使用 Bounds.Intersects() 進行軸對齊包圍盒檢測會導致誤判。
二、OBB(有向包圍盒)
1、Physics.ComputePenetration (Unity 物理引擎)
1.1 基本概念
Physics.ComputePenetration 是 Unity 物理引擎提供的一個方法,用于精確計算兩個碰撞體之間的穿透情況。它比簡單的 Bounds.Intersects 或 Collider.bounds 檢測更準確,特別是對于旋轉后的物體或非軸對齊的碰撞體。
1.2 Unity中的實現
public static bool Physics.ComputePenetration(Collider colliderA, // 第一個碰撞體Vector3 positionA, // 第一個碰撞體的位置Quaternion rotationA, // 第一個碰撞體的旋轉Collider colliderB, // 第二個碰撞體Vector3 positionB, // 第二個碰撞體的位置Quaternion rotationB, // 第二個碰撞體的旋轉out Vector3 direction, // 穿透方向(從A指向B)out float distance // 穿透深度
);
返回值
-
true → 兩個碰撞體發生穿透
-
false → 兩個碰撞體沒有穿透
1.3 實際應用示例
bool CheckOverlap(Vector3 position, Quaternion rotation, Vector3 size, List<GameObject> placedObjects)
{// 創建一個臨時 BoxCollider 用于檢測GameObject tempObj = new GameObject("TempCollider");BoxCollider testCollider = tempObj.AddComponent<BoxCollider>();testCollider.size = size; // 注意:size 是完整尺寸(不是 halfExtents)// 檢查與所有已放置物體的碰撞foreach (GameObject obj in placedObjects){Collider[] objColliders = obj.GetComponentsInChildren<Collider>();foreach (Collider col in objColliders){Vector3 dir;float dist;if (Physics.ComputePenetration(testCollider, position, rotation,col, col.transform.position, col.transform.rotation,out dir, out dist)){Destroy(tempObj);return true; // 發生重疊}}}Destroy(tempObj);return false; // 無重疊
}
調用
// 修改為存儲實際物體
List<GameObject> placedObjects = new List<GameObject>();if (CheckOverlap(position, rotation, halfExtents * 2, placedObjects)) return;GameObject propInstance = Instantiate(prefab, position, rotation);// 實例化后添加
placedObjects.Add(propInstance);
2、OBB (SAT) 手動實現
2.1 基本概念
OBB(Oriented Bounding Box)是更精確的包圍盒:
-
可以隨物體旋轉/縮放
-
方向與物體的本地坐標軸一致
-
使用分離軸定理(SAT)進行相交檢測
2.2 數學表示
一個OBB需要存儲:
-
中心點(center)
-
半尺寸(extents)
-
旋轉(rotation)
2.3 Unity中的實現
Unity沒有直接提供OBB結構,但可以自定義實現:
using UnityEngine;/// <summary>
/// 有向包圍盒(Oriented Bounding Box)結構體
/// </summary>
public struct OBB
{public Vector3 center; // 包圍盒中心點public Vector3 extents; // 包圍盒半尺寸(從中心到各邊的距離)public Quaternion rotation; // 包圍盒旋轉/// <summary>/// 檢測兩個OBB是否相交/// </summary>/// <param name="other">另一個OBB</param>/// <returns>true表示相交,false表示不相交</returns>public bool Intersects(OBB other){// 使用分離軸定理(SAT)進行相交檢測return SATIntersection(this, other);}/// <summary>/// 分離軸定理(SAT)實現/// </summary>private static bool SATIntersection(OBB a, OBB b){// 獲取兩個OBB的旋轉矩陣Matrix4x4 aRot = Matrix4x4.Rotate(a.rotation);Matrix4x4 bRot = Matrix4x4.Rotate(b.rotation);// 需要測試的15條分離軸:// 6條來自兩個OBB的局部坐標軸// 9條來自兩兩軸的叉積Vector3[] axes = new Vector3[15];// A的3個局部軸(X/Y/Z)axes[0] = aRot.GetColumn(0).normalized; // A的X軸axes[1] = aRot.GetColumn(1).normalized; // A的Y軸axes[2] = aRot.GetColumn(2).normalized; // A的Z軸// B的3個局部軸(X/Y/Z)axes[3] = bRot.GetColumn(0).normalized; // B的X軸axes[4] = bRot.GetColumn(1).normalized; // B的Y軸axes[5] = bRot.GetColumn(2).normalized; // B的Z軸// 計算A和B各軸的叉積(9條軸)// 這些軸是兩個OBB各邊平面法線的方向axes[6] = Vector3.Cross(axes[0], axes[3]).normalized; // A.X × B.Xaxes[7] = Vector3.Cross(axes[0], axes[4]).normalized; // A.X × B.Yaxes[8] = Vector3.Cross(axes[0], axes[5]).normalized; // A.X × B.Zaxes[9] = Vector3.Cross(axes[1], axes[3]).normalized; // A.Y × B.Xaxes[10] = Vector3.Cross(axes[1], axes[4]).normalized; // A.Y × B.Yaxes[11] = Vector3.Cross(axes[1], axes[5]).normalized; // A.Y × B.Zaxes[12] = Vector3.Cross(axes[2], axes[3]).normalized; // A.Z × B.Xaxes[13] = Vector3.Cross(axes[2], axes[4]).normalized; // A.Z × B.Yaxes[14] = Vector3.Cross(axes[2], axes[5]).normalized; // A.Z × B.Z// 在每條分離軸上做投影測試foreach (var axis in axes){// 忽略長度接近0的無效軸(叉積結果可能是零向量)if (axis.sqrMagnitude < 0.001f) continue;// 如果在當前軸上投影不重疊,則存在分離軸,OBB不相交if (!OverlapOnAxis(a, b, axis))return false;}// 所有軸上都重疊,則OBB相交return true;}/// <summary>/// 檢測兩個OBB在指定軸上的投影是否重疊/// </summary>private static bool OverlapOnAxis(OBB a, OBB b, Vector3 axis){// 計算兩個OBB在當前軸上的投影半徑float aProj = GetProjectionRadius(a, axis);float bProj = GetProjectionRadius(b, axis);// 計算兩個中心點在當前軸上的距離float centerDistance = Mathf.Abs(Vector3.Dot(axis, b.center - a.center));// 如果中心距離小于投影半徑之和,則投影重疊return centerDistance <= (aProj + bProj);}/// <summary>/// 計算OBB在指定軸上的投影半徑/// </summary>private static float GetProjectionRadius(OBB obb, Vector3 axis){// 獲取OBB的旋轉矩陣Matrix4x4 rot = Matrix4x4.Rotate(obb.rotation);// 計算OBB三個軸向的向量(考慮半尺寸)Vector3 xAxis = rot.GetColumn(0) * obb.extents.x;Vector3 yAxis = rot.GetColumn(1) * obb.extents.y;Vector3 zAxis = rot.GetColumn(2) * obb.extents.z;// 投影半徑 = 各軸在測試軸上投影長度的絕對值之和return Mathf.Abs(Vector3.Dot(xAxis, axis))+ Mathf.Abs(Vector3.Dot(yAxis, axis))+ Mathf.Abs(Vector3.Dot(zAxis, axis));}
}
2.4 實際應用示例
/// <summary>
/// 檢查新物體是否與已放置物體發生OBB重疊
/// </summary>
/// <param name="position">新物體的中心位置</param>
/// <param name="halfExtents">新物體的半尺寸</param>
/// <param name="rotation">新物體的旋轉</param>
/// <param name="existingOBBs">已放置物體的OBB列表</param>
/// <returns>true表示有重疊,false表示無重疊</returns>
bool CheckOverlap(Vector3 position, Vector3 halfExtents, Quaternion rotation, List<OBB> existingOBBs)
{// 創建新物體的OBBOBB newOBB = new OBB(){center = position, // 設置中心點extents = halfExtents, // 設置半尺寸rotation = rotation // 設置旋轉};// 遍歷所有已放置物體的OBBforeach (var obb in existingOBBs){// 如果與任一已放置物體相交,返回trueif (newOBB.Intersects(obb))return true;}// 沒有發現重疊return false;
}
調用
// 存儲已放置物體的OBB列表
List<OBB> placedOBBs = new List<OBB>();// 嘗試在指定位置放置物體
if (!CheckOverlap(position, halfExtents, rotation, placedOBBs))
{// 如果沒有重疊,則實例化新物體Instantiate(prop.prefab, position, rotation);// 將新物體的OBB添加到已放置列表placedOBBs.Add(new OBB(){center = position, // 記錄中心位置extents = halfExtents, // 記錄半尺寸rotation = rotation // 記錄旋轉});
}
3、如何選擇?
? 使用 OBB (SAT) 的情況
-
需要檢測 大量簡單形狀(如地牢墻壁、道具擺放)。
-
在 非物理系統 中使用(如自定義碰撞邏輯)。
-
追求 極致性能(如子彈碰撞檢測)。
? 使用 Physics.ComputePenetration 的情況
-
需要檢測 復雜形狀(如 MeshCollider)。
-
需要 穿透信息(如角色控制器解決卡頓)。
-
已在使用 Unity 物理系統(如 Rigidbody 物體)。
三、選擇建議:
-
對靜態物體或簡單碰撞使用AABB
-
對動態旋轉物體使用OBB
-
可采用兩階段檢測:先用AABB快速篩選剔除絕對不相交的物體,再用OBB精確判斷
專欄推薦
地址 |
---|
【unity游戲開發入門到精通——C#篇】 |
【unity游戲開發入門到精通——unity通用篇】 |
【unity游戲開發入門到精通——unity3D篇】 |
【unity游戲開發入門到精通——unity2D篇】 |
【unity實戰】 |
【制作100個Unity游戲】 |
【推薦100個unity插件】 |
【實現100個unity特效】 |
【unity框架/工具集開發】 |
【unity游戲開發——模型篇】 |
【unity游戲開發——InputSystem】 |
【unity游戲開發——Animator動畫】 |
【unity游戲開發——UGUI】 |
【unity游戲開發——聯網篇】 |
【unity游戲開發——優化篇】 |
【unity游戲開發——shader篇】 |
【unity游戲開發——編輯器擴展】 |
【unity游戲開發——熱更新】 |
【unity游戲開發——網絡】 |
完結
好了,我是向宇
,博客地址:https://xiangyu.blog.csdn.net,如果學習過程中遇到任何問題,也歡迎你評論私信找我。
贈人玫瑰,手有余香!如果文章內容對你有所幫助,請不要吝嗇你的點贊評論和關注
,你的每一次支持
都是我不斷創作的最大動力。當然如果你發現了文章中存在錯誤
或者有更好的解決方法
,也歡迎評論私信告訴我哦!