Technology Sharing

Unity--RayCast

2024-07-12

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

Unity – Raycast

1. The meaning of X-ray detection

Ray detection, as the name suggests, uses a ray to detect whether it hits an object/objects

Radiographic testing consists of two parts: RaysandDetection

2. Where can X-ray detection be used?

  1. shooting game
    • Player aiming and shooting: Detecting whether the player's line of sight intersects with an enemy or other target.
    • Bullet trajectories and effects: simulate the flight path and impact effects of bullets.
  2. Interaction and UI
    • Mouse click detection: Detects whether the player's mouse click intersects with a game object or UI element.
    • Touchscreen interactions: Detect whether a player's touch intersects specific game elements on mobile devices.
  3. Character Controller and AI
    • Vision detection: NPCs or enemies detect players or other characters within a certain range.
    • Collision Avoidance: AI characters use raycasting to avoid collisions when moving.
  4. Virtual Reality (VR) and Augmented Reality (AR)
    • Eye or hand tracking: Detecting where the player is looking or where their hands are in VR.
    • Object Interaction: Detecting whether the player intersects with virtual objects in AR.

3. Rays in Unity

Scene rays can be seen in many places in daily life, such asflashlight,ppt laser page turning pen, in theCyclops

A ray consists of a starting point, a direction and a distance, namely: origin , direction anddistance

In physics, the distance of a ray is infinite, so a ray in physics has only one starting point and one direction. In the game, the maximum distance of a ray is also limited by the system. Generally, we define a custom distance, such as 1000.0 meters. The following is a description of Ray.

In Unity, Ray is a structure, and the basic members of the structure volume includeorigin, direction andGetPoint. That is, the starting point, direction, and position of a point at a certain distance along the ray. The following is the code for Ray in Unity

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 Constructing Rays

According to the above code, we can see that we can directly use the Ray constructor to construct a ray, for example:

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

According to the above code, we can see that the starting point of the ray is the origin of the world coordinates in Unity (0,0,0), and the direction of the ray is the forward direction of the world coordinates.

3.2 How to display rays

In reality, our laser pointer and flashlight are visible, but the rays in Unity are invisible. Therefore, if we want to display the rays, we can use the following methods:

  • useDebug.DrawRay()Display ray
  • useLineRendererThe component draws the ray

4. Ray Detection in Unity

Just constructing or displaying the rays is meaningless, which is like holding a tool but not using it to do the work.

How to use rays to perform object detection.

Think about it carefully: In daily life, our laser pointer or flashlight can emit a light spot on the wall or illuminate a certain place. This shows that the wall is interactive, in other words, the ray hits the object/detects the object. Use the following API in Unity to determine whether the object is detected.

4.1Raycat Function

// 基础版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

To use ray tracing, you need to use Unity'sPhysicsLibrary, which contains static functions related to physics

Seeing so many overloaded functions above, we often don't know which one to use. In fact, only the basic ones are needed. The ones with more parameters actually use different overloaded parameters according to the needs.

First look at the basic version of the API public static bool Raycast(Ray ray); Based on the return value, we can know whether the ray hits an object. If it hits, it returns true, and if it does not hit, it returns false.

4.2HitInfo Structure

Ray Detection in Another APIpublic static bool Raycast(Ray ray, out RaycastHit hitInfo);There is one more parameter inhitInfo

The hitInfo parameter is the information structure of the object hit by the ray. This structure is relatively large, which means it contains more information. This is different from the result of the ray detection hitting the object in Unreal Engine (FHitResult) is similar. From a macroscopic or practical point of view, when a ray hits an object, we can obtain information about the object itself and the point of impact.

  • Object information, all information can be obtained through transfor
  • Hit point information

Getting the information of an object is easy to understand: such as the name of the object, the object's transfrom component, the object's tag...

Get the information of the hit point, that is, the point where the ray hits the object (hit point): such as the coordinates of the point,Normal(normal), normal plane, the pointVertex Color

The following areRaycastHitThe information of the structure, the commonly used ones have been commented. There are also many commonly used ones without comments, such aslightmapCoordThese are lightmap coordinates, used for rendering.

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

That is to sayHitInfoSaves the information about the object we hit, we can do more with this information

4.3 layerMask

In Unity,layerMask It is an object layer selection mechanism used to control physical collision, raycasting, ray detection, etc.layerMask, you can specify which layers should be included or excluded from these operations.

Let’s go back to the common ray detection API public static bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance, int layerMask);middle.

In the above API, maxDistance is self-explanatory, it is the distance of the ray, and the system also has a maximum value set toMathf.Infinity. There is also a parameter layerMask in this APi, which is the layer mask.

To distinguish game objects in Unity, we can add tags to distinguish game objects. However, tags are more troublesome, and we need to enter the tag name manually, which is easy to make mistakes. At the same time, because it is a string, the speed is slower when it is calculated at the bottom layer of Unity. In Unity, there is a concept of layers.

The layerMask in Unity contains 32 layers, some of which are already used by the system, such asPlayerlayer, UILayer. There are still many layers that are not used by the system. We can add layers and then add child labor layers to game objects to classify them.

How do I add a layer? Click Layer on the Insepector panel of any object, then clickAddLayerThat's it, then manually specify the layer for the objects whose levels need to be modified.

Unity_RayCast_AddLayer

How to use after manually adding a Layer.

existpublic static bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance, int layerMask);middlelayereMaskWe understand that it makes aintHowever, we cannot fill in the numbers directly. The filling rules use shift operations.

How to fill in layerMask:

  1. Get the Layer Mask value

    • Each layer has a corresponding integer value, starting at 0. For example, the value of the Default layer is 0, and the UI layer is usually 5.
    • To create a layerMask for a specific layer, you can use 1 << LayerMask.NameToLayer("LayerName"). This returns an integer value representing the layerMask of the layer.
  2. Combining multiple layers

    • If you want to combine multiple layers, you can use the bitwise OR operator. |.For example,layerMask = LayerMask.GetMask("Layer1", "Layer2") A layerMask will be created, including "Layer1" and "Layer2".
  3. Exclusion Layer

    • To exclude a layer, you can first create a mask containing all layers and then use the bitwise XOR operator ^ to exclude specific layers. For example,layerMask = ~LayerMask.GetMask("ExcludeLayer")
  4. Check Layer

    • To check if an object is in a given layerMask, you can use layerMask.value & (1 << gameObject.layer)If the result is not 0, it means the object is in the layerMask.
    // 示例代码
    // 创建一个包含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. X-ray detection code

The following is the code using the hitInfo parameter without and with 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.Ray alarm

Use ray detection to realize a rotating laser pen. When encountering an object, the ray length decreases. It is necessary to draw the ray

Skills needed: Ray Detection + Rotation + LineRender

Following is the code

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

Optimized code

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

The effect diagram is as follows

Unity_RayCast.gif

7. Mouse click to generate a special effect

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