Unity运行时编辑器UI拖拽偏移:虚拟相机与射线投射的解决方案
1. 项目概述当UI拖拽遇上“幽灵射线”在Unity Runtime Editor运行时编辑器的开发中实现自定义UI的拖拽功能是一个高频需求它能让你的应用在运行时拥有强大的动态编辑能力。然而很多开发者包括我自己都曾踩过一个不大不小的坑当你精心编写的拖拽逻辑在Scene视图中运行良好一到Game视图或者打包后的运行时环境UI元素的拖拽就“飘”了——鼠标点击的位置和UI实际移动的位置对不上仿佛中间隔着一层看不见的偏移。这个问题其核心往往指向一个幕后角色Runtime Editor插件为了编辑便利而悄悄创建的“虚拟相机”及其生成的“射线”。简单来说这不是你的拖拽代码写错了而是你的射线“打偏”了。你写的拖拽逻辑默认是基于主摄像机Main Camera的视口射线进行坐标转换的。但在Runtime Editor模式下插件为了让你能在编辑状态下自由漫游和操作常常会在后台生成一个独立的、用于编辑的虚拟相机。当你点击UI进行拖拽时你的射线计算如果仍然傻傻地引用主摄像机那么射线起点和方向就是错的自然无法正确命中UI的RectTransform导致计算出的世界坐标或屏幕坐标产生难以察觉的偏移。这个问题尤其容易在混合使用Screen Space - Overlay和Screen Space - Camera渲染模式的UI或者当Runtime Editor的编辑视图Editor Camera与游戏视图Game Camera分离时爆发。本文将彻底拆解这个“射线偏移”问题的来龙去脉。无论你是正在为你的工具、关卡编辑器或可视化应用集成Runtime Editor功能还是单纯想深入理解Unity中UI交互与多相机协作的底层机制这篇从实战中总结的排查指南和解决方案都能为你提供清晰的路径。我们将从问题现象入手逐步分析原理并给出多种经过验证的、可直接“抄作业”的修复方案。2. 核心原理虚拟相机与射线坐标系的错位要解决问题必须先理解问题是如何产生的。Unity的UI交互特别是基于EventSystem的拖拽IDragHandler其底层依赖于射线投射Raycasting。对于GraphicRaycaster用于UI它需要一条从摄像机出发、穿过屏幕某点的射线来与Canvas下的UI元素进行碰撞检测。2.1 默认逻辑的“想当然”大多数教程或我们直觉写的拖拽位置更新代码是这样的以世界空间Canvas为例public void OnDrag(PointerEventData eventData) { // 常见的、但有问题的写法 Ray ray Camera.main.ScreenPointToRay(eventData.position); if (Physics.Raycast(ray, out RaycastHit hit)) { // 假设hit.point是我们想要的世界坐标 transform.position hit.point; } // 或者对于UI到UI的拖拽直接使用eventData.position RectTransformUtility.ScreenPointToWorldPointInRectangle( myParentRectTransform, eventData.position, eventData.pressEventCamera, out Vector3 worldPoint ); transform.position worldPoint; }这段代码在纯游戏运行时只有一个主摄像机渲染Game视图是没问题的。EventSystem会正确地将当前渲染UI的摄像机赋值给eventData.pressEventCamera。这个摄像机通常是你的主摄像机或者是Canvas指定的Render Camera。2.2 Runtime Editor引入的“变量”当你引入如“Runtime Transform Gizmos”、“Runtime Editor Camera”这类插件或自行开发的运行时编辑框架时情况变了。为了实现在运行时像Scene视图一样自由旋转、缩放、移动视角来观察和编辑物体这些框架通常会做以下几件事创建虚拟编辑相机在后台动态生成一个额外的摄像机我们称之为EditorCamera。这个相机可能独立于游戏的主摄像机GameCamera拥有自己的变换、投影矩阵和渲染目标可能是RenderTexture也可能是直接渲染到屏幕的某个层。接管输入与渲染在编辑模式下框架可能会将用户的鼠标、键盘输入优先派发给这个EditorCamera的控制脚本用于视角漫游。同时它可能会调整相机的渲染顺序或层级确保编辑视图的内容正确覆盖或与游戏视图结合。切换渲染上下文当你点击一个按钮进入“编辑模式”时屏幕上你看到的画面可能已经不是GameCamera渲染的了而是EditorCamera渲染的画面。但你的UI Canvas可能仍然由原来的摄像机或一个独立的UICamera渲染。关键冲突点就在这里EventSystem在派发事件如OnDrag时其eventData.position是屏幕坐标而eventData.pressEventCamera理论上应该是产生该次点击的射线对应的摄像机。然而如果EventSystem没有正确感知到当前有效的渲染摄像机是EditorCamera它可能仍然返回GameCamera或Canvas的默认摄像机作为pressEventCamera。当你用错误的摄像机GameCamera去解释一个源于正确摄像机EditorCamera渲染画面的屏幕坐标时ScreenPointToRay或ScreenPointToWorldPointInRectangle计算出的射线就完全错位了。因为这两个摄像机的位置、旋转、视野FOV、近远裁剪面甚至投影模式透视/正交可能完全不同。注意即使你的UI是Screen Space - Overlay模式理论上不依赖于摄像机但涉及到需要将屏幕坐标转换为相对于某个RectTransform的局部坐标时如果参考系不对同样会产生偏移。Screen Space - Camera模式则直接依赖指定的摄像机更容易出问题。2.3 问题复现场景分析让我们通过几个典型场景来加深理解场景一分离的编辑视图与游戏视图。你的Runtime Editor有一个独立的浮动窗口来显示编辑视图由EditorCamera渲染到RenderTexture并显示在RawImage上而游戏画面在另一个区域。你在编辑视图里拖拽UI偏移产生了。因为点击事件发生在编辑视图的纹理上但其屏幕坐标是相对于整个游戏窗口的而你的拖拽逻辑可能错误地使用了主游戏摄像机去做转换。场景二全屏编辑模式切换。点击“编辑”按钮后游戏逻辑暂停主摄像机被禁用EditorCamera被启用并全屏渲染。此时Canvas的渲染模式或渲染摄像机如果没有随之切换那么UI事件对应的摄像机引用就会混乱。场景三多相机堆叠渲染。EditorCamera和GameCamera可能同时渲染到屏幕通过不同的Depth和Culling Mask来控制显示内容。UI Canvas可能被设置为同时被两个相机渲染或只被其中一个渲染。事件系统的射线投射需要明确知道当前点击应该由哪个相机的射线来判定。理解了这个原理我们就知道解决方案的核心思路是确保在计算拖拽位置时使用的摄像机与当前产生输入事件的渲染视图所对应的摄像机严格一致。3. 诊断与排查定位偏移根源的实战步骤当遇到拖拽偏移问题时不要盲目修改代码。系统性的诊断能帮你快速定位问题层。你可以按照以下步骤进行排查3.1 第一步确认问题现象与模式记录偏移量在OnDrag方法中同时打印出eventData.position和通过Camera.main或eventData.pressEventCamera转换得到的世界坐标。拖拽时观察控制台输出。对比模式分别测试纯游戏模式关闭Runtime Editor功能和编辑模式下的拖拽行为。如果纯游戏模式正常而编辑模式偏移问题几乎可以锁定在相机引用错误上。检查Canvas设置确认你的可拖拽UI所在的Canvas的Render Mode和Render Camera设置。记下它在不同模式下的配置。3.2 第二步审查事件系统中的摄像机引用这是最关键的一步。修改你的OnBeginDrag或OnDrag方法加入详细的调试信息public void OnDrag(PointerEventData eventData) { Debug.Log($屏幕点击位置: {eventData.position}); Debug.Log($EventSystem当前帧的射线投射摄像机: {eventData.pressEventCamera?.name ?? \NULL\}); Debug.Log($Camera.main: {Camera.main?.name}); // 尝试查找所有摄像机并判断哪个正在渲染点击区域 Camera[] allCams Camera.allCameras; foreach (var cam in allCams) { if (cam.gameObject.activeInHierarchy cam.enabled) { Debug.Log($活动相机: {cam.name}, Depth: {cam.depth}, CullingMask: {cam.cullingMask}); } } }运行程序进入编辑模式并进行拖拽。观察日志如果eventData.pressEventCamera是null说明EventSystem没有正确关联上摄像机。如果eventData.pressEventCamera的名字是你的主游戏摄像机如“MainCamera”但你在用EditorCamera的视图进行操作那这就是问题的直接证据。观察所有活动相机的Depth。通常Depth值更高的相机会后渲染覆盖在先渲染的相机画面上。当前你看到的画面很可能来自Depth最高的那个活动相机。3.3 第三步验证坐标转换编写一个简单的调试脚本来可视化射线// 附加到一个临时物体上在Update中绘制Debug射线 void Update() { if (Input.GetMouseButton(0)) // 按住鼠标左键 { // 假设editorCam是你的运行时编辑相机 Camera editorCam GameObject.Find(RuntimeEditorCamera)?.GetComponentCamera(); if (editorCam ! null) { Ray ray editorCam.ScreenPointToRay(Input.mousePosition); Debug.DrawRay(ray.origin, ray.direction * 100, Color.green, 0.1f); } // 同时绘制主摄像机的射线作为对比 if (Camera.main ! null) { Ray mainRay Camera.main.ScreenPointToRay(Input.mousePosition); Debug.DrawRay(mainRay.origin, mainRay.direction * 100, Color.red, 0.1f); } } }在Scene视图如果是运行时需要使用Debug.DrawRay并在Game视图查看Gizmos中观察。在编辑模式下绿色的射线EditorCamera应该从你点击的位置正确发出。而红色的射线MainCamera很可能指向一个完全不同的方向。你的拖拽逻辑如果用了红射线结果自然错误。通过以上三步你就能百分百确定偏移是否由相机/射线不匹配引起并明确是哪两个相机之间产生了混淆。4. 解决方案多策略修复射线偏移定位问题后我们可以从不同层面实施解决方案。你可以根据项目架构复杂度和需求选择其一或组合使用。4.1 方案一动态指定正确的渲染摄像机推荐这是最根本的解决方案。思路是确保EventSystem和UI Canvas在运行时编辑模式下使用正确的摄像机进行射线投射。步骤1为Runtime Editor相机设置Tag给你创建的运行时编辑相机EditorCamera打上一个独特的Tag例如“RuntimeEditorCamera”。步骤2修改或创建GraphicRaycaster你需要让UI的射线投射器知道该用哪个相机。有两种方法方法A动态切换Canvas的World Camera适用于Screen Space - Camera模式。public Canvas myCanvas; // 你的UI Canvas private Camera gameCamera; private Camera editorCamera; void Start() { gameCamera Camera.main; // 或Canvas原来指定的相机 editorCamera GameObject.FindWithTag(RuntimeEditorCamera).GetComponentCamera(); } // 当进入编辑模式时调用 public void SwitchToEditorMode() { myCanvas.renderMode RenderMode.ScreenSpaceCamera; myCanvas.worldCamera editorCamera; // 可能需要调整Canvas的Plane Distance } // 当退出编辑模式时调用 public void SwitchToGameMode() { myCanvas.renderMode RenderMode.ScreenSpaceCamera; // 或原来的模式 myCanvas.worldCamera gameCamera; }方法B自定义一个GraphicRaycaster更通用适用于所有模式。 继承GraphicRaycaster重写Raycast方法在内部指定使用的摄像机。using UnityEngine.UI; using UnityEngine.EventSystems; public class DynamicCameraGraphicRaycaster : GraphicRaycaster { private Camera _currentCamera; public void SetActiveCamera(Camera cam) { _currentCamera cam; } public override void Raycast(PointerEventData eventData, ListRaycastResult resultAppendList) { // 如果指定了相机则使用它 if (_currentCamera ! null) { // 这里需要一些反射或复制基类逻辑来修改camera因为基类的camera是protected。 // 一个更直接的方法在EventSystem触发射线投射前我们通过其他方式设置eventData。 // 因此更常见的做法是采用方案二修正事件数据。 } else { base.Raycast(eventData, resultAppendList); } } }实操心得直接重写GraphicRaycaster来硬性替换摄像机比较复杂因为基类逻辑与Canvas、Camera耦合紧密。更常见的做法是采用下一个方案或者在更高层——EventSystem层面解决问题。4.2 方案二修正PointerEventData中的摄像机引用既然问题出在eventData.pressEventCamera不对那我们可以在事件触发的最上游修正它。这通常需要在管理Runtime Editor模式的代码中或者通过一个全局的输入管理类来实现。步骤创建一个输入桥接层在负责切换编辑/游戏模式的Manager中获取到EditorCamera的引用。使用EventSystem.current来模拟或修改输入事件。但更优雅的方式是利用IPointerClickHandler、IDragHandler等接口的实现是可以多层调用的。我们可以创建一个“门面”或“代理”拖拽脚本附着在顶层UI或一个独立的管理器上由它来修正事件数据再分发给具体的UI元素。using UnityEngine.EventSystems; public class InputEventCorrector : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler { public Camera activeEditorCamera; // 在编辑模式下动态赋值为EditorCamera // 存储最后一次正确的摄像机 private Camera correctPressCamera null; public void OnPointerDown(PointerEventData eventData) { // 如果处于编辑模式且有活动的EditorCamera if (IsInEditMode() activeEditorCamera ! null) { // 修正这次按下事件的摄像机引用 eventData.pressEventCamera activeEditorCamera; correctPressCamera activeEditorCamera; } else { correctPressCamera eventData.pressEventCamera; // 保持原样 } // 这里可以再将事件传递给实际需要响应的UI元素事件传播 // 例如通过EventSystem.current.RaycastAll重新获取目标并手动调用其OnPointerDown ExecuteEvents.ExecuteHierarchy(GetActualTarget(eventData), eventData, ExecuteEvents.pointerDownHandler); } public void OnDrag(PointerEventData eventData) { // 在拖拽持续期间强制使用我们修正过的摄像机 if (correctPressCamera ! null) { eventData.pressEventCamera correctPressCamera; } // 传递拖拽事件 ExecuteEvents.ExecuteHierarchy(GetActualTarget(eventData), eventData, ExecuteEvents.dragHandler); } public void OnPointerUp(PointerEventData eventData) { // 传递抬起事件 ExecuteEvents.ExecuteHierarchy(GetActualTarget(eventData), eventData, ExecuteEvents.pointerUpHandler); correctPressCamera null; // 清理 } private GameObject GetActualTarget(PointerEventData eventData) { // 这里需要实现逻辑根据eventData.position和correctPressCamera重新射线检测 // 找到真正被点击的UI元素。可以复制GraphicRaycaster的部分逻辑或者简单起见 // 如果UI结构固定可以直接指定目标。 // 这是一个简化示例实际项目需要更健壮的实现。 return eventData.pointerCurrentRaycast.gameObject; } private bool IsInEditMode() { // 返回你的运行时编辑模式状态 return true; // 示例 } }这个方案需要一定的架构设计但它能从根源上修正事件流中的错误数据是最彻底的解决方案之一。4.3 方案三在拖拽逻辑中手动计算坐标快速修复如果项目结构简单或者你只想快速修复一个特定的拖拽功能可以采用这种“打补丁”式的方法。核心思想是在OnDrag方法中忽略eventData.pressEventCamera直接使用我们知道的正确摄像机EditorCamera进行坐标转换。public class CorrectedDraggableUI : MonoBehaviour, IDragHandler { private Camera _editorCamera; // 在Start或Awake中赋值例如通过Tag查找 private RectTransform _parentRectTransform; // 如果是UI到UI的拖拽需要父级RectTransform private Vector3 _offset; // 用于记录点击点与UI中心的偏移 public void OnBeginDrag(PointerEventData eventData) { // 1. 获取正确的摄像机 if (_editorCamera null) { _editorCamera GameObject.FindWithTag(RuntimeEditorCamera)?.GetComponentCamera(); if (_editorCamera null) { Debug.LogError(Runtime Editor Camera not found!); return; } } // 2. 计算正确的世界坐标示例将UI拖到3D空间某平面 Ray ray _editorCamera.ScreenPointToRay(eventData.position); Plane dragPlane new Plane(Vector3.up, Vector3.zero); // 假设在XZ平面上拖拽 if (dragPlane.Raycast(ray, out float distance)) { Vector3 worldPoint ray.GetPoint(distance); _offset transform.position - worldPoint; } // 或者对于UI到UI的拖拽在同一个Canvas下 if (RectTransformUtility.ScreenPointToWorldPointInRectangle( _parentRectTransform, eventData.position, _editorCamera, // 关键使用EditorCamera out Vector3 worldPos)) { _offset transform.position - worldPos; } } public void OnDrag(PointerEventData eventData) { if (_editorCamera null) return; // 使用EditorCamera持续计算新位置 Ray ray _editorCamera.ScreenPointToRay(eventData.position); Plane dragPlane new Plane(Vector3.up, Vector3.zero); if (dragPlane.Raycast(ray, out float distance)) { Vector3 newWorldPoint ray.GetPoint(distance); transform.position newWorldPoint _offset; } // UI到UI的拖拽版本 if (RectTransformUtility.ScreenPointToWorldPointInRectangle( _parentRectTransform, eventData.position, _editorCamera, // 关键使用EditorCamera out Vector3 newWorldPos)) { transform.position newWorldPos _offset; } } }注意事项此方案需要在每个可拖拽组件上绑定脚本并正确配置_editorCamera和_parentRectTransform。它没有解决其他UI交互事件如点击、悬停的射线问题只针对拖拽。适用于功能单一、需要快速上线的场景。4.4 方案四配置EventSystem的射线投射器Unity的EventSystem依赖于BaseInputModule如StandaloneInputModule或InputSystemUIInputModule来生成射线数据。你可以尝试创建一个自定义的Input Module在生成PointerEventData时就为其分配合适的摄像机。using UnityEngine.EventSystems; public class CustomStandaloneInputModule : StandaloneInputModule { public Camera editorCamera; protected override MouseState GetMousePointerEventData(int id) { MouseState m base.GetMousePointerEventData(id); // 获取基类生成的鼠标状态 if (IsInEditMode() editorCamera ! null) { // 修改鼠标状态中所有PointerEventData的pressEventCamera PointerEventData leftData; if (GetPointerData(kMouseLeftId, out leftData, true)) { leftData.pressEventCamera editorCamera; } // 同样处理右键、中键... } return m; } // 还需要重写GetTouchPointerEventData等方法以处理触摸输入 private bool IsInEditMode() { /* 你的模式判断逻辑 */ } }然后在场景中用你的CustomStandaloneInputModule替换掉标准的StandaloneInputModule。这个方案比较底层影响全局的UI输入需要谨慎测试。5. 深入优化处理复杂场景与性能考量解决了基本的偏移问题后在一些复杂场景下我们还需要考虑更多细节以确保功能的健壮性和性能。5.1 处理多Canvas与相机堆叠如果你的项目中有多个Canvas且它们可能被不同的相机渲染例如一个Canvas用于游戏HUD另一个Canvas专门用于Runtime Editor的工具栏你需要确保每个Canvas的GraphicRaycaster在射线检测时使用的是正确的相机。策略为不同用途的Canvas分组。游戏UI Canvas使用默认或主游戏相机。编辑器UI Canvas则明确指定其World Camera为EditorCamera并且确保其GraphicRaycaster的eventCamera属性可通过代码访问被正确设置。在模式切换时可能需要动态启用/禁用不同Canvas的GraphicRaycaster组件以避免事件被错误的Canvas拦截。5.2 屏幕空间匹配与视口矩形当EditorCamera渲染到一个非全屏的RenderTexture比如画中画、小窗口时情况更复杂。此时鼠标点击的屏幕坐标Input.mousePosition是全屏坐标但你需要将其转换为相对于那个RenderTexture显示区域例如一个RawImage的矩形区域的视口坐标Viewport Point范围0-1然后再用EditorCamera进行转换。public RawImage editorViewportImage; // 显示EditorCamera渲染纹理的UI Image public Camera editorCamera; private RectTransform _viewportRectTransform; void Start() { _viewportRectTransform editorViewportImage.rectTransform; } Vector3 GetCorrectWorldPointFromEditorView(Vector2 screenPosition) { // 1. 将屏幕坐标转换到ViewportImage的局部坐标 RectTransformUtility.ScreenPointToLocalPointInRectangle( _viewportRectTransform, screenPosition, null, // 这里用null因为screenPosition已经是屏幕坐标 out Vector2 localPoint ); // 2. 将局部坐标归一化为视口坐标 (0,0)到(1,1) Rect rect _viewportRectTransform.rect; Vector2 viewportPoint new Vector2( (localPoint.x - rect.x) / rect.width, (localPoint.y - rect.y) / rect.height ); // 3. 检查点击是否在Viewport内部 if (viewportPoint.x 0 viewportPoint.x 1 viewportPoint.y 0 viewportPoint.y 1) { // 4. 使用EditorCamera的视口坐标生成射线 Ray ray editorCamera.ViewportPointToRay(viewportPoint); // ... 后续的射线碰撞检测 return ray.origin ray.direction * 10f; // 示例 } return Vector3.zero; }5.3 性能优化建议摄像机查找缓存避免在每帧的OnDrag中通过GameObject.FindWithTag或GetComponent查找摄像机。应在Start或Awake中缓存引用或在模式切换时由管理器统一分发。减少不必要的射线投射在拖拽过程中如果只是更新UI位置且目标平面固定可以只在OnBeginDrag时计算一次射线与平面的交点以及偏移量在OnDrag中只需基于鼠标移动的eventData.delta进行位移计算这比每帧都进行ScreenPointToRay和Raycast更高效。区分渲染与事件相机在架构设计上可以明确区分“渲染相机”和“事件相机”。例如始终使用一个专门的、正交投影的UICamera来渲染所有UI而EditorCamera只用于渲染3D场景。这样UI事件永远只和UICamera关联简化了逻辑。EditorCamera的视图交互如选中3D物体则通过另一套基于其自身的物理射线检测来处理。6. 常见问题与排查技巧实录即使按照上述方案实施你可能还会遇到一些“坑”。以下是我在实际项目中总结的一些典型问题及其解决方法。6.1 问题拖拽在编辑模式下正常但退出编辑模式后游戏模式下的UI拖拽也失灵或错乱。原因模式切换时Canvas的渲染模式或渲染相机没有正确还原。或者你在编辑模式下修改了某些全局状态如EventSystem.current.currentInputModule退出时未重置。解决在模式切换的代码中严格记录切换前的状态。例如在进入编辑模式前保存Canvas的renderMode和worldCamera退出时精确还原。使用状态机管理游戏的不同模式如GamePlayMode、EditorMode确保每个模式有独立的初始化Enter()和清理Exit()方法。检查是否有单例或静态类在编辑模式下被修改并在退出时没有恢复。6.2 问题UI拖拽时元素会突然“跳”到很远的位置。原因这通常是offset计算错误导致的。在OnBeginDrag中计算偏移量时用于计算点击位置的世界坐标和当前UI位置的世界坐标可能不是在同一参考系或同一时刻计算的。解决确保OnBeginDrag中计算offset使用的摄像机与OnDrag中更新位置使用的摄像机是同一个。对于UI到UI的拖拽确保RectTransformUtility.ScreenPointToWorldPointInRectangle使用的parentRectTransform和camera参数在整个拖拽过程中保持一致。在计算offset时使用transform.InverseTransformPoint将世界坐标差值转换为UI局部空间的偏移可能更稳定。6.3 问题触摸设备上拖拽完全无效。原因自定义的输入校正逻辑可能只处理了鼠标事件GetMousePointerEventData没有处理触摸事件GetTouchPointerEventData。解决如果采用了方案四自定义InputModule务必同时重写处理触摸输入的相关方法。如果采用了方案二事件修正确保你的InputEventCorrector也实现了IInitializePotentialDragHandler、IBeginDragHandler等接口并能正确处理Touch类型的PointerEventData。在Unity的Input System包中需要检查InputSystemUIInputModule的相关设置。6.4 问题Debug.Log显示pressEventCamera已经是EditorCamera了但拖拽仍有微小偏移。原因可能是Canvas的Render Mode或Plane Distance设置问题。对于Screen Space - Camera模式UI元素位于一个与相机特定距离的平面上。如果这个Plane Distance与EditorCamera到其渲染的3D场景中实际拖拽平面的距离不匹配就会产生视差偏移。解决尝试将可拖拽UI所在的Canvas改为Screen Space - Overlay模式完全脱离摄像机。但这可能影响UI与3D场景的混合效果。精确计算并设置Canvas的Plane Distance使其与EditorCamera到主要交互平面的距离大致相等。考虑使用Physics Raycaster与3D物体交互而UI拖拽仅用于2D界面操作将两者逻辑分离。6.5 排查工具箱当你遇到问题时可以按顺序使用以下工具进行诊断帧调试器Frame Debugger查看当前帧究竟是哪个摄像机在渲染你的UI和场景确认渲染管线。场景视图的调试绘制如前文所述使用Debug.DrawRay可视化不同相机发出的射线这是最直观的方法。EditorCamera的视口坐标输出在点击时将鼠标屏幕坐标转换为EditorCamera的视口坐标并打印检查其是否在预期的[0,1]范围内。检查Canvas的Raycast Target确保你的可拖拽UI元素的raycastTarget属性是勾选的否则它根本接收不到事件。简化测试创建一个最简场景只有一个Camera一个Canvas一个可拖拽Image。然后逐步加入Runtime Editor相机观察在哪一步出现偏移。这能有效隔离问题。最后记住一个核心原则在Unity的UI事件系统中“谁渲染谁负责射线”。确保处理输入事件的代码所认知的“摄像机”与用户当前所看到的、产生该输入事件的画面渲染者是同一个摄像机就能从根本上杜绝这类偏移问题。

相关新闻