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

Unity Raycast同时命中两物体 交互触发顺序异常解决方案

问题原因

两个核心逻辑错误导致误触发:

  1. 射线检测重复冗余:玩家脚本和可销毁对象脚本各自独立跑射线检测,没有统一的交互优先级判断,点击时两个逻辑同时触发。
  2. 触发逻辑存在漏洞:
    • 用Input.GetButton监听输入,按住鼠标时每帧都会触发交互,不是单次点击触发
    • InteractionRaycast的销毁判断完全没校验射线命中的是不是自身,只要射线产生过命中结果就会销毁挂载对象
    • 可移动交互对象的_isOpen状态在动画启动时就立刻切换,动画播放过程中会被误判为"已经移开",导致射线穿到下方对象触发销毁
    • 所有射线检测都没判断Physics.Raycast的返回值,会残留上一帧的命中数据导致误触发

修复方案

第一步:精简InteractionRaycast逻辑

删掉脚本里独立的射线检测代码,只保留公开的销毁方法供玩家交互脚本统一调用,避免多脚本并行检测冲突。
修改后InteractionRaycast.cs代码:

public class InteractionRaycast : MonoBehaviour
{
    // 供外部调用的销毁方法
    public void DestroySelf()
    {
        Destroy(gameObject);
    }
}

第二步:统一射线检测逻辑到Interactor

所有交互检测由玩家身上的脚本统一管理,优先处理距离最近的命中对象,触发完高优先级交互就终止逻辑,避免穿透误触。
修改后Interactor.cs代码:

public class Interactor : MonoBehaviour
{
    [SerializeField]
    private float _interctRange;
    private Camera _camera;
    private RaycastHit _hit;

    void Start()
    {
        _camera = Camera.main;
    }

    void Update()
    {
        // 改为鼠标按下瞬间触发,避免按住时每帧重复执行
        if (Input.GetButtonDown("Fire1"))
        {
            // 先判断射线检测是否有效,避免残留上一帧的命中数据
            if (Physics.Raycast(_camera.transform.position, _camera.transform.forward, out _hit, _interctRange))
            {
                // 优先判断最近命中对象是不是可移动交互物(比如枕头)
                InteractionObject hitInteractObj = _hit.transform.GetComponent<InteractionObject>();
                if (hitInteractObj != null)
                {
                    hitInteractObj.PerfomAction();
                    // 触发完直接返回,不处理后方物体,避免穿模误触
                    return;
                }

                // 最近对象不是可移动物体时,再判断是不是可销毁对象
                InteractionRaycast hitDestroyObj = _hit.transform.GetComponent<InteractionRaycast>();
                if (hitDestroyObj != null)
                {
                    hitDestroyObj.DestroySelf();
                }
            }
        }
    }
}

第三步:修正InteractionObject状态切换时机

把_isOpen的状态切换从动画启动时,改为动画播放完成后,避免动画过程中误判物体已经移开。
修改后InteractionObject.cs代码:

public class InteractionObject : MonoBehaviour
{
    [SerializeField]
    private Vector3 _openPosition, _closePosition;
    [SerializeField]
    private float _animationTime;
    private Hashtable _iTweenArgs;
    public bool _isOpen;

    void Start()
    {
        _iTweenArgs = iTween.Hash();
        _iTweenArgs.Add("position", _openPosition);
        _iTweenArgs.Add("time", _animationTime);
        _iTweenArgs.Add("islocal", true);
        // 绑定动画完成回调
        _iTweenArgs.Add("oncomplete", "OnMoveAnimationComplete");
    }

    public void PerfomAction()
    {
        if (_isOpen)
        {
            _iTweenArgs["position"] = _closePosition;
        }
        else
        {
            _iTweenArgs["position"] = _openPosition;
        }
        iTween.MoveTo(gameObject, _iTweenArgs);
    }

    // 动画播放完成后再更新开合状态
    private void OnMoveAnimationComplete()
    {
        _isOpen = !_isOpen;
    }
}

额外优化建议

  • 可以给不同类型的交互对象设置独立Layer,射线检测时通过LayerMask指定检测层级,提升检测效率,进一步降低误判概率
  • 尽量不要用公开字段存储交互状态,后续可以封装为只读属性,避免外部脚本随意修改状态导致逻辑异常
  • 如果使用带碰撞体的物体做动效,建议给iTween加"rigidbody"参数传入物体的Rigidbody组件,避免碰撞体更新滞后于物体移动导致的射线穿模

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 09:48:53