Unity中的射线检测

前言

什么是射线检测?

射线检测(Raycasting)是游戏开发中一种常用的碰撞检测技术,核心原理是发射一条虚拟的"射线"(有起点和方向的无限长直线),检测这条射线是否与场景中的碰撞体(Collider)相交,并获取相交的详细信息。它比传统的碰撞体检测更灵活、高效,尤其适合处理"点到面"“线到体” 的交互场景。

一、怎么创建射线?

Ray ray = new Ray(transform.position, Vector3.down);

transform.position 表示射线的起点(origin),即当前挂载该脚本的游戏对象在世界空间中的位置。

Vector3.down 表示射线的方向(direction),等价于 new Vector3(0, -1, 0),即沿世界坐标系的 Y 轴负方向(向下)延伸。

最后通过 new Ray(origin,direction) 创建射线对象,这条射线可用于后续的射线检测(如 Physics.Raycast),判断射线是否与场景中的碰撞体相交,常用于实现鼠标交互、碰撞检测等功能(比如检测射线是否击中某个物体)。

二、基本原理

射线的定义:射线由两部分组成

起点(Origin):射线开始的位置(通常是某个物体的位置,如摄像机、角色、鼠标在世界空间的对应点)。

方向(Direction):射线延伸的方向(单位向量,如向前、向下、鼠标指向的方向等)。

检测逻辑:

射线沿方向无限延伸(或按设定的最大距离延伸),当它与场景中带有 Collider 组件 的物体相交时,会返回 “击中” 状态,并记录相交的详细信息(如击中的点、法线、所在物体等)。

注意:射线只能与带有 Collider 的物体发生检测(触发器 Trigger 需特殊设置是否检测)。

其在unity文档中的定义为:
Raycast文档
向场景中的所有碰撞体投射一条射线,该射线起点为 origin,朝向 direction,长度为 maxDistance

可以选择写入一个 LayerMask,以过滤掉不想生成与其碰撞的碰撞体。

也可以通过指定 queryTriggerInteraction 来控制是让触发碰撞体生成命中效果,还是使用全局
Physics.queriesHitTriggers 设置。

三、射线检测的典型应用场景

1. 玩家交互:连接 2D 输入与 3D 世界

玩家通过鼠标、触屏等 2D 输入设备与 3D 场景交互时,射线检测是"桥梁",负责将屏幕坐标映射到 3D 空间中的物体。

(1)鼠标拾取 / 选中物体
场景:RPG 游戏中点击 NPC 对话、策略游戏中选中单位、解谜游戏中点击道具。
实现思路:从摄像机向鼠标位置发射射线,检测射线击中的物体,触发交互逻辑。

示例代码:

void Update() {
    // 鼠标左键点击时检测
    if (Input.GetMouseButtonDown(0)) 
    {
        // 从主摄像机发射射线到鼠标位置(屏幕坐标转世界射线)
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        // 检测射线(最大距离100米,忽略UI层)
        if (Physics.Raycast(ray, out RaycastHit hit, 100f, ~LayerMask.GetMask("UI"))) 
        {
            // 若击中NPC,触发对话
            if (hit.transform.CompareTag("NPC")) 
            {
                hit.transform.GetComponent<NPCDialogue>().StartDialogue();
            }
            // 若击中道具,拾取物品
            else if (hit.transform.CompareTag("Item")) 
            {
                Destroy(hit.transform.gameObject);
                Inventory.Instance.AddItem(hit.transform.name);
            }
        }
    }
}

(2)鼠标悬浮提示
场景:MOBA
游戏中鼠标悬停在英雄上显示属性、策略游戏中悬停在建筑上显示信息。
实现思路:每帧发射射线检测鼠标位置,若持续击中某物体,显示其信息面板。

示例代码:

private GameObject currentHoverObject; // 当前悬浮的物体

void Update() 
{
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

    if (Physics.Raycast(ray, out RaycastHit hit, 200f)) 
    {
        // 若悬浮在新物体上,更新当前物体并显示信息
        if (hit.transform != currentHoverObject) 
        {
            // 隐藏上一个物体的信息
            if (currentHoverObject != null) 
            {
                currentHoverObject.GetComponent<HoverInfo>().HidePanel();
            }
        
            // 显示当前物体的信息
            currentHoverObject = hit.transform.gameObject;
            currentHoverObject.GetComponent<HoverInfo>().ShowPanel();
        }
    } 
    else 
    {
        // 未击中物体,隐藏信息
        if (currentHoverObject != null) 
        {
            currentHoverObject.GetComponent<HoverInfo>().HidePanel();
            currentHoverObject = null;
        }
    }
}
2. 游戏机制:实现核心玩法逻辑

射线检测可用于判断 “是否命中”“是否可达”"是否可见"等核心规则。

这是很多玩法的基础,地面检测与角色移动是一个很经典的问题。

场景:第三人称游戏中角色贴地移动(避免悬浮)、跳跃后判断是否落地、车辆行驶时检测地面高度。
实现思路:从角色脚下发射向下的射线,检测与地面的距离,动态调整角色位置或状态。

示例图:
角色贴地射线检测
示例代码:

void isOnGroundCheck()
{
    isOnGround = Physics2D.Raycast(groundCheck.position, Vector2.down,groundCheckDistance, groundLayer);
}

private void OnDrawGizmos()
{
    //程序编写中可视化
    Gizmos.DrawLine(transform.position, new Vector3(transform.position.x, transform.position.y - groundCheckDistance));
    //可以理解为根据提供的两个点,连成一条直线
}
3. AI 行为:实现智能决策

射线检测是 AI “感知环境” 的核心手段,帮助 AI判断视野、目标可见性、攻击范围等。

视线检测(AI 视野)与攻击范围检测
场景:潜行游戏中敌人是否看到玩家、AI 守卫是否发现入侵者,AI近战攻击时判断是否在攻击距离内、远程 AI 判断是否可射击目标。

实现思路:从 AI 眼睛向目标(玩家)发射射线,若射线未被障碍物阻挡,则判定
“看到目标”,向目标发射射线,若距离小于攻击范围且无遮挡,则触发攻击。

示例图:
敌人视野范围
扇形区域为怪物视野

敌人视野被遮挡
被障碍物遮住,怪物和玩家之间不会产生射线

敌人发现玩家
无障碍物遮挡,且在怪物视线之内,发出射线

怪物视野(可操控视野角度范围)示例代码:

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

public class enemy : MonoBehaviour
{
    [Header("视野设置")]
    [Tooltip("AI的视野距离")]
    public float viewDistance = 10f;

    [Tooltip("AI的视野角度(度)")]
    [Range(0, 180)]
    public float viewAngle = 60f;

    [Tooltip("玩家图层")]
    public LayerMask playerLayer;

    [Tooltip("障碍物图层")]
    public LayerMask obstacleLayer;

    [Header("引用")]
    [Tooltip("AI的眼睛位置(射线起点)")]
    public Transform eyePosition;

    [Tooltip("玩家Transform组件")]
    public Transform player;

    [Header("调试")]
    [Tooltip("是否显示视野Gizmos")]
    public bool showGizmos = true;

    // 玩家是否在视野内
    public bool IsPlayerInSight { get; private set; }

    private void Update()
    {
        CheckForPlayer();
    }

    /// 检测玩家是否在视野范围内且可见
    private void CheckForPlayer()
    {
        if (player == null)
        {
            IsPlayerInSight = false;
            return;
        }

        // 计算AI到玩家的方向和距离
        Vector2 directionToPlayer = (player.position - eyePosition.position).normalized;
        float distanceToPlayer = Vector2.Distance(eyePosition.position, player.position);
        Debug.Log(distanceToPlayer);
        // 1. 检查距离:玩家是否在视野范围内
        if (distanceToPlayer > viewDistance)
        {
            IsPlayerInSight = false;
            return;
        }
        Debug.Log(IsPlayerInSight);

        // 2. 检查角度:玩家是否在视野角度内
        float angleToPlayer = Vector2.Angle(transform.right, directionToPlayer);
        if (angleToPlayer > viewAngle / 2)
        {
            IsPlayerInSight = false;
            return;
        }

        // 3. 检查遮挡:射线是否能击中玩家(无障碍物)
        if (Physics2D.Raycast(eyePosition.position, directionToPlayer, distanceToPlayer, obstacleLayer))
        {
            // 射线被障碍物阻挡
            IsPlayerInSight = false;
            return;
        }

        // 4. 检查是否真的击中了玩家
        RaycastHit2D hit = Physics2D.Raycast(eyePosition.position, directionToPlayer, distanceToPlayer, playerLayer);
        Debug.Log(hit.collider);
        if (hit.collider != null)
        {
            IsPlayerInSight = true;
            OnPlayerDetected();
        }
        else
        {
            IsPlayerInSight = false;
        }
    }

   
    private void OnPlayerDetected()
    {
        // 可以在这里添加检测到玩家后的逻辑
        // 例如:切换AI状态、播放警报音效、追击玩家等
        Debug.Log("发现玩家!");
    }

    /// 绘制视野范围的Gizmos
    private void OnDrawGizmos()
    {
        if (!showGizmos || eyePosition == null) return;

        // 绘制视野距离
        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(eyePosition.position, viewDistance);

        // 绘制视野角度边界
        Vector2 leftBoundary = Quaternion.Euler(0, 0, viewAngle / 2) * transform.right * viewDistance;
        Vector2 rightBoundary = Quaternion.Euler(0, 0, -viewAngle / 2) * transform.right * viewDistance;

        Gizmos.DrawLine(eyePosition.position, eyePosition.position + (Vector3)leftBoundary);
        Gizmos.DrawLine(eyePosition.position, eyePosition.position + (Vector3)rightBoundary);

        // 如果发现玩家,绘制指向玩家的射线
        if (IsPlayerInSight && player != null)
        {
            Gizmos.color = Color.red;
            Gizmos.DrawLine(eyePosition.position, player.position);
        }
    }
}
ine(eyePosition.position, eyePosition.position + (Vector3)rightBoundary);

        // 如果发现玩家,绘制指向玩家的射线
        if (IsPlayerInSight && player != null)
        {
            Gizmos.color = Color.red;
            Gizmos.DrawLine(eyePosition.position, player.position);
        }
    }
}
资源下载链接为: https://pan.xunlei.com/s/VOYaEvb5YbXDcdRVMg3ANOaDA1?pwd=sjwe data.py 用于创建数据集。 makelabel.py 的功能是融合数字与背景并保存。其中,一张背景图会在四个象限随机添加一个数字,且几乎无重叠。标签形状为(32,32,11),32×32 是热图输出大小,每个热图像素对应原图 4×4 的方格,每个方格作为分类器,可分出 11 类,0-9 对应数字,10 代表背景。fusion_img 函数将一个数字融合到背景图的随机位置;fusion_4img 函数考虑到单个数字太少,可处理四个数字,输入参数为(背景,(图片 1,标签 1),(图片 2,标签 2)...),输出为图片(0-255)和标签。 model.py 是模型文件,最终占用 192kb 内存。 test.py 为测试脚本,包含两个定义的函数,加载模型后可进行单张测试和视频测试,使用时注释另一个即可。onepoint 函数输入矩阵和点的 xy 坐标,逐行扫描该点周围 6 行的像素,若为 1(表示有物体),就将对应方格的 xy 加入数组并置零。扫描完周围 6 行后,若总点数超过 10 个,判定为一个物体,对所有 xy 分别求平均,得到物体中心。 单张图片后处理过程:获取输出的 32×32×11 矩阵,先扫描 32×32 区域,对每行取 argmax,若不属于背景类,说明可能存在物体,再设阈值过滤部分误识别框,然后将该点值置为 1 作为标记。 再次扫描矩阵时,为避免越界,从第 6 行开始到 25 行结束。若扫描到 1,如(20,20,3)这一格为 1,就取矩阵对应 3 的那一层(32×32 大小),将该矩阵和(20,20)坐标传入 onepoint 函数,返回中心,类别为 3。一般不会误判,若一个数字有两种可能且两种像素数都超 10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值