기술나눔

Unity--광선 감지--RayCast

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Unity – 광선 감지 – RayCast

1. 방사선투과검사의 의미

광선 감지는 이름에 따라 광선을 사용하여 물체/여러 물체에 닿는지 여부를 감지합니다.

방사선 촬영 테스트는 두 부분으로 구성됩니다. 광선그리고발각

2. 방사선투과검사는 어디에 사용될 수 있나요?

  1. 슈팅 게임
    • 플레이어 조준 및 사격: 플레이어의 시선이 적 또는 다른 대상과 교차하는지 여부를 감지합니다.
    • 총알 궤적 및 효과: 총알의 비행 경로와 타격 효과를 시뮬레이션합니다.
  2. 상호작용 및 UI
    • 마우스 클릭 감지: 플레이어의 마우스 클릭이 게임 개체 또는 UI 요소와 교차하는지 감지합니다.
    • 터치스크린 상호작용: 플레이어의 터치가 모바일 장치의 특정 게임 요소와 교차하는지 감지합니다.
  3. 캐릭터 컨트롤러와 AI
    • 시각 감지: NPC나 적들은 특정 범위 내의 플레이어나 다른 캐릭터를 감지합니다.
    • 충돌 회피: AI 캐릭터는 광선 감지를 사용하여 이동할 때 충돌을 방지합니다.
  4. 가상현실(VR)과 증강현실(AR)
    • 눈 또는 손 추적: VR에서 플레이어의 시선이나 손 위치를 감지합니다.
    • 객체 상호작용: 플레이어가 AR에서 가상 객체와 교차하는지 감지합니다.

3.레이의 유니티

광선은 일상생활의 여러 곳에서 볼 수 있습니다.플래시,ppt 레이저 페이지 터닝 펜, 청우 시대 중반물벼룩

광선은 시작점, 방향, 거리로 구성됩니다. origin , direction 그리고distance

물리학에서는 광선의 거리가 무한하므로 물리적 광선에는 하나의 시작점과 하나의 방향만 있습니다. 게임에서는 광선의 최대 거리도 시스템에 의해 제한됩니다. 1000.0미터 다음은 Ray에 대한 설명입니다.

Unity에서 Ray는 구조 볼륨의 기본 멤버입니다.origin, direction 그리고GetPoint즉, 광선을 따라 일정 거리에 있는 점의 시작점, 방향 및 위치는 다음과 같습니다. Unity의 Ray에 대한 코드입니다.

using System;

namespace UnityEngine
{
    public struct Ray : IFormattable
    {        
        public Ray(Vector3 origin, Vector3 direction);
        public Vector3 origin { get; set; } // 起点(向量)
        public Vector3 direction { get; set; }// 方向(向量)
        public Vector3 GetPoint(float distance);// 沿着射线一定距离的点(向量)
        public override string ToString();
        public string ToString(string format);
        public string ToString(string format, IFormatProvider formatProvider);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

3.1 광선 구성

위의 코드에 따르면 Ray 생성자를 사용하여 광선을 직접 생성할 수 있음을 알 수 있습니다. 예를 들면 다음과 같습니다.

Ray ray = new Ray(Vector3.zero, Vector3.forward); // 射线的起点 + 射线的方向
  • 1

위의 코드에 따르면 광선의 시작점은 Unity의 월드 좌표 원점(0,0,0)이고, 광선의 방향은 월드 좌표의 정방향임을 알 수 있습니다.

3.2 광선 표시 방법

실제로는 레이저 포인터와 손전등을 볼 수 있지만 Unity의 광선은 보이지 않습니다. 따라서 광선을 표시하려면 다음 방법을 사용할 수 있습니다.

  • 사용Debug.DrawRay()디스플레이 레이
  • 사용LineRenderer구성요소가 광선을 그립니다.

4.Unity의 광선 감지

단순히 광선을 구성하거나 표시하는 것은 손에 도구를 들고 도구 없이 작업하는 것과 같습니다.

물체 감지를 위해 광선을 사용하는 방법.

생각해 보세요. 일상 생활에서 레이저 포인터나 손전등은 벽에 밝은 점을 표시하거나 레이저 빛을 방출한 후 특정 장소를 비출 수 있습니다. 이는 벽이 상호 작용할 수 있음, 즉 광선이 충돌할 수 있음을 보여줍니다. 객체/ 객체가 감지되었습니다. Unity에서 다음 API를 사용하여 객체가 감지되었는지 확인하세요.

4.1Raycat 기능

// 基础版API
public static bool Raycast(Ray ray);

public static bool Raycast(Vector3 origin, Vector3 direction, float maxDistance, int layerMask);
public static bool Raycast(Ray ray, out RaycastHit hitInfo);
public static bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance);
                           
// 常用版API
public static bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance, int layerMask);
                           
public static bool Raycast(Ray ray, float maxDistance);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

광선 감지를 사용하려면 다음을 사용해야 합니다.Physics물리학과 관련된 정적 함수가 포함된 라이브러리

위의 오버로드된 함수를 보면 어떤 것을 사용해야 할지 모르는 경우가 많습니다. 실제로 매개변수가 많은 경우에는 필요에 따라 서로 다른 오버로드된 매개변수를 사용합니다.

먼저 API의 기본 버전을 살펴보세요. public static bool Raycast(Ray ray); 반환 값에 따라 광선이 객체에 닿는지 여부를 알 수 있으며, 닿지 않으면 false를 반환합니다.

4.2HitInfo 구조

다른 API의 광선 감지public static bool Raycast(Ray ray, out RaycastHit hitInfo);매개변수가 하나 더 있습니다.hitInfo

hitInfo 매개변수는 광선에 맞은 객체의 정보 구조입니다. 구조가 상대적으로 크기 때문에 Unreal Engine에서 광선 감지에 맞은 객체의 결과와 동일합니다.FHitResult)은 거시적 관점이나 실용적인 관점에서 보면 광선이 물체에 부딪힐 때 물체 자체에 대한 정보와 히트 포인트에 대한 정보를 얻을 수 있습니다.

  • 객체정보, 모든 정보는 변환을 통해 얻을 수 있음
  • 히트 포인트 정보

객체의 이름, 객체의 transfrom 구성 요소, 객체의 태그 등 객체 정보를 얻는 것은 이해하기 쉽습니다.

히트 포인트 정보, 즉 광선이 객체에 닿는 지점(히트 포인트)의 정보를 얻습니다.정상(노멀), 법선, 점정점 색상

다음은RaycastHit구조 정보에는 일반적으로 사용되는 정보에 주석이 추가되어 있습니다.lightmapCoord이는 렌더링에 사용되는 라이트 맵 좌표입니다.

namespace UnityEngine
{
    public struct RaycastHit
    {
        public Collider collider { get; }						// 碰撞器
        public int colliderInstanceID { get; }
        public Vector3 point { get; set; }						// 击中的点(命中点)
        public Vector3 normal { get; set; }						// 命中点的法线
        public Vector3 barycentricCoordinate { get; set; }		 // 重心坐标
        public float distance { get; set; }						// 命中点距离射线起点的距离
        public int triangleIndex { get; }
        public Vector2 textureCoord { get; }
        public Vector2 textureCoord2 { get; }
        public Transform transform { get; }						// Transform组件
        public Rigidbody rigidbody { get; }						// 刚体组件
        public ArticulationBody articulationBody { get; }
        public Vector2 lightmapCoord { get; }
        public Vector2 textureCoord1 { get; }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

즉 말하자면HitInfo우리가 부딪힌 물체에 대한 정보를 저장합니다. 이 정보를 사용하여 더 많은 작업을 수행할 수 있습니다.

4.3 레이어 마스크

유니티에서는layerMask 물리적 충돌, 광선 캐스팅, 광선 감지 및 기타 작업을 제어하는 ​​데 사용되는 개체 레이어 선택 메커니즘입니다.설정으로layerMask를 사용하면 이러한 작업에 어떤 레이어를 포함하거나 제외할지 지정할 수 있습니다.

일반적으로 사용되는 광선 감지 API로 계속 돌아가기 public static bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance, int layerMask);가운데.

위 API에서 maxDistance가 광선의 거리라는 것은 말할 필요도 없습니다. 시스템에는 최대값도 다음과 같이 설정되어 있습니다.Mathf.Infinity. 이 API에는 레이어 마스크인 layerMask 매개변수도 있습니다.

Unity에서 게임오브젝트를 구별하려면 태그를 추가하는 것, 즉 태그를 추가함으로써 게임오브젝트를 구별할 수 있습니다. 그러나 태그는 수동으로 태그 이름을 입력해야 하고, 손으로 입력할 때 실수하기 쉽습니다. 동시에 문자열이기 때문에 Unity 하단의 계산 속도가 약간 느립니다. Unity에는 레이어라는 개념이 있습니다.

Unity의 layerMask에는 32개의 레이어가 포함되어 있으며 그 중 일부는 이미 시스템에서 사용됩니다.Player층, UI레이어 시스템에서 사용하지 않는 레이어가 여전히 많이 있습니다. 레이어를 추가한 다음 게임 개체에 아동 노동 레이어를 추가하여 분류할 수 있습니다.

레이어를 추가하는 방법은 개체의 검사기 패널에서 레이어를 클릭한 다음레이어 추가그게 전부입니다. 그런 다음 수정해야 하는 개체에 대한 레이어를 수동으로 지정합니다.

유니티_레이캐스트_레이어 추가

레이어를 수동으로 추가한 후 사용하는 방법입니다.

존재하다public static bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance, int layerMask);가운데layereMask우리는 그것이int그러나 정수 유형에서는 숫자를 직접 채울 수 없으며 채우기 규칙은 시프트 연산을 사용합니다.

레이어 마스크를 채우는 방법:

  1. 레이어 마스크 값 가져오기

    • 각 레이어에는 0부터 시작하는 해당 정수 값이 있습니다. 예를 들어 기본 레이어(Default)의 값은 0이고, UI 레이어의 값은 보통 5입니다.
    • 특정 레이어에 대한 layerMask를 생성하려면 다음을 사용할 수 있습니다. 1 << LayerMask.NameToLayer("LayerName") . 이는 이 레이어의 layerMask를 나타내는 정수 값을 반환합니다.
  2. 여러 레이어 결합

    • 여러 레이어를 결합하려면 비트별 OR 연산자를 사용할 수 있습니다. | . 예를 들어,layerMask = LayerMask.GetMask("Layer1", "Layer2") "Layer1" 및 "Layer2"를 포함하는 layerMask가 생성됩니다.
  3. 레이어 제외

    • 레이어를 제외하려면 먼저 모든 레이어를 포함하는 마스크를 만든 다음 비트 XOR 연산자를 사용하면 됩니다. ^ 특정 레이어를 제외하려면 예를 들어,layerMask = ~LayerMask.GetMask("ExcludeLayer")
  4. 레이어 확인

    • 객체가 지정된 layerMask에 있는지 확인하려면 다음을 사용할 수 있습니다. layerMask.value & (1 << gameObject.layer) . 결과가 0이 아니면 객체가 레이어 마스크에 있음을 의미합니다.
    // 示例代码
    // 创建一个包含Layer1和Layer2的layerMask
    int layerMask = LayerMask.GetMask("Layer1", "Layer2");
    
    // 排除Layer3
    layerMask = ~LayerMask.GetMask("Layer3");
    
    // 使用layerMask进行射线检测
    RaycastHit hit;
    if (Physics.Raycast(ray, out hit, maxDistance, layerMask))
    {
        // 处理射线击中的对象
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

5. 광선 감지 코드

다음은 hitInfo 매개변수 없이 또는 hitInfo 매개변수와 함께 hitInfo 매개변수를 사용하는 코드입니다.

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

public class RayCast : MonoBehaviour
{
    /
    // 构建一条射线 : 射线产生的起始点 + 射线的方向.
    private Ray ray1 = new Ray(Vector3.zero, Vector3.forward);
    // 射线距离
    private float rayDistance = 100.0f;
   
    // 击中的判定结果
    bool hitResult = false;
    // 射线击中的物体
    private RaycastHit hitInfo;

    void Start()
    {
        // 不含有hitInfo的函数
        bool result1 = Physics.Raycast(Vector3.zero + new Vector3(0,0,10), Vector3.forward, 1000.0f, 1 << LayerMask.NameToLayer("Default"), QueryTriggerInteraction.UseGlobal);
        if (result1)
        {
            Debug.Log("射线击中物体");
        }
        // 含有hitInfo的函数
		hitResult =  Physics.Raycast(ray1, out hitInfo, rayDistance, 1 << LayerMask.NameToLayer("Default"), QueryTriggerInteraction.UseGlobal);
        if (hitResult == true)
        {
            print(hitInfo.collider.name);
            print(hitInfo.transform);
            print(hitInfo.point);
        }
    }       
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

6.레이 알람

광선 감지를 사용하여 회전하는 레이저 포인터를 구현합니다. 물체를 만나면 광선 길이가 줄어들어 광선을 그려야 합니다.

필요한 기술: 광선 감지 + 회전 + LineRender

다음은 코드입니다

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

public class RotateRay : MonoBehaviour
{
    // LinerRender组件
    private LineRenderer lineRenderer = null;

    // 构建一条射线 : 射线产生的起始点 + 射线的方向.
    private Ray ray = new Ray(Vector3.zero, Vector3.forward);
    // 射线距离
    private float rayDistance = 1000.0f;
    // 击中的判定结果
    bool hitResult = false;
    // 射线击中的物体
    private RaycastHit hitInfo;

    void Start()
    {
        // 添加线条绘制组件
        lineRenderer = this.gameObject.AddComponent<LineRenderer>();
        InitLineRenderer(lineRenderer);

        // 设置射线的起点和方向
        ray.origin = this.transform.position;
        ray.direction = this.transform.forward;
    }

    // Update is called once per frame
    void Update()
    {
        // 重新设置射线的位置
        ray.origin = this.transform.position;
        ray.direction = this.transform.forward;

        // 旋转游戏对象 -- 每秒旋转60°
        Quaternion quaternion = Quaternion.AngleAxis(60f * Time.deltaTime, this.transform.up);
        this.transform.rotation *= quaternion;        

        // 判断击中的物体
        hitResult =  Physics.Raycast(ray, out hitInfo, rayDistance, 1 << LayerMask.NameToLayer("Default"), QueryTriggerInteraction.UseGlobal);
        if (hitResult == true)
        {
            print(hitInfo.collider.name);
        }
        // 显示并更新射线
        UpdateLineRendererByRay(lineRenderer, ray, hitResult,hitInfo, rayDistance);
    }

    /// <summary>
    /// 初始化线条渲染组件
    /// </summary>
    void InitLineRenderer(LineRenderer lineRenderer)
    {
        // lineRenderer = this.gameObject.AddComponent<LineRenderer>();
        lineRenderer.positionCount = 2;
        lineRenderer.startWidth = 0.2f;
        lineRenderer.endWidth = 0.2f;
        lineRenderer.startColor = Color.red;
        lineRenderer.endColor = Color.green;
    }

    /// <summary>
    /// 击中物体的时候修改射线的长度
    /// </summary>
    /// <param name="lineRenderer">lineRenderer组件</param>
    /// <param name="ray">射线</param>
    /// <param name="hitResult">是否命中物体</param>
    /// <param name="hitInfo">命中物体信息</param>
    /// <param name="rayDistance">射线距离</param>
    void UpdateLineRendererByRay(LineRenderer lineRenderer,Ray ray, bool hitResult, RaycastHit hitInfo, float rayDistance)
    {
        if (lineRenderer == null || lineRenderer.positionCount < 2)
        {
            Debug.Log("LineRender组件不可以使用");
            return;
        }

        // 修改起点位置
        lineRenderer.SetPosition(0, ray.origin);
        // 修改终点位置
        if (hitResult == true)
        {
            lineRenderer.SetPosition(1, hitInfo.point);
        }
        else 
        {
            lineRenderer.SetPosition(1, ray.GetPoint(rayDistance));
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93

최적화된 코드

using UnityEngine;

public class RotateRay : MonoBehaviour
{
    private LineRenderer lineRenderer;
    private Ray ray;
    private float rayDistance = 1000.0f;
    private RaycastHit hitInfo;

    void Start()
    {
        lineRenderer = this.gameObject.AddComponent<LineRenderer>();
        InitLineRenderer(lineRenderer);
        ray = new Ray(Vector3.zero, Vector3.forward);
    }

    void Update()
    {
        UpdateRayPosition();
        RotateObject();
        PerformRaycast();
        UpdateLineRenderer();
    }

    void InitLineRenderer(LineRenderer lineRenderer)
    {
        lineRenderer.positionCount = 2;
        lineRenderer.startWidth = 0.2f;
        lineRenderer.endWidth = 0.2f;
        lineRenderer.startColor = Color.red;
        lineRenderer.endColor = Color.green;
    }

    void UpdateRayPosition()
    {
        ray.origin = this.transform.position;
        ray.direction = this.transform.forward;
    }

    void RotateObject()
    {
        Quaternion rotation = Quaternion.AngleAxis(60f * Time.deltaTime, this.transform.up);
        this.transform.rotation *= rotation;
    }

    void PerformRaycast()
    {
        int layerMask = 1 << LayerMask.NameToLayer("Default");
        hitInfo = new RaycastHit(); // 初始化hitInfo,避免未击中时的错误
        Physics.Raycast(ray, out hitInfo, rayDistance, layerMask, QueryTriggerInteraction.UseGlobal);
    }

    void UpdateLineRenderer()
    {
        if (lineRenderer == null || lineRenderer.positionCount < 2)
        {
            Debug.LogError("LineRenderer component is not available or not properly initialized.");
            return;
        }

        lineRenderer.SetPosition(0, ray.origin);
        lineRenderer.SetPosition(1, hitInfo.collider != null ? hitInfo.point : ray.GetPoint(rayDistance));
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

렌더링은 다음과 같습니다

유니티_레이캐스트.gif

7. 마우스 클릭으로 특수 효과 생성

using UnityEngine;

public class  CameraRay: MonoBehaviour
{
    // 射线距离
    private float rayDistance = 1000.0f;
    // 特效Prefab--外部可以自定义特效
    public GameObject effectPrefab;

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            // 从摄像机发出一条射线
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hitInfo;

            // 如果射线击中了指定层级的物体
            if (Physics.Raycast(ray, out hitInfo, rayDistance, LayerMask.GetMask("Wall")))
            {
                // 生成特效 --格局法线来计算特效位置
                GameObject effectObject = Instantiate(effectPrefab, hitInfo.point, Quaternion.LookRotation(hitInfo.normal));
                // 销毁特效,参数为延迟时间
                Destroy(effectObject, EffectPrefab.GetComponent<ParticleSystem>().main.duration);
            }
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29