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

Unity+Meta Quest中RenderTexture触摸射线投射坐标偏移修正

问题修复方案

针对你遇到的触摸无响应和射线坐标偏移问题,以下是修正后的代码及关键修改说明:

修正后的完整代码

using UnityEngine;
using Photon.Pun;
using UnityEngine.UI;

public class PanelManager : MonoBehaviourPun
{
    [Header("References")]
    public Camera displayRenderCamera; // 渲染到RenderTexture的相机
    private RawImage displayRawImage; // 显示RenderTexture的RawImage
    private Vector2? panelTouchPos = null; // 面板上的触摸位置(UV坐标)

    // 初始化只执行一次,避免反复查找引用
    void Awake()
    {
        // 直接获取自身挂载的RawImage(假设脚本挂在面板RawImage所在物体上)
        displayRawImage = GetComponent<RawImage>();
        if (displayRawImage == null)
        {
            Debug.LogError("当前物体未挂载RawImage组件");
        }
    }

    void Update()
    {
        // 检测右手控制器的触摸交互(改用射线检测面板,替代Trigger)
        CheckHandPanelInteraction();

        // 当握持Grip且有触摸点时执行射线投射
        bool gripHeld = OVRInput.Get(OVRInput.Button.PrimaryHandTrigger, OVRInput.Controller.RTouch);
        if (gripHeld && panelTouchPos.HasValue && displayRenderCamera != null)
        {
            CastRayFromRenderCamera();
        }
    }

    // 查找其他玩家的渲染相机(仅在需要时调用,比如玩家加入时)
    public void FindOtherPlayerRenderCamera()
    {
        PhotonView[] allPhotonViews = FindObjectsOfType<PhotonView>();
        foreach (PhotonView view in allPhotonViews)
        {
            if (view.Owner != null && view.Owner.ActorNumber != PhotonNetwork.LocalPlayer.ActorNumber)
            {
                GameObject cameraObj = view.gameObject.transform.Find("Head/ViewCamera")?.gameObject;
                if (cameraObj != null)
                {
                    displayRenderCamera = cameraObj.GetComponent<Camera>();
                    Debug.Log("找到其他玩家相机: " + displayRenderCamera.name);
                    break; // 只取第一个其他玩家,若有多玩家需调整逻辑
                }
            }
        }
    }

    // 检测右手与面板的交互,获取精确触摸位置
    private void CheckHandPanelInteraction()
    {
        // 获取右手控制器的世界空间射线
        Vector3 controllerPos = OVRInput.GetLocalControllerPosition(OVRInput.Controller.RTouch);
        Quaternion controllerRot = OVRInput.GetLocalControllerRotation(OVRInput.Controller.RTouch);
        Ray handRay = new Ray(controllerPos, controllerRot * Vector3.forward);

        // 检测射线是否命中面板的RectTransform
        if (RectTransformUtility.RaycastWorldPointInRectangle(displayRawImage.rectTransform, 
                                                              handRay.origin, 
                                                              Camera.main, 
                                                              out var result))
        {
            // 记录面板上的局部触摸位置
            panelTouchPos = result.localPosition;
        }
        else
        {
            panelTouchPos = null;
        }
    }

    // 从其他玩家相机投射射线
    private void CastRayFromRenderCamera()
    {
        Rect rect = displayRawImage.rectTransform.rect;
        // 将局部触摸位置转换为0-1范围的Viewport坐标
        Vector2 viewportPos = new Vector2(
            (panelTouchPos.Value.x + rect.width / 2) / rect.width,
            (panelTouchPos.Value.y + rect.height / 2) / rect.height
        );
        
        // 翻转Y轴:RawImage的Y轴顶部为0,Unity Viewport的Y轴底部为0
        viewportPos.y = 1 - viewportPos.y;

        Ray ray = displayRenderCamera.ViewportPointToRay(viewportPos);

        // 调试:生成红色立方体
        Vector3 debugPoint = ray.GetPoint(2.0f);
        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        cube.transform.position = debugPoint;
        cube.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
        cube.GetComponent<Renderer>().material.color = Color.red;
        Destroy(cube, 0.1f);

        // 射线检测触发粒子系统
        if (Physics.Raycast(ray, out RaycastHit hit, 10.0f))
        {
            if (hit.transform.TryGetComponent<CubeManager>(out CubeManager cubeManager))
            {
                cubeManager.StartParticleSystem();
            }
        }
    }

    // 玩家加入房间后延迟查找相机,确保玩家物体加载完成
    public override void OnJoinedRoom()
    {
        base.OnJoinedRoom();
        Invoke(nameof(FindOtherPlayerRenderCamera), 1f);
    }
}

关键修改说明

1. 修复触摸无响应问题

  • 移除不稳定的OnTriggerEnter/Exit检测,改用控制器射线精确命中面板的方式,适配VR交互逻辑,避免触发范围不准确的问题。
  • 将相机和面板的初始化逻辑从Update中移除,改为在玩家加入房间后延迟执行,避免反复查找组件导致引用被覆盖,同时确保玩家物体加载完成。
  • 直接获取脚本所在物体的RawImage组件,减少不必要的层级查找。

2. 修正射线坐标偏移问题

  • 使用RectTransformUtility.RaycastWorldPointInRectangle获取面板上的精确触摸位置,替代之前用手部包围盒中心估算的方式,位置精度大幅提升。
  • 正确转换坐标空间:将RawImage的局部触摸位置转换为相机Viewport坐标时,处理了Y轴方向翻转(RawImage与Unity Viewport的Y轴原点方向相反),彻底解决偏移问题。
  • 简化了UV坐标计算逻辑,避免复杂的UVRect转换错误。

3. 其他优化

  • 添加Header属性,方便在Inspector面板管理组件引用。
  • 增加错误日志输出,便于排查组件缺失问题。
  • 限制只查找第一个其他玩家的相机,若支持多玩家可修改FindOtherPlayerRenderCamera逻辑。

内容的提问来源于stack exchange,提问作者Yumekawa-chan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 04:19:53