在我們的游戲開發過程中,有一個很重要的工作就是進行碰撞檢測。例如在射擊游戲中子彈是否擊中敵人,在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靜態函數用于在場景中發射一條可以和碰撞器碰撞的射線
(1)下面來實現一個簡單的demo
代碼如下:
using UnityEngine;
using System.Collections;public class RayCast : MonoBehaviour {// Use this for initializationvoid Start () {}// Update is called once per framevoid Update () {Ray ray = new Ray(transform.position, transform.forward);RaycastHit hit;if (Physics.Raycast(ray, out hit, Mathf.Infinity)){print("射線碰撞到了"+hit.collider.gameObject+"物體"+ray.origin+" "+hit.point);Debug.DrawLine(ray.origin,hit.point,Color.red);}}
}
?當運動的物體前進方向發出的射線接觸到cube時,scene中將會畫出一條紅色射線,以及打印出提示信息。
?
定向發射射線的實現
當我們要使用鼠標拾取物體或判斷子彈是否擊中物體時,我們往往是沿著特定的方向發射射線,這個方向可能是朝向屏幕上的一個點,或者是世界坐標系中的一個矢量方向,沿世界坐標系中的矢量方向發射射線我們已經在上面演示過如何實現。針對向屏幕上的某一點發射射線,Unity 3D為我們提供了兩個API函數以供使用,分別是ScreenPointToRay和ViewportPointToRay。
(2)ScreenPointToRay
public Ray ScreenPointToRay(Vector3 position);
參數說明:position是屏幕上的一個參考點坐標。
返回值說明:返回射向position參考點的射線。當發射的射線未碰撞到物體時,碰撞點hit.point的值為(0,0,0)。
ScreenPointToRay方法從攝像機的近視口nearClip向屏幕上的一點position發射射線。Position用實際像素值表示射線到屏幕上的位置。當參考點position的x分量或y分量從0增長到最大值時,射線將從屏幕的一邊移動到另一邊。由于position在屏幕上,因此z分量始終為0。
下面我們用一段程序示例說明如何利用ScreenPointToRay來發射一條指向屏幕上的某點來進行定向檢測碰撞體。在場景中創建一個c_Cube位于攝像機的正前方,將下面的腳本RayDemo.cs掛載到攝像機上。
using UnityEngine;
using System.Collections;public class RayDemo : MonoBehaviour {Ray ray;RaycastHit hit;//創建射線到屏幕上的參考點,像素坐標Vector3 position = new Vector3(Screen.width / 2.0f, Screen.height / 2.0f, 0.0f);// Use this for initializationvoid Start () {}// Update is called once per framevoid 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)){//如果物體發生了碰撞,在screen視圖中繪制射線Debug.DrawLine(ray.origin,hit.point,Color.green);//打印射線檢測到的物體的名稱Debug.Log("射線檢測到的物體的名稱:"+hit.transform.name);}}
}
運行截圖如下:
(3)ViewportPointToRay
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來發射一條指向屏幕上的某點來進行定向檢測碰撞體。在場景中創建一個c_Cube位于攝像機的正前方,將下面的腳本RayDemo01.cs掛載到攝像機上。
using UnityEngine;
using System.Collections;public class RayDemo : MonoBehaviour {Ray ray;RaycastHit hit;//創建射線到屏幕上的參考點,像素坐標Vector3 position = new Vector3(0.5f, 0.5f, 0.0f);// Use this for initializationvoid Start () {}// Update is called once per framevoid Update () {//射線沿著屏幕x軸,從左向右循環掃描position.x = position.x >= 1.0f ? 0.0f : position.x + 0.005f;//生成射線ray = Camera.main.ViewportPointToRay(position);if (Physics.Raycast(ray, out hit, 100.0f)){//如果物體發生了碰撞,在screen視圖中繪制射線Debug.DrawLine(ray.origin,hit.point,Color.blue);//打印射線檢測到的物體的名稱Debug.Log("射線檢測到的物體的名稱:"+hit.transform.name);}}
}
運行截圖如下:
(3)
利用二次發射射線的方式檢測內部物體
有的時候我們要檢測的物體在其他物體的內部,并且這兩個物體都具有碰撞器,用射線檢測返回的是第一個物體的信息。在這種情況下,我們需要使用二次射線發射的做法,即以第一次射線碰撞的外層物體的碰撞點作為第二次射線發射的起點,沿原來方向發射射線,判斷是否與內部物體發生碰撞。
下面我們用一段代碼示例來說明如何用二次發射射線來檢測位于物體內部的目標。在場景中創建兩個Cube,位于攝像機的正前方。在其中一個Cube的位置上創建一個Sphere,并設置它的大小為Cube的一半,這樣Sphere就位于Cube的內部。將下面的腳本RayDemo02.cs掛載到攝像機上。
using UnityEngine;
using System.Collections;public class RayDemo : 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);}}
當點擊cube時,注意在偏離中心位置時,sphere沒有顯示出來,如圖:
點擊球的位置時,外部的cube 被隱藏了,因此我們能看到里面的小球