hitomo 2025-10-12 03:25 采纳率: 98.9%
浏览 6
已采纳

OnDrawGizmosSelected如何在场景视图中绘制文本?

在使用 `OnDrawGizmosSelected` 时,开发者常遇到无法在场景视图中绘制文本的问题。Unity 的 `Gizmos` 类本身不提供直接绘制文本的 API,导致即使在 `OnDrawGizmosSelected` 中调用常规绘制方法也无法显示文字。许多开发者误以为可通过 `Gizmos.DrawXXX` 实现文本渲染,但实际上该类仅支持几何图形(如线、立方体等)。如何在选中对象时于场景视图中正确叠加文本提示(如状态信息或调试数据),成为常见痛点。虽然可通过 SceneView 回调结合 Handles.Label 实现,但需注意调用时机与坐标转换问题。
  • 写回答

1条回答 默认 最新

  • 白萝卜道士 2025-10-12 03:25
    关注

    一、问题背景与核心限制

    在 Unity 开发过程中,OnDrawGizmosSelected 是一个常用于可视化调试的回调方法。它允许开发者在场景视图中选中 GameObject 时绘制辅助图形,例如边界框、方向箭头或触发区域等。

    然而,许多开发者尝试在此方法中绘制文本提示(如状态信息、参数值或逻辑标识)时会发现:尽管调用了看似合理的 API,文本始终无法显示。

    根本原因在于:Unity 的 Gizmos 类并未提供任何直接绘制文本的接口。该类仅支持几何图元绘制,包括:

    • Gizmos.DrawLine
    • Gizmos.DrawWireCube
    • Gizmos.DrawSphere
    • Gizmos.DrawRay
    • Gizmos.DrawMesh

    这些方法只能渲染形状,无法输出字符内容。因此,即使在 OnDrawGizmosSelected 中尝试使用不存在的 Gizmos.DrawText 或类似变体,编译器将报错或静默失败。

    二、常见误解与错误实践

    不少开发者基于“Gizmo 能画东西”这一认知,误以为可通过扩展 Gizmos 实现文本渲染。以下是几种典型的错误尝试:

    尝试方式实际效果问题分析
    Gizmos.Label(transform.position, "Debug Info")编译失败Gizmos 类无 Label 静态方法
    GUI.LabelOnDrawGizmosSelected不显示或崩溃GUI 系统不可在此上下文中调用
    自定义 Shader 渲染文字到 Gizmo 位置复杂且无效Shader 不参与 Scene 视图的 Gizmo 渲染流程

    三、正确路径:引入 HandlesSceneView 回调

    要实现在选中对象时于场景视图叠加文本,必须跳出 Gizmos 的局限,转向 UnityEditor.Handles 模块。该模块专为编辑器可视化设计,提供了 Handles.Label 方法,可用于在世界坐标中绘制文本。

    但关键挑战在于:OnDrawGizmosSelected 并非运行在 Scene GUI 的渲染上下文中,直接调用 Handles.Label 可能导致异常或无输出。

    解决方案是结合 SceneView.duringSceneGui 判断和委托注册机制,在正确的时机绘制文本。

    四、实现方案:安全绘制场景文本

    以下是一个完整且线程安全的实现示例,可在选中对象时于其上方显示调试文本:

    
    using UnityEngine;
    using UnityEditor;
    
    [ExecuteInEditMode]
    public class DebugTextLabel : MonoBehaviour
    {
        public string debugInfo = "State: Active";
    
        private void OnEnable()
        {
            SceneView.duringSceneGui -= OnSceneGUI;
            SceneView.duringSceneGui += OnSceneGUI;
        }
    
        private void OnDisable()
        {
            SceneView.duringSceneGui -= OnSceneGUI;
        }
    
        private void OnSceneGUI(SceneView sceneView)
        {
            if (Selection.activeGameObject == gameObject && sceneView.camera != null)
            {
                // 将世界坐标转换为屏幕空间以适配 Handles.Label
                Vector3 worldPosition = transform.position + Vector3.up * 0.5f;
                Handles.Label(worldPosition, debugInfo);
            }
        }
    }
        

    上述代码通过订阅 SceneView.duringSceneGui 事件,在每次场景视图重绘时检查当前选中对象,并仅在匹配时调用 Handles.Label

    五、坐标系统与视觉优化策略

    使用 Handles.Label 时需注意坐标系转换问题。虽然传入的是世界坐标,但标签的对齐方式默认基于摄像机视角。若需固定朝向或偏移显示,可结合 HandleUtility.WorldToGUIPoint 进行微调。

    进阶技巧包括:

    • 使用 EditorGUIStyle 自定义字体颜色与大小
    • 通过 SceneView.currentDrawingSceneView.camera 获取当前视角进行深度判断
    • 利用 Handles.matrix 应用局部坐标变换
    • 避免在非选中状态下频繁绘制以提升性能

    六、流程图:文本绘制决策逻辑

    graph TD A[进入 OnSceneGUI 回调] --> B{当前选中对象 == 本体?} B -- 否 --> C[跳过绘制] B -- 是 --> D[计算文本世界坐标] D --> E[调用 Handles.Label(worldPos, text)] E --> F[文本显示在场景视图中] C --> G[结束] F --> G

    七、性能考量与最佳实践

    虽然 Handles.Label 功能强大,但在大量对象上同时启用可能导致编辑器卡顿。建议采用以下优化策略:

    1. 仅在 Selection.Contains(gameObject) 时绘制
    2. 缓存频繁访问的组件引用
    3. 使用对象池管理调试标签实例
    4. 提供编辑器开关控制全局显示/隐藏
    5. 避免在每帧都重建委托,确保 OnEnable/OnDisable 成对注册
    6. 考虑使用 EditorApplication.delayCall 延迟初始化
    7. 对复杂文本格式化做缓存处理
    8. 测试不同 Unity 版本的兼容性(尤其是 2021+ 的 UI Toolkit 变更)
    9. 结合 [CustomEditor] 实现更精细的控制
    10. 文档化调试功能,防止误提交至生产构建
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

问题事件

  • 已采纳回答 10月23日
  • 创建了问题 10月12日