【Unity3D】Unity 3D中的射線與碰撞檢測

在我們的游戲開發過程中,有一個很重要的工作就是進行碰撞檢測。例如在射擊游戲中子彈是否擊中敵人,在RPG游戲中是否撿到裝備等等。在進行碰撞檢測時,我們最常用的工具就是射線,Unity 3D的物理引擎也為我們提供了射線類以及相關的函數接口。本文將對射線的使用進行一個總結。

射線是在三維世界中從一個點沿一個方向發射的一條無限長的線。在射線的軌跡上,一旦與添加了碰撞器的模型發生碰撞,將停止發射。我們可以利用射線實現子彈擊中目標的檢測,鼠標點擊拾取物體等功能。

射線的創建和顯示

Ray射線類和RaycastHit射線投射碰撞信息類是兩個最常用的射線工具類。

創建一條射線Ray需要指明射線的起點(origin)和射線的方向(direction)。這兩個參數也是Ray的成員變量。注意,射線的方向在設置時如果未單位化,Unity 3D會自動進行單位歸一化處理。射線Ray的構造函數為 :
public Ray(Vector3 origin, Vector3 direction);

RaycastHit類用于存儲發射射線后產生的碰撞信息。常用的成員變量如下:collider與射線發生碰撞的碰撞器
distance 從射線起點到射線與碰撞器的交點的距離
normal 射線射入平面的法向量
point 射線與碰撞器交點的坐標(Vector3對象)

Physics.Raycast靜態函數用于在場景中發射一條可以和碰撞器碰撞的射線,相關的API如下:



 **1)public static bool Raycast(Vector3 origin, Vector3 direction, float distance=Mathf.Infinity, intlayerMask=DefaultRaycastLayers);**
     **參數說明:**
     origin            射線起點世界坐標
     direction          射線方向矢量
     distance            射線長度(起點到終點的距離),默認設置為無限長
     layerMask        顯示層掩碼(只選擇層次為layerMask指定層次的碰撞器進行碰撞,其他層次的碰撞器忽略)
     **返回值說明:**
     當射線與碰撞器發生碰撞時返回值為true,未穿過任何碰撞器時返回為false。

     **2)public static boolRaycast(Vector3 origin, Vector3 direction, RaycastHit hitInfo, float distance =Mathf.Infinity, int layerMask = DefaultRaycastLayers);**
     這個重載函數定義了一個碰撞信息類**RaycastHit**,在使用時通過out關鍵字傳入一個空的碰撞信息對象。當射線與碰撞器發生碰撞時,該對象將被賦值,可以獲得碰撞信息包括transform、rigidbody、point 等。如果未發生碰撞,該對象為空。

     **3)public static boolRaycast(Ray ray, float distance = Mathf.Infinity, int layerMask =DefaultRaycastLayers);**
     這個重載函數使用已有的一條射線Ray來作為參數。

     **4)public static boolRaycast(Ray ray, RaycastHit hitInfo, float distance = Mathf.Infinity, intlayerMask = DefaultRaycastLayers);**
     這個重載函數使用已有的射線Ray來作為參數并獲取碰撞信息RaycastHit。
     在調試時如果想顯示一條射線,可以使用Debug.DrawLine來實現。
     **public static void DrawLine(Vector3start, Vector3 end, Color color);**
     只有當發生碰撞時,在Scene視圖中才能看到畫出的射線。

     下面這個例子創建了一個從主攝像機向y軸負向發射一條射線檢測下方是否有平面存在。在場景中攝像機下方創建一個Plane游戲對象,并將下面的腳本RayDemo01.cs掛載到攝像機上。

using UnityEngine;  
         using System.Collections;  

         public class RayDemo01 : MonoBehaviour {  

                void Update () {  
                   // 以攝像機所在位置為起點,創建一條向下發射的射線  
                   Ray ray = new Ray(transform.position, -transform.up);  
                   RaycastHit hit;  
                   if(Physics.Raycast(ray, out hit, Mathf.Infinity))  
                   {  
                       // 如果射線與平面碰撞,打印碰撞物體信息  
                       Debug.Log("碰撞對象: " + hit.collider.name);  
                        // 在場景視圖中繪制射線  
                       Debug.DrawLine(ray.origin, hit.point, Color.red); 
                   }  
               }  
         }

運行程序后,如圖1所示,在場景視圖中可以看見攝像機發出的射線。當檢測到下方的平面時,會在控制臺中打印輸出檢測結果,如圖2所示。


圖1 在場景中顯示的射線.jpg

圖2 在控制臺中打印的碰撞檢測信息.jpg

定向發射射線的實現
當我們要使用鼠標拾取物體或判斷子彈是否擊中物體時,我們往往是沿著特定的方向發射射線,這個方向可能是朝向屏幕上的一個點,或者是世界坐標系中的一個矢量方向,沿世界坐標系中的矢量方向發射射線我們已經在上面演示過如何實現。針對向屏幕上的某一點發射射線,Unity 3D為我們提供了兩個API函數以供使用,分別是ScreenPointToRayViewportPointToRay
public Ray ScreenPointToRay(Vector3 position);****參數說明:position是屏幕上的一個參考點坐標。返回值說明:返回射向position參考點的射線。當發射的射線未碰撞到物體時,碰撞點hit.point的值為(0,0,0)。
ScreenPointToRay方法從攝像機的近視口nearClip向屏幕上的一點position發射射線。Position用實際像素值表示射線到屏幕上的位置。當參考點position的x分量或y分量從0增長到最大值時,射線將從屏幕的一邊移動到另一邊。由于position在屏幕上,因此z分量始終為0。
下面我們用一段程序示例說明如何利用ScreenPointToRay來發射一條指向屏幕上的某點來進行定向檢測碰撞體。在場景中創建一個Cube位于攝像機的正前方,將下面的腳本RayDemo02.cs掛載到攝像機上。

運行程序后,如圖1所示,在場景視圖中可以看見攝像機發出的射線。當檢測到下方的平面時,會在控制臺中打印輸出檢測結果,如圖2所示。


圖1 在場景中顯示的射線.jpg

圖2 在控制臺中打印的碰撞檢測信息.jpg

定向發射射線的實現
當我們要使用鼠標拾取物體或判斷子彈是否擊中物體時,我們往往是沿著特定的方向發射射線,這個方向可能是朝向屏幕上的一個點,或者是世界坐標系中的一個矢量方向,沿世界坐標系中的矢量方向發射射線我們已經在上面演示過如何實現。針對向屏幕上的某一點發射射線,Unity 3D為我們提供了兩個API函數以供使用,分別是ScreenPointToRayViewportPointToRay
public Ray ScreenPointToRay(Vector3 position);****參數說明:position是屏幕上的一個參考點坐標。返回值說明:返回射向position參考點的射線。當發射的射線未碰撞到物體時,碰撞點hit.point的值為(0,0,0)。
ScreenPointToRay方法從攝像機的近視口nearClip向屏幕上的一點position發射射線。Position用實際像素值表示射線到屏幕上的位置。當參考點position的x分量或y分量從0增長到最大值時,射線將從屏幕的一邊移動到另一邊。由于position在屏幕上,因此z分量始終為0。
下面我們用一段程序示例說明如何利用ScreenPointToRay來發射一條指向屏幕上的某點來進行定向檢測碰撞體。在場景中創建一個Cube位于攝像機的正前方,將下面的腳本RayDemo02.cs掛載到攝像機上。

using UnityEngine;  
using System.Collections;  

public class RayDemo02 : MonoBehaviour {  
    Ray ray;  
    RaycastHit hit;  
    // 創建射線到屏幕上的參考點,像素坐標  
    Vector3 position = new Vector3(Screen.width/2.0f, Screen.height/2.0f, 0.0f);  

    void Update () {  
        // 射線沿著屏幕x軸從左向右循環掃描  
        position.x = position.x >= Screen.width ? 0.0f : position.x + 1.0f;  
        // 生成射線  
        ray = Camera.main.ScreenPointToRay(position);  
        if(Physics.Raycast(ray, out hit, 100.0f))  
        {  
            // 如果與物體發生碰撞,在Scene視圖中繪制射線  
            Debug.DrawLine(ray.origin, hit.point, Color.green);  
            // 打印射線檢測到的物體的名稱  
            Debug.Log("射線檢測到的物體名稱: " + hit.transform.name);  
        }  
    }  
}

在這段代碼中,首先聲明了一個變量position,用于記錄射線到屏幕上的實際交點的像素坐標,然后在Update方法中更改position的x分量值,使得射線從屏幕左方向右方不斷循環掃描,接著調用方法ScreenPointToRay生成射線ray,最后繪制射線和打印射線探測到的物體的名稱。運行程序后,如圖3所示,在Scene視圖中可以看到我們繪制的射線正在場景中掃描,圖4是在控制臺下打印輸出射線探測到的物體名稱。


圖3 使用ScreenPointToRay方法發射射線.jpg

圖4 控制臺中輸出的碰撞檢測信息.jpg

public Ray ViewportPointToRay(Vector3 position);****參數說明:position為屏幕上的一個參考點坐標(坐標已單位化處理)。返回值說明:返回射向position參考點的射線。當發射的射線未碰撞到物體時,碰撞點hit.point的值為(0,0,0)。
ViewportPointToRay方法從攝像機的近視口nearClip向屏幕上的一點position發射射線。Position用單位化比例值的方式表示射線到屏幕上的位置。當參考點position的x分量或y分量從0增長到1時,射線將從屏幕的一邊移動到另一邊。由于position在屏幕上,因此z分量始終為0。
下面我們用一段程序示例說明如何利用ViewportPointToRay來發射一條指向屏幕上的某點來進行定向檢測碰撞體。在場景中創建一個Cube位于攝像機的正前方,將下面的腳本RayDemo03.cs掛載到攝像機上。

using UnityEngine;  
using System.Collections;  

public class RayDemo03 : MonoBehaviour {  
    Ray ray;  
    RaycastHit hit;  
    // 創建射線到屏幕上的參考點,單位化坐標  
    Vector3 position = new Vector3(0.5f, 0.5f, 0.0f);  
    void Update () {  
        // 射線沿著屏幕x軸從左向右循環掃描  
        position.x = position.x >= 1.0f ? 0.0f : position.x + 0.002f;  
        // 生成射線  
        ray = Camera.main.ViewportPointToRay(position);  
        if(Physics.Raycast(ray, out hit, 100.0f))  
        {  
            // 如果與物體發生碰撞,在Scene視圖中繪制射線  
            Debug.DrawLine(ray.origin, hit.point, Color.green);  
            // 打印射線檢測到的物體的名稱  
            Debug.Log("射線檢測到的物體名稱: " + hit.transform.name);  
        }  
    }  
}

在這段代碼中,首先聲明了一個變量position,用于記錄射線到屏幕上的實際交點的像素坐標,然后在Update方法中更改position的x分量值,使得射線從屏幕左方向右方不斷循環掃描,接著調用方法ViewportPointToRay生成射線ray,最后繪制射線和打印射線探測到的物體的名稱。運行程序后,如圖5所示,在Scene視圖中可以看到我們繪制的射線正在場景中掃描,圖6是在控制臺下打印輸出射線探測到的物體名稱。


圖5 使用ViewportPointToRay方法發射射線.jpg

圖6 控制臺中輸出的碰撞檢測信息.jpg

利用二次發射射線的方式檢測內部物體
有的時候我們要檢測的物體在其他物體的內部,并且這兩個物體都具有碰撞器,用射線檢測返回的是第一個物體的信息。在這種情況下,我們需要使用二次射線發射的做法,即以第一次射線碰撞的外層物體的碰撞點作為第二次射線發射的起點,沿原來方向發射射線,判斷是否與內部物體發生碰撞。
下面我們用一段代碼示例來說明如何用二次發射射線來檢測位于物體內部的目標。在場景中創建兩個Cube,位于攝像機的正前方。在其中一個Cube的位置上創建一個Sphere,并設置它的大小為Cube的一半,這樣Sphere就位于Cube的內部。將下面的腳本RayDemo04.cs掛載到攝像機上。

using UnityEngine;  
using System.Collections;  

public class RayDemo04 : MonoBehaviour {  
        GameObject wrapper; // 外層物體  
        GameObject target; // 內層物體  
        string info = ""; // 碰撞檢測信息  

        void Update () {  

        if(Input.GetMouseButton (0))  
        {  
            // 當鼠標左鍵按下時,向鼠標所在的屏幕位置發射一條射線  
      Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);  
            RaycastHit hitInfo;  
            if(Physics.Raycast(ray, out hitInfo))  
            {  
                // 當射線與物體發生碰撞時,在場景視圖中繪制射線  
                Debug.DrawLine(ray.origin, hitInfo.point, Color.red);  
                // 獲得第一次碰撞的外層物體對象  
                wrapper = hitInfo.collider.gameObject;  
               // 以第一次的碰撞點為起點,沿原來的方向二次發射射線  
               Ray ray2= new Ray(hitInfo.point, ray.direction);  
               RaycastHit hitInfo2;  
               if(Physics.Raycast(ray2, out hitInfo2))  
               {  
               // 當射線與內層物體碰撞時,在場景中繪制射線  
               Debug.DrawLine(ray2.origin, ray2.direction, Color.green);  
               // 獲得內層物體對象  
               target = hitInfo2.collider.gameObject;  
               // 將外層物體的網格隱藏  
               wrapper.GetComponent<MeshRenderer().enabled = false;  
               // 設置碰撞信息  
               info = "檢測到物體: " + target.name + "坐標: " + target.transform.position;  
                }  
                else  
                {  
               // 如果二次發射的射線沒有與內層物體碰撞  
               // 顯示外層物體的網格  
               wrapper.GetComponent<MeshRenderer>().enabled = true;  
               // 設置碰撞信息  
               info = "檢測到物體: " + wrapper.name + "坐標: " + wrapper.transform.position;  
               }  
            }  
        }  
    }  

    void OnGUI(){  
        // 在屏幕上打印輸出射線檢測的信息  
        GUILayout.Label(info);  
    }  
}

在上面這段代碼中我們使用左移位操作符<<來設置碰撞層的掩碼layerMask。Unity 3D中共有32個層,對應使用一個32位整數的各個位來表示每個層級,當這個位為1時表示使用這個層,為0時表示不使用這個層。
LayerMask.NameToLayer這個API是返回我們使用自定義命名所定義的層的層索引,注意從0開始。當我們使用左移位操作設置層次掩碼時,對應的自定義層級是n我們就將1左移n位,這樣射線就只在layerMask指定的層次上進行碰撞檢測。可供使用的自定義的層級從第8層開始,我們將8~10層分別命名為Capsule、Sphere和Cube,并將Capsule、Shpere和Cube三個物體的layer分別設置為對應的層次。一開始我們將所有物體設置為透明不可見。當按下鼠標左鍵發射射線時,返回射線方向上所有碰撞的物體信息,將獲取到的物體對象,全部設置為半透明可見。點擊按鈕可以切換檢測碰撞的層次。
運行代碼,如圖9、圖10所示,當切換不同的按鈕控制射線在不同的層次上檢測碰撞,顯示的物體也便不同。


圖9 僅顯示Cube層時進行的射線碰撞檢測.jpg

圖10 顯示所有層時進行的射線碰撞檢測.jpg

當然還有很多的關于射線使用的API不能一一贅述,這篇只是做一個簡單的梳理,更多的API例如SphereCast、LineCast的具體用法可以查閱官方文檔。在我們的游戲開發過程中,有一個很重要的工作就是進行碰撞檢測。例如在射擊游戲中子彈是否擊中敵人,在RPG游戲中是否撿到裝備等等。在進行碰撞檢測時,我們最常用的工具就是射線,Unity 3D的物理引擎也為我們提供了射線類以及相關的函數接口。本文將對射線的使用進行一個總結。

射線是在三維世界中從一個點沿一個方向發射的一條無限長的線。在射線的軌跡上,一旦與添加了碰撞器的模型發生碰撞,將停止發射。我們可以利用射線實現子彈擊中目標的檢測,鼠標點擊拾取物體等功能。

射線的創建和顯示

Ray射線類和RaycastHit射線投射碰撞信息類是兩個最常用的射線工具類。

創建一條射線Ray需要指明射線的起點(origin)和射線的方向(direction)。這兩個參數也是Ray的成員變量。注意,射線的方向在設置時如果未單位化,Unity 3D會自動進行單位歸一化處理。射線Ray的構造函數為 :
public Ray(Vector3 origin, Vector3 direction);

RaycastHit類用于存儲發射射線后產生的碰撞信息。常用的成員變量如下:collider與射線發生碰撞的碰撞器
distance 從射線起點到射線與碰撞器的交點的距離
normal 射線射入平面的法向量
point 射線與碰撞器交點的坐標(Vector3對象)

Physics.Raycast靜態函數用于在場景中發射一條可以和碰撞器碰撞的射線,相關的API如下:



 **1)public static bool Raycast(Vector3 origin, Vector3 direction, float distance=Mathf.Infinity, intlayerMask=DefaultRaycastLayers);**
     **參數說明:**
     origin            射線起點世界坐標
     direction          射線方向矢量
     distance            射線長度(起點到終點的距離),默認設置為無限長
     layerMask        顯示層掩碼(只選擇層次為layerMask指定層次的碰撞器進行碰撞,其他層次的碰撞器忽略)
     **返回值說明:**
     當射線與碰撞器發生碰撞時返回值為true,未穿過任何碰撞器時返回為false。

     **2)public static boolRaycast(Vector3 origin, Vector3 direction, RaycastHit hitInfo, float distance =Mathf.Infinity, int layerMask = DefaultRaycastLayers);**
     這個重載函數定義了一個碰撞信息類**RaycastHit**,在使用時通過out關鍵字傳入一個空的碰撞信息對象。當射線與碰撞器發生碰撞時,該對象將被賦值,可以獲得碰撞信息包括transform、rigidbody、point 等。如果未發生碰撞,該對象為空。

     **3)public static boolRaycast(Ray ray, float distance = Mathf.Infinity, int layerMask =DefaultRaycastLayers);**
     這個重載函數使用已有的一條射線Ray來作為參數。

     **4)public static boolRaycast(Ray ray, RaycastHit hitInfo, float distance = Mathf.Infinity, intlayerMask = DefaultRaycastLayers);**
     這個重載函數使用已有的射線Ray來作為參數并獲取碰撞信息RaycastHit。
     在調試時如果想顯示一條射線,可以使用Debug.DrawLine來實現。
     **public static void DrawLine(Vector3start, Vector3 end, Color color);**
     只有當發生碰撞時,在Scene視圖中才能看到畫出的射線。

     下面這個例子創建了一個從主攝像機向y軸負向發射一條射線檢測下方是否有平面存在。在場景中攝像機下方創建一個Plane游戲對象,并將下面的腳本RayDemo01.cs掛載到攝像機上。

using UnityEngine;  
         using System.Collections;  

         public class RayDemo01 : MonoBehaviour {  

                void Update () {  
                   // 以攝像機所在位置為起點,創建一條向下發射的射線  
                   Ray ray = new Ray(transform.position, -transform.up);  
                   RaycastHit hit;  
                   if(Physics.Raycast(ray, out hit, Mathf.Infinity))  
                   {  
                       // 如果射線與平面碰撞,打印碰撞物體信息  
                       Debug.Log("碰撞對象: " + hit.collider.name);  
                        // 在場景視圖中繪制射線  
                       Debug.DrawLine(ray.origin, hit.point, Color.red); 
                   }  
               }  
         }

運行程序后,如圖1所示,在場景視圖中可以看見攝像機發出的射線。當檢測到下方的平面時,會在控制臺中打印輸出檢測結果,如圖2所示。


圖1 在場景中顯示的射線.jpg

圖2 在控制臺中打印的碰撞檢測信息.jpg

定向發射射線的實現
當我們要使用鼠標拾取物體或判斷子彈是否擊中物體時,我們往往是沿著特定的方向發射射線,這個方向可能是朝向屏幕上的一個點,或者是世界坐標系中的一個矢量方向,沿世界坐標系中的矢量方向發射射線我們已經在上面演示過如何實現。針對向屏幕上的某一點發射射線,Unity 3D為我們提供了兩個API函數以供使用,分別是ScreenPointToRayViewportPointToRay
public Ray ScreenPointToRay(Vector3 position);****參數說明:position是屏幕上的一個參考點坐標。返回值說明:返回射向position參考點的射線。當發射的射線未碰撞到物體時,碰撞點hit.point的值為(0,0,0)。
ScreenPointToRay方法從攝像機的近視口nearClip向屏幕上的一點position發射射線。Position用實際像素值表示射線到屏幕上的位置。當參考點position的x分量或y分量從0增長到最大值時,射線將從屏幕的一邊移動到另一邊。由于position在屏幕上,因此z分量始終為0。
下面我們用一段程序示例說明如何利用ScreenPointToRay來發射一條指向屏幕上的某點來進行定向檢測碰撞體。在場景中創建一個Cube位于攝像機的正前方,將下面的腳本RayDemo02.cs掛載到攝像機上。

運行程序后,如圖1所示,在場景視圖中可以看見攝像機發出的射線。當檢測到下方的平面時,會在控制臺中打印輸出檢測結果,如圖2所示。


圖1 在場景中顯示的射線.jpg

圖2 在控制臺中打印的碰撞檢測信息.jpg

定向發射射線的實現
當我們要使用鼠標拾取物體或判斷子彈是否擊中物體時,我們往往是沿著特定的方向發射射線,這個方向可能是朝向屏幕上的一個點,或者是世界坐標系中的一個矢量方向,沿世界坐標系中的矢量方向發射射線我們已經在上面演示過如何實現。針對向屏幕上的某一點發射射線,Unity 3D為我們提供了兩個API函數以供使用,分別是ScreenPointToRayViewportPointToRay
public Ray ScreenPointToRay(Vector3 position);****參數說明:position是屏幕上的一個參考點坐標。返回值說明:返回射向position參考點的射線。當發射的射線未碰撞到物體時,碰撞點hit.point的值為(0,0,0)。
ScreenPointToRay方法從攝像機的近視口nearClip向屏幕上的一點position發射射線。Position用實際像素值表示射線到屏幕上的位置。當參考點position的x分量或y分量從0增長到最大值時,射線將從屏幕的一邊移動到另一邊。由于position在屏幕上,因此z分量始終為0。
下面我們用一段程序示例說明如何利用ScreenPointToRay來發射一條指向屏幕上的某點來進行定向檢測碰撞體。在場景中創建一個Cube位于攝像機的正前方,將下面的腳本RayDemo02.cs掛載到攝像機上。

using UnityEngine;  
using System.Collections;  

public class RayDemo02 : MonoBehaviour {  
    Ray ray;  
    RaycastHit hit;  
    // 創建射線到屏幕上的參考點,像素坐標  
    Vector3 position = new Vector3(Screen.width/2.0f, Screen.height/2.0f, 0.0f);  

    void Update () {  
        // 射線沿著屏幕x軸從左向右循環掃描  
        position.x = position.x >= Screen.width ? 0.0f : position.x + 1.0f;  
        // 生成射線  
        ray = Camera.main.ScreenPointToRay(position);  
        if(Physics.Raycast(ray, out hit, 100.0f))  
        {  
            // 如果與物體發生碰撞,在Scene視圖中繪制射線  
            Debug.DrawLine(ray.origin, hit.point, Color.green);  
            // 打印射線檢測到的物體的名稱  
            Debug.Log("射線檢測到的物體名稱: " + hit.transform.name);  
        }  
    }  
}

在這段代碼中,首先聲明了一個變量position,用于記錄射線到屏幕上的實際交點的像素坐標,然后在Update方法中更改position的x分量值,使得射線從屏幕左方向右方不斷循環掃描,接著調用方法ScreenPointToRay生成射線ray,最后繪制射線和打印射線探測到的物體的名稱。運行程序后,如圖3所示,在Scene視圖中可以看到我們繪制的射線正在場景中掃描,圖4是在控制臺下打印輸出射線探測到的物體名稱。


圖3 使用ScreenPointToRay方法發射射線.jpg

圖4 控制臺中輸出的碰撞檢測信息.jpg

public Ray ViewportPointToRay(Vector3 position);****參數說明:position為屏幕上的一個參考點坐標(坐標已單位化處理)。返回值說明:返回射向position參考點的射線。當發射的射線未碰撞到物體時,碰撞點hit.point的值為(0,0,0)。
ViewportPointToRay方法從攝像機的近視口nearClip向屏幕上的一點position發射射線。Position用單位化比例值的方式表示射線到屏幕上的位置。當參考點position的x分量或y分量從0增長到1時,射線將從屏幕的一邊移動到另一邊。由于position在屏幕上,因此z分量始終為0。
下面我們用一段程序示例說明如何利用ViewportPointToRay來發射一條指向屏幕上的某點來進行定向檢測碰撞體。在場景中創建一個Cube位于攝像機的正前方,將下面的腳本RayDemo03.cs掛載到攝像機上。

using UnityEngine;  
using System.Collections;  

public class RayDemo03 : MonoBehaviour {  
    Ray ray;  
    RaycastHit hit;  
    // 創建射線到屏幕上的參考點,單位化坐標  
    Vector3 position = new Vector3(0.5f, 0.5f, 0.0f);  
    void Update () {  
        // 射線沿著屏幕x軸從左向右循環掃描  
        position.x = position.x >= 1.0f ? 0.0f : position.x + 0.002f;  
        // 生成射線  
        ray = Camera.main.ViewportPointToRay(position);  
        if(Physics.Raycast(ray, out hit, 100.0f))  
        {  
            // 如果與物體發生碰撞,在Scene視圖中繪制射線  
            Debug.DrawLine(ray.origin, hit.point, Color.green);  
            // 打印射線檢測到的物體的名稱  
            Debug.Log("射線檢測到的物體名稱: " + hit.transform.name);  
        }  
    }  
}

在這段代碼中,首先聲明了一個變量position,用于記錄射線到屏幕上的實際交點的像素坐標,然后在Update方法中更改position的x分量值,使得射線從屏幕左方向右方不斷循環掃描,接著調用方法ViewportPointToRay生成射線ray,最后繪制射線和打印射線探測到的物體的名稱。運行程序后,如圖5所示,在Scene視圖中可以看到我們繪制的射線正在場景中掃描,圖6是在控制臺下打印輸出射線探測到的物體名稱。


圖5 使用ViewportPointToRay方法發射射線.jpg

圖6 控制臺中輸出的碰撞檢測信息.jpg

利用二次發射射線的方式檢測內部物體
有的時候我們要檢測的物體在其他物體的內部,并且這兩個物體都具有碰撞器,用射線檢測返回的是第一個物體的信息。在這種情況下,我們需要使用二次射線發射的做法,即以第一次射線碰撞的外層物體的碰撞點作為第二次射線發射的起點,沿原來方向發射射線,判斷是否與內部物體發生碰撞。
下面我們用一段代碼示例來說明如何用二次發射射線來檢測位于物體內部的目標。在場景中創建兩個Cube,位于攝像機的正前方。在其中一個Cube的位置上創建一個Sphere,并設置它的大小為Cube的一半,這樣Sphere就位于Cube的內部。將下面的腳本RayDemo04.cs掛載到攝像機上。

using UnityEngine;  
using System.Collections;  

public class RayDemo04 : MonoBehaviour {  
        GameObject wrapper; // 外層物體  
        GameObject target; // 內層物體  
        string info = ""; // 碰撞檢測信息  

        void Update () {  

        if(Input.GetMouseButton (0))  
        {  
            // 當鼠標左鍵按下時,向鼠標所在的屏幕位置發射一條射線  
      Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);  
            RaycastHit hitInfo;  
            if(Physics.Raycast(ray, out hitInfo))  
            {  
                // 當射線與物體發生碰撞時,在場景視圖中繪制射線  
                Debug.DrawLine(ray.origin, hitInfo.point, Color.red);  
                // 獲得第一次碰撞的外層物體對象  
                wrapper = hitInfo.collider.gameObject;  
               // 以第一次的碰撞點為起點,沿原來的方向二次發射射線  
               Ray ray2= new Ray(hitInfo.point, ray.direction);  
               RaycastHit hitInfo2;  
               if(Physics.Raycast(ray2, out hitInfo2))  
               {  
               // 當射線與內層物體碰撞時,在場景中繪制射線  
               Debug.DrawLine(ray2.origin, ray2.direction, Color.green);  
               // 獲得內層物體對象  
               target = hitInfo2.collider.gameObject;  
               // 將外層物體的網格隱藏  
               wrapper.GetComponent<MeshRenderer().enabled = false;  
               // 設置碰撞信息  
               info = "檢測到物體: " + target.name + "坐標: " + target.transform.position;  
                }  
                else  
                {  
               // 如果二次發射的射線沒有與內層物體碰撞  
               // 顯示外層物體的網格  
               wrapper.GetComponent<MeshRenderer>().enabled = true;  
               // 設置碰撞信息  
               info = "檢測到物體: " + wrapper.name + "坐標: " + wrapper.transform.position;  
               }  
            }  
        }  
    }  

    void OnGUI(){  
        // 在屏幕上打印輸出射線檢測的信息  
        GUILayout.Label(info);  
    }  
}

在上面這段代碼中我們使用左移位操作符<<來設置碰撞層的掩碼layerMask。Unity 3D中共有32個層,對應使用一個32位整數的各個位來表示每個層級,當這個位為1時表示使用這個層,為0時表示不使用這個層。
LayerMask.NameToLayer這個API是返回我們使用自定義命名所定義的層的層索引,注意從0開始。當我們使用左移位操作設置層次掩碼時,對應的自定義層級是n我們就將1左移n位,這樣射線就只在layerMask指定的層次上進行碰撞檢測。可供使用的自定義的層級從第8層開始,我們將8~10層分別命名為Capsule、Sphere和Cube,并將Capsule、Shpere和Cube三個物體的layer分別設置為對應的層次。一開始我們將所有物體設置為透明不可見。當按下鼠標左鍵發射射線時,返回射線方向上所有碰撞的物體信息,將獲取到的物體對象,全部設置為半透明可見。點擊按鈕可以切換檢測碰撞的層次。
運行代碼,如圖9、圖10所示,當切換不同的按鈕控制射線在不同的層次上檢測碰撞,顯示的物體也便不同。
[圖片上傳中。。。(1)]圖9 僅顯示Cube層時進行的射線碰撞檢測.jpg

圖10 顯示所有層時進行的射線碰撞檢測.jpg

當然還有很多的關于射線使用的API不能一一贅述,這篇只是做一個簡單的梳理,更多的API例如SphereCast、LineCast的具體用法可以查閱官方文檔。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容