You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity Raycast行为不一致:有时碰撞起始碰撞器有时无碰撞

Unity Raycast行为不一致问题:射线偶尔与起始碰撞器触发碰撞

我在Unity开发中遇到Raycast行为不一致的问题:射线有时会与它起始所在的碰撞器发生碰撞,有时则不会。

问题示意图
图片文字已完整说明问题

核心代码片段

以下是生成射线的核心代码(可忽略Debug.DrawRay()):

RaycastHit hitInfo;
RaycastHit wall;
RaycastHit backFace;

if(Physics.Raycast(barrelEnd.transform.position, barrelEnd.transform.forward, out hitInfo)) {
            
    Debug.DrawRay(barrelEnd.transform.position, barrelEnd.transform.forward*5, Color.green, 5f, false);
    Instantiate(rayOneHit, hitInfo.point, Quaternion.identity);
    Enemy target = hitInfo.transform.GetComponent<Enemy>();
    
    if(target != null) {

        if(Physics.Raycast(hitInfo.point, barrelEnd.transform.forward, out wall)) {
            
            Debug.DrawRay(hitInfo.point, (hitInfo.point - barrelEnd.transform.position)*2, Color.blue, 5f, false);
            Instantiate(rayTwoHit, wall.point, Quaternion.identity);

            if(Physics.Raycast(wall.point, ((hitInfo.point - wall.point)/Vector3.Distance(hitInfo.point,wall.point)), out backFace)) { 
                
                Debug.DrawRay(wall.point, (hitInfo.point - wall.point)*10, Color.red, 5f, false);
                
                Vector3 hitLocation = new Vector3(backFace.point.x, backFace.point.y, backFace.point.z);
                Instantiate(hitPoint, hitLocation, Quaternion.identity);
            }
        }
        target.dealDamage(damage);
    }
    
    lineRenderer.SetPosition(0, aim.transform.position);
    lineRenderer.SetPosition(1, hitInfo.point);
}

调用逻辑:

void Update() {
    if(Input.GetButtonDown("Fire1")) {
        Shoot();
    }
}

完整代码

using System.Collections;
using UnityEngine;

public class Shooting : MonoBehaviour {

    [SerializeField] Transform aim;
    [SerializeField] LineRenderer lineRenderer;
    public float damage = 30f;
    public Transform barrelEnd;
    public Transform crosshair;
    public Light muzzleFlash;
    public Object hitPoint;
    public Object rayOneHit;
    public Object rayTwoHit;
    public LayerMask enemyLayer;
    
    //public Light muzzleFlashSelf;
    
   // public GameObject projectile;
    public float force = 100f;

    void Start() {
        Cursor.visible = false;
        muzzleFlash.enabled = false;
        //muzzleFlashSelf.enabled = false;
    }

    void Update() {
        if(Input.GetButtonDown("Fire1")) {
            Shoot();
        }
    }

    void Shoot() {
        RaycastHit hitInfo;
        RaycastHit wall;
        RaycastHit backFace;

        StartCoroutine(DrawLine());
        StartCoroutine(Flash());

        if(Physics.Raycast(barrelEnd.transform.position, barrelEnd.transform.forward, out hitInfo)) {
            
            Debug.DrawRay(barrelEnd.transform.position, barrelEnd.transform.forward*5, Color.green, 5f, false);
            Instantiate(rayOneHit, hitInfo.point, Quaternion.identity);
            Enemy target = hitInfo.transform.GetComponent<Enemy>();
            
            if(target != null) {

                if(Physics.Raycast(hitInfo.point, barrelEnd.transform.forward, out wall)) {
                    
                    Debug.DrawRay(hitInfo.point, (hitInfo.point - barrelEnd.transform.position)*2, Color.blue, 5f, false);
                    Instantiate(rayTwoHit, wall.point, Quaternion.identity);

                    if(Physics.Raycast(wall.point, ((hitInfo.point - wall.point)/Vector3.Distance(hitInfo.point,wall.point)), out backFace)) { 
                        
                        Debug.DrawRay(wall.point, (hitInfo.point - wall.point)*10, Color.red, 5f, false);
                        
                        Vector3 hitLocation = new Vector3(backFace.point.x, backFace.point.y, backFace.point.z);
                        Instantiate(hitPoint, hitLocation, Quaternion.identity);
                    }
                }
                target.dealDamage(damage);
            }
            
            lineRenderer.SetPosition(0, aim.transform.position);
            lineRenderer.SetPosition(1, hitInfo.point);
        }
    }

    IEnumerator DrawLine() {
        lineRenderer.enabled = true;
        float t = 0;
        float time = 0.01f;

        Vector3 orig = lineRenderer.GetPosition(0);
        Vector3 orig2 = lineRenderer.GetPosition(1);

        lineRenderer.SetPosition(1, orig);

        for (; t < time; t += Time.deltaTime) {
            yield return null;
        }

        lineRenderer.SetPosition(1, orig2);
        lineRenderer.enabled = false;
    }

    IEnumerator Flash() {
        float t = 0;
        float time = 0.1f;
        muzzleFlash.enabled = true;
        //muzzleFlashSelf.enabled = true;

        for (; t < time; t += Time.deltaTime) {
            yield return null;
        }

        muzzleFlash.enabled = false;
        //muzzleFlashSelf.enabled = false;
    }
}

已尝试的无效方案

  • 让第二个射线从敌人内部稍许位置起始,无效果
  • 使用~enemyMask排除敌人层,导致第二个射线完全不触发

可行解决方案

1. 精准控制射线检测的碰撞体/层

当从敌人碰撞点发射射线时,默认可能因浮点精度问题检测到敌人自身,可通过以下方式规避:

  • 忽略当前击中的碰撞体:使用Physics.Raycast的重载参数,直接排除第一个射线击中的敌人碰撞器:
    // 修改第二个Raycast,忽略hitInfo对应的碰撞体
    if(Physics.Raycast(hitInfo.point, barrelEnd.transform.forward, out wall, Mathf.Infinity, -1, QueryTriggerInteraction.Ignore)) {
        // 确保检测到的不是同一碰撞体
        if(wall.collider != hitInfo.collider) {
            // 执行后续逻辑
        }
    }
    
  • 修正层掩码设置:如果使用~enemyLayer无效,检查敌人层是否仅包含敌人,墙壁是否在其他层。正确设置后,用层掩码只检测墙壁所在层:
    // 假设wallLayer是你定义的墙壁层
    if(Physics.Raycast(hitInfo.point, barrelEnd.transform.forward, out wall, Mathf.Infinity, wallLayer)) {
        // 后续逻辑
    }
    

2. 合理偏移射线起始点

将第二个射线的起始点沿前进方向偏移一小段距离,确保离开敌人碰撞器表面:

// 偏移量根据碰撞器大小调整,建议0.01f~0.1f
Vector3 offsetStart = hitInfo.point + barrelEnd.transform.forward * 0.05f;
if(Physics.Raycast(offsetStart, barrelEnd.transform.forward, out wall)) {
    // 后续逻辑
}

3. 优化碰撞体设置

  • 如果敌人使用Mesh Collider,勾选convex选项,提升射线检测稳定性(注意复杂Mesh不适合勾选)
  • 确保敌人碰撞体的isTrigger设置符合需求,避免触发器干扰射线检测

4. 调整射线检测精度

在Unity编辑器的Edit > Project Settings > Physics中,调整Raycast Hit Margin参数,适当增大数值可以缓解浮点精度导致的检测异常。

内容的提问来源于stack exchange,提问作者beholdthespoon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 05:50:38