技術共有

Unity--光線検出--RayCast

2024-07-12

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

Unity – レイ検出 – RayCast

1.放射線検査の意味

光線検出は、名前の通り、光線を使用して物体または複数の物体に当たるかどうかを検出します。

放射線検査は 2 つの部分で構成されます。 光線そして検出

2. X 線検査はどこで使用できますか?

  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での光線検出

光線を構築したり表示したりするだけでは意味がありません。ツールを手に持って、ツールなしで作業するのと同じです。

物体検出に光線を使用する方法。

よく考えてください。私たちの日常生活では、レーザー ポインターや懐中電灯がレーザー光を放射した後、壁に光点を表示したり、特定の場所を照らしたりすることがあります。これは、壁が相互作用できる、つまり光線が衝突する可能性があることを示しています。 the object/ オブジェクトが検出されました。 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); 戻り値に従って、光線がオブジェクトに当たったかどうかがわかります。当たった場合は true が返され、当たらなかった場合は false が返されます。

4.2HitInfo構造体

別の API での光線検出public static bool Raycast(Ray ray, out RaycastHit hitInfo);パラメータがもう 1 つありますhitInfo

hitInfo パラメータは、レイが当たったオブジェクトの情報構造です。この構造は比較的大きいため、Unreal Engine のレイ検出によるオブジェクトの結果と同じです (FHitResult) はマクロの観点から見ても、実際的な観点から見ても、光線がオブジェクトに当たると、オブジェクト自体に関する情報とヒット ポイントに関する情報を取得できます。

  • オブジェクト情報、すべての情報は変換を通じて取得できます
  • ヒットポイント情報

オブジェクトの名前、オブジェクトの変換コンポーネント、オブジェクトのタグなど、オブジェクトの情報を取得するのは簡単です。

ヒットポイントの情報、つまり光線が物体に当たった点(ヒットポイント)の座標などの情報を取得します。普通(法線)、法線平面、点頂点カラー

以下は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 には、レイヤー マスクであるレイヤーマスクというパラメーターもあります。

Unityでゲームオブジェクトを区別するにはタグを追加する、つまりTagを付けることで区別することができますが、タグ名を手入力する必要があり、手入力だと間違えやすいです。同時に、文字列であるため、Unity の下部の計算速度は少し遅くなります。 Unity にはレイヤーの概念があります。

Unity のlayerMask には 32 のレイヤーが含まれており、そのうちのいくつかはシステムによってすでに使用されています。Player層、 UIレイヤー: システムによって使用されていないレイヤーがまだたくさんあります。レイヤーを追加して、ゲーム オブジェクトに児童労働レイヤーを追加することでそれらを分類できます。

レイヤーを追加するには、オブジェクトの「インスペクター」パネルで「レイヤー」をクリックし、レイヤーの追加これで、変更が必要なオブジェクトのレイヤーを手動で指定できます。

Unity_RayCast_レイヤーの追加

手動でレイヤーを追加した後の使い方。

存在するpublic static bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance, int layerMask);真ん中layereMask私たちは、それがint整数の型ですが、数値を直接入力することはできません。入力ルールではシフト演算が使用されます。

LayerMask を入力する方法:

  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 でない場合は、オブジェクトが 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.光線検出コード

以下は、 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

レンダリングは次のとおりです

Unity_RayCast.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