本文簡要分析了Unity中各類 射線檢測 的基本原理及用法,及不同檢測手段的性能對比。內容包括:
- Ray 射線
- RaycastHit 光線投射碰撞信息
- Raycast 光線投射
- BoxCast/SphereCast/CapsuleCast 體投射
- OverlapBox/OverlapSphere/OverlapCapsule 相交體
- OverlapBoxNonAlloc/OverlapSphereNonAlloc/OverlapCapsuleNonAlloc 無GC相交體
- 常用射線Debug方法
- GC消耗分析
博客園:Unity - Raycast
項目地址:Raycast - SouthBegonia
Ray 射線
- 含義:官方解釋為一條無窮的線,開始於origin點,朝向direction方向(但是,根據項目驗證來看其默認長度為單位向量,只有對direction進行乘以倍率,才可實現延長射線,而非無窮)
- 用法:
Ray ray = new Ray(transform.position,transform.forward)
:從物體中心創建一條指向前方的射線rayRay camerRay = Camera.main.ScreenPointToRay(Input.mousePosition)
:產生一條從攝像機產生、經過屏幕上光標的射線。當相機為perspective模式,射線在相機梯形視野內發散;若為orthoGraphic,則為垂直與相機面的直線段(見上圖)
RaycastHit 光線投射碰撞信息
- 含義:取得從Raycast函數中得到的碰撞信息(注意不是collider哈,是包含collider信息)
- 關鍵變量:point、collider、rigidbody、transform
檢測方法 - 線型檢測
Physics.Raycast 光線投射
- 功能:在已有一條射線(也可無)的基礎上,使用射線(新建射線)進行一定距離內的定向檢測。可修改射線長度,限制其檢測的Layer層,並且可以得到射線檢測到的碰撞信息。但僅能檢測到第一個被射線碰撞的物體,后面的物體無法被檢測到
- 用法:
Raycast(transform.position, Vector.forward, distance, LayerMask.GetMask("Enemy"))
: 從物體中心點起,朝向Vector3.forward方向發射一條射線,該射線長度為distance,射線可檢測到的層為Enemy層,返回bool類型Raycast(transform.position, Vector.forward, distance, out RaycastHitInformation ,LayerMask.GetMask("Enemy"))
:從物體中心點起,朝向Vector3.forward方向發射一條射線,該射線長度為distance,將碰撞信息反饋到RaycastHitInformation上,射線可檢測到的層為Enemy層,返回bool類型Raycast (MyRay, distance, LayerMash.GetMask("Enemy"))
:從已有的射線MyRay出發,長度延伸至distance,射線可檢測到的層為Enemy層,返回bool類型Raycast (MyRay, out RaycastHitInformation, distance, LayerMask.GetMask("Enemy"))
- 適用場合:配合相機坐標轉換實現各類交互
Physics.RaycastAll 所有光線投射
- 功能:機理用法大致同Raycast,區別在於可檢測射線路徑上的所有物體,返回RaycastHit[] 。其他帶All后綴的方法也同理
- 用法:
RaycastHit[] hits = RaycastAll(Vector3.zero, Vector.forward, distance, LayerMask.GetMask("Enemy"))
RaycastHit[] hits = RaycastAll(MyRay, distance, LayerMash.GetMask("Enemy"))
- 適用場合:穿透性檢測
Physics.Linecast 線段投射
- 功能:建立某兩點之間的射線進行檢測,返回bool類型
- 用法:
Linecast(startPos, endPos, LayerMask.GetMask("Enemy"))
Linecast(startPos, endPos, out RaycastHit, LayerMask.GetMask("Enemy"))
- 適用場合:特定地點局部距離射線檢測
檢測方法 - 體型檢測
Physics.XXXCast 體投射
- BoxCast 立方體投射:
- 功能:檢測范圍是正立方,返回bool。但是該投射用法需要萬分小心,見下
- 用法:
BoxCast(originPos, halfExtents, direction, out RaycastHit, distance, LayerMask.GetMask("Enemy"))
:含義是在originPos點創建半徑halfBoxLength的立方體(Vector3型,代表為正立方體在三個方向上的大小,一般用localScale/2);以朝向direction方向的平面為起始面(另一面舍棄),移動distance距離,期間經過的區域即為檢測區域。(見下補充分析) - 適用場合:檢測目的地是否可抵達,從而判斷可移動性
- SphereCast 球體投射:
- 功能:擴展檢測范圍為球形,返回bool類型。
- 用法:
SphereCast(originPos, radius, direction, out RaycastHit, distance, LayerMask.GetMask("Enemy"))
:含義是在originPos點創建半徑為radius的球體;以朝向direction方向的球面為起始面(另一面舍棄),移動distance距離,期間半球面經過的區域即為檢測區域。那么originPos到originPos+radius內的半球區域呢?答案是舍棄,用官方的話來說,是邊界而不是包圍體 。(立體結構:以左右球球心為軸線,建立半徑為radius、高為distance的圓柱體,左球挖去右半體積,右球添加右半體積)SphereCast (Ray, radius, out RaycastHit, distance, LayerMask.GetMask("Enemy"))
- CapsuleCast 膠囊體投射:
- 功能:檢測范圍是膠囊體,返回bool
- 用法:
Physics.CapsuleCast(pos1, pos2, radius, direction, out RaycastHit, maxDistance, LayerMask.GetMask("Anchor"))
:機理和SphereCast類似,在pos1、pos2兩點創建半徑為0.5f的球體,以此作為膠囊體模型兩端;以朝向direction方向的半膠囊體面為起始面,移動maxDistance距離,期間該面經過的區域即為檢測區域。(注:maxDistance和上面的distance必須非0否則無用) - 項目示例:見下圖,pos1、pos2均為膠囊體兩球坐標,視角右側為forward,移動距離為0.1f,從實驗結果我們不僅可以直觀感受maxDistance的含義,更印證上面所說的 是邊界不是包圍體 ,代碼:
Physics.CapsuleCast(pos1, pos2, 0.5f, Vector3.forward, out RaycastHit, 0.1f, LayerMask.GetMask("Anchor"))
- XXXCastAll 穿透投射:
- 功能:上述三種投射都只返回bool,只能檢測單個物體,但是All方法的可檢測射線上的所有物體,返回 RaycastHit[],產生GC極多
Physics.OverlapXXX 相交體
- OverlapBox 相交盒:
- 功能:檢測與正立方體接觸、重疊、或者處於其內的所有collider
- 用法:
Collider[] hits = OverlapBox(Pos, halfExtents, Quaternion.identity, LayerMask.GetMask("Enemy"))
:以Pos點為中心創建三維半徑halfExtents的正立方體,不對其進行旋轉,檢測層為Enemy - 適用場合:檢測掛載物體范圍內是否存在碰撞,常用方法
- OverlapSphere 相交球:
- 功能:檢測與球體接觸、重疊、或者處於其內的所有collider,即包圍體。但注意,自身collider也會被檢測到(下列Overlap方法都是)
- 用法:
Collider[] hits = Physics.OverlapSphere(Pos, radius, LayerMask.GetMask("Enemy"))
:以Pos為原點,創建半徑為radius的球形,檢測區域為整個球形包圍體(實心),檢測Enemy層上的物體,返回所有碰撞物體的collider而不是RaycastHit(注意:存在於球內部的物體也會被檢測到)
- OverlapCapsule 相交膠囊體:
- 功能:檢測與膠囊體接觸、重疊、或者處於其內的所有collider
- 用法:
Collider[] hits = OverlapCapsule(pos1, pos2, radius, LayerMask.GetMask("Enemy"))
:在pos1、pos2兩點創建半徑為radius的球體,加上中間部分組成膠囊體,檢測Enemy層
Physics.OverlapXXXNonAlloc 無GC相交體
- OverlapBoxNonAlloc 無GC相交盒:
- 功能:實現OverlapBox的所有功能,但是另傳遞進colliders[] ,返回相交物體數量,從而杜絕GC的產生
- 用法:
CollAmount = Physics.OverlapBoxNonAlloc(Pos, halfExtents, colliders, Quaternion.identity, LayerMask.GetMask("Enemy"))
- OverlapSphereNonAlloc 無GC相交球:
- 功能:參考上面
- 用法:
CollAmount = OverlapSphereNonAlloc(Pos, radius, colliders, LayerMask.GetMask("Enemy"))
- OverlapCapsuleNonAlloc 無GC相交膠囊體:
- 功能:參考上面
- 用法:
CollAmount = OverlapCapsuleNonAlloc(pos1, pos2, radius, colliders, LayerMask.GetMask("Enemy"))
Physics.CheckXXX 檢驗體
- CheckBox 檢驗盒:
- 功能:創建檢測盒,檢測是否被碰撞。較比與上面的檢測方法,該類方法特點在於檢驗是否發生了碰撞,而不是取得碰撞體信息,效率最高。注此方法同樣也會檢驗自身collider
- 用法:
IsOverlapAnyCollider = Physics.CheckBox(transform.position, transform.localScale / 2, Quaternion.identity, LayerMask.GetMask("Enemy"))
:在物體中心創建檢驗盒,一定大小,不旋轉,檢測Enemy層,若有檢測到碰撞則返回True
- CheckSphere 檢驗球:
- 功能:參考上面
- 用法:
IsOverlapAnyCollider = Physics.CheckSphere(transform.position, radius, LayerMask.GetMask("Enemy"))
- CheckCapsule 檢驗膠囊體:
- 功能:參考上面
- 用法:
IsOverlapAnyCollider = Physics.CheckCapsule(pos1, pos2,radius, LayerMask.GetMask("Enemy"))
Physics.IgnoreCollision 忽略碰撞
- 功能:屏蔽兩個collider的碰撞,第三個參數為bool
- 用法:
IgnoreCollision (collider1, collider2, ignore)
DEBUG手段
- 繪制線段:
DrawLine(startPos, endPos, color)
:繪制一條從startPos到endPos點、顏色為color的線段
- 繪制射線:
DrawRay(startPos, direction, color)
:繪制一條從startPos出發,指向direction的、顏色color的射線(默認長度為單位向量,再乘以倍率即可邊長;在下一次繪制才會覆蓋上一次的射線)Debug.DrawRay(startPos , direction, color, duration)
:同理繪制一定方向射線,但射線持續時間為duration :
- Gimos.DrawXXX方法:
void OnDrawGizmos() { Gizmos.DrawCube(transform.position, transform.localScale );}
GC開銷問題
從上面對幾種檢測方法的分析及對比其返回值不難發現,不同方法產生GC情況相差甚遠,因此在工程項目上應該慎重使用。此處引用網友 HONT的測試作為GC情況參考:
- 同方法下不同模型GC開銷:Box < Sphere < Capsule
- 同模型下不同方法GC開銷:CheckXXX < OverlapXXX < XXXCast