天天看點

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

建立一條射線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所示。

定向發射射線的實作

當我們要使用滑鼠拾取物體或判斷×××是否擊中物體時,我們往往是沿着特定的方向發射射線,這個方向可能是朝向螢幕上的一個點,或者是世界坐标系中的一個矢量方向,沿世界坐标系中的矢量方向發射射線我們已經在上面示範過如何實作。針對向螢幕上的某一點發射射線,Unity 3D為我們提供了兩個API函數以供使用,分别是ScreenPointToRay和ViewportPointToRay。

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 System.Collections;

public class RayDemo02 : MonoBehaviour {

Ray ray;

// 建立射線到螢幕上的參考點,像素坐标

Vector3 position = new Vector3(Screen.width/2.0f, Screen.height/2.0f, 0.0f);

// 射線沿着螢幕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是在控制台下列印輸出射線探測到的物體名稱。

public Ray ViewportPointToRay(Vector3 position);

參數說明:position為螢幕上的一個參考點坐标(坐标已機關化處理)。

ViewportPointToRay方法從錄影機的近視口nearClip向螢幕上的一點position發射射線。Position用機關化比例值的方式表示射線到螢幕上的位置。當參考點position的x分量或y分量從0增長到1時,射線将從螢幕的一邊移動到另一邊。由于position在螢幕上,是以z分量始終為0。

下面我們用一段程式示例說明如何利用ViewportPointToRay來發射一條指向螢幕上的某點來進行定向檢測碰撞體。在場景中建立一個Cube位于錄影機的正前方,将下面的腳本RayDemo03.cs挂載到錄影機上。

public class RayDemo03 : MonoBehaviour {

// 建立射線到螢幕上的參考點,機關化坐标

Vector3 position = new Vector3(0.5f, 0.5f, 0.0f);

position.x = position.x >= 1.0f ? 0.0f : position.x + 0.002f;

ray = Camera.main.ViewportPointToRay(position);

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

利用二次發射射線的方式檢測内部物體

有的時候我們要檢測的物體在其他物體的内部,并且這兩個物體都具有碰撞器,用射線檢測傳回的是第一個物體的資訊。在這種情況下,我們需要使用二次射線發射的做法,即以第一次射線碰撞的外層物體的碰撞點作為第二次射線發射的起點,沿原來方向發射射線,判斷是否與内部物體發生碰撞。

下面我們用一段代碼示例來說明如何用二次發射射線來檢測位于物體内部的目标。在場景中建立兩個Cube,位于錄影機的正前方。在其中一個Cube的位置上建立一個Sphere,并設定它的大小為Cube的一半,這樣Sphere就位于Cube的内部。将下面的腳本RayDemo04.cs挂載到錄影機上。

public class RayDemo04 : MonoBehaviour {

GameObject wrapper; // 外層物體

GameObject target; // 内層物體

string info = ""; // 碰撞檢測資訊

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// 設定碰撞資訊

info = "檢測到物體: " + target.name + "坐标: " + target.transform.position;

else

// 如果二次發射的射線沒有與内層物體碰撞

// 顯示外層物體的網格

wrapper.GetComponent().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分别設定為對應的層次。一開始我們将所有物體設定為透明不可見。當按下滑鼠左鍵發射射線時,傳回射線方向上所有碰撞的物體資訊,将擷取到的物體對象,全部設定為半透明可見。點選按鈕可以切換檢測碰撞的層次。

更多unity2018的功能介紹請到paws3d爪爪學院查找。

繼續閱讀