iT邦幫忙

2022 iThome 鐵人賽

DAY 4
0

射線 Raycast 在Unity 中運用很廣泛,在射擊遊戲中常見,任何瞄準、理解當前物件的轉動方向的使用也很普遍。也因為最近在實驗室要使用到 Oculus進行研究,研究上某一小部分要知道當前 HMD 轉動的方向與角度是多少,故需要給定一個目標來給使用者瞄準,並衡量使用者頭部的轉動量。所以需要知道目前使用者是否瞄準目標的方式就是透過 Raycast 來觸發目標物件上的 Collider ,來確定是否視角有位移到該目標,並計數停留在目標持續的時間。

所以這邊我想簡單介紹該 Raycast 的使用。

環境建置

  1. 首先建置一個簡單的環境。 一個 Plane, 一個 Cube (目標物)、 一個 Sphere (User)。假設實驗的環境。 這個場景的製作不難,主要在 Raycast 的應用,所以就先不大說明環境的建置方法,自己做做看或許會更好看XD。

開始撰寫文本

  1. 接著新增一個文本 RaycastTest 到我們的 User_Sphere 中。

  2. 接下來就是撰寫當 User 的視線,也就是 Raycast 觸發到某一個 Collider 的時候回報給User。首先我們要先準備 Ray 也就是我們的射線。transform.position為我文本上物件的位置, Vector3.forward 為 new Vector3(0, 0, 1) 為物件前方的意思(z軸為物件的前方)。

Ray ray = new Ray(transform.position, Vector3.forward);
  1. 之後當我們觸發到任何物件的時候顯示觸發到某樣物品在 Console 上。 整體的程式碼如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RaycastTest : MonoBehaviour
{
    void Update() 
    {
        Ray ray = new Ray(transform.position, Vector3.forward);
        if(Physics.Raycast(ray))
        {
            Debug.Log("The Ray hit something!");
        }

    }
}
  1. 這樣當我們執行的時候若User 視角對應到某個物件的時候,就會顯示。

顯示被射線觸發的物件名稱

  1. 但我們當前僅知道Ray 有射擊到某樣物件,尚不清楚射擊到的物件是什麼,故就需要 RaycastHit 的物件來幫助我知道當前射擊到的物件名稱。 RaycastHit就是將Ray射線觸發的物件顯示出來或是進行一些特定的處理。
RaycastHit hit;

修改一下程式碼後得到

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RaycastTest : MonoBehaviour
{
    void Update() 
    {
        RaycastHit hit;
        Ray ray = new Ray(transform.position, Vector3.forward);
        if(Physics.Raycast(ray, out hit))
        {
            Debug.Log(hit.transform.name + " hit by the ray!");
        }

    }
}
  1. 執行後就會知道是 Target_Cube 被射線給觸發到,顯示在 Console 中。

顯示目標點在 Raycast射線觸發的物件上

  1. 首先新增一個小 Sphere 。取名為 TargetPoint。 (p.s 可以透過按鍵 F 來追蹤在Scene該物件的位置)

  2. 我們也可透過 Raycast 的物件獲取該User 與 Target 的距離。

Debug.Log("Distance" + hit.distance);

  1. 接下來就是當我們Ray觸發到物件的時候,顯示我們的 TargetPoint 在該觸發物件的位置上,也就是我們Ray 的頂點。 如果Ray 沒有觸發到任何物件就消失。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RaycastTest : MonoBehaviour
{
    public GameObject targetPoint;          // here the target point generate 
    void Awake() {
        targetPoint.SetActive(false);
    }
    void Update() 
    {
        RaycastHit hit;
        Ray ray = new Ray(transform.position, Vector3.forward);
        if(Physics.Raycast(ray, out hit))
        {
            Debug.Log(hit.transform.name + " hit by the ray!");
            Debug.Log("Distance" + hit.distance);
            targetPoint.SetActive(true);
            targetPoint.transform.position = hit.point;
        }
        else{
            targetPoint.SetActive(false);
        }

    }
}
  1. 回到 Unity 將該 TargetPoint 拉近到我們的Script 文本中 GameObject 的位置。 並且務必記得要將TargetPoint 的Sphere Collider 取消勾選,否則 Ray會錯誤的一直去觸發 TargetPoint 的 Collider 而非我們要 Targtet的物件(TargetPoint後面的物件)。

  2. 執行後就會看到該小點在Target 上面。

透過 LayerMask 獲取該觸發物件

  1. 首先我們知道每個物件會有個 Layer 的設定。我們依照 Layer 來確認該物件是否被 Ray 觸發。

  2. 接著修改我們觸發的判斷式,可以發現到 LayerMask 傳入的數值是 int,所以宣告的部分如下。

int layerMask = LayerMask.GetMask("Default");

if(Physics.Raycast(ray, out hit, Mathf.Infinity, layerMask))
        {
            Debug.Log(hit.transform.name + " hit by the ray!");
            Debug.Log("Distance" + hit.distance);
            targetPoint.SetActive(true);
            targetPoint.transform.position = hit.point;
        }
  1. 最後當物件的 Layer 改變,即使 Ray 觸發到某物件也不會有任何的顯示。

避免誤觸發其他物件上的Collider

  1. 首先目前的程式碼如下。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RaycastTest : MonoBehaviour
{
    public GameObject targetPoint;          // here the target point generate 
    void Awake() {
        targetPoint.SetActive(false);
    }
    void Update() 
    {
        RaycastHit hit;
        Ray ray = new Ray(transform.position, Vector3.forward);


        int layerMask = LayerMask.GetMask("Default");

        if(Physics.Raycast(ray, out hit, Mathf.Infinity, layerMask, QueryTriggerInteraction.UseGlobal))
        {
            Debug.Log(hit.transform.name + " hit by the ray!");
            Debug.Log("Distance" + hit.distance);
            targetPoint.SetActive(true);
            targetPoint.transform.position = hit.point;
        }
        else{
            targetPoint.SetActive(false);
        }

    }
}
  1. 在環境中新增一個空白物件 ZonSeDetect 並且在元素中新增一個 Box Collider,點選 Box Collider 上的Edit Collider 可以有效地去調整該 Collider 的長寬高與大小。

點選 Edit Collider 後就會看到Scene 出現一個綠色的框,也就是我們Collider 的樣貌。

  1. 但是這樣會讓 Ray 會去觸發到 Collider 的物件上,變成以下的樣子,這樣很怪有被空氣牆擋住的樣子。

  2. 這邊我們將剛剛的判斷式去做修改,主要修改 QueryTriggerInteraction.UseGlobal 的位置上。

if(Physics.Raycast(ray, out hit, Mathf.Infinity, layerMask, QueryTriggerInteraction.UseGlobal))
        {
            Debug.Log(hit.transform.name + " hit by the ray!");
            Debug.Log("Distance" + hit.distance);
            targetPoint.SetActive(true);
            targetPoint.transform.position = hit.point;
        }

變成以下結果:

if(Physics.Raycast(ray, out hit, Mathf.Infinity, layerMask, QueryTriggerInteraction.Ignore))
        {
            Debug.Log(hit.transform.name + " hit by the ray!");
            Debug.Log("Distance" + hit.distance);
            targetPoint.SetActive(true);
            targetPoint.transform.position = hit.point;
        }
  1. 這樣就可以不被 Collider 給擋住了。也就是就不會被額外的 Collider 的物件給觸發。之後就可以看看環境上的樣貌。

結論

  1. 今天說明如何結合 Ray 與 RaycastHit 的使用,針對當前的視線做使用。
  2. Ray 與 RaycastHit 的搭配可以使用在視角、瞄準與射擊遊戲中,應用的層面很廣。未來也可以結合 LineRenderer 來顯示射線的樣貌。
  3. 透過 LayerMask 可以去讓 RaycastHit 去判別哪一個是我需要觸發的Layer物件,將其同樣Layer名稱的物件Group 起來讓 Ray 給觸發。
  4. QueryTriggerInteraction.Ignore 可以有效的避免RaycastHit觸發單純的Collider的物件。

上一篇
Day3: GetComponent from Object
下一篇
Day5: Understand Line Renderer
系列文
Unity 基本功能實作與日常紀錄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言