第三章自定义检视面板_创建自定义编辑器类_如何自定义预览窗口(本章进度5/9)

发布于:2025-07-22 ⋅ 阅读:(13) ⋅ 点赞:(0)

3.1.5 如何自定义预览窗口

在unity中我们经常会用到预览窗口,如下图,去查看一些东西。比如模型、动画之类。
在这里插入图片描述

书上提供是一个例子使用与DoTween结合做了动画相关的一个预览窗口。内容比较多,总共代码五百多行,不适合在这里讲,我会在专栏的最后,把所有例子解析一下,因为我感觉例子里的其他东西都太多了,我只想介绍简单预览窗口,我会举个简单例子,实现一个可以鼠标旋转缩放的预览窗口。

它的逻辑流程图是这样的
在这里插入图片描述

代码实现

using UnityEngine;
using UnityEditor;

// 使用 CustomEditor 特性指定这个编辑器用于 Transform 组件
[CustomEditor(typeof(Transform))]
public class ObjectPreviewEditor : Editor
{
    // 预览渲染工具 - 用于创建和管理预览窗口
    private PreviewRenderUtility previewRenderUtility;
    
    // 预览对象实例 - 目标对象的副本
    private GameObject previewInstance;
    
    // 相机旋转角度 - 存储鼠标拖拽的旋转值
    private Vector2 rotation;
    
    // 相机距离 - 控制预览的缩放
    private float distance = 5f;

    /// <summary>
    /// 当编辑器启用时调用 - 初始化预览系统
    /// </summary>
    private void OnEnable()
    {
        // 创建预览渲染工具实例
        previewRenderUtility = new PreviewRenderUtility();
        
        // 创建目标对象的副本作为预览对象
        // target 是当前选中的组件(这里是 Transform)
        previewInstance = Instantiate((target as Component).gameObject);
        
        // 设置隐藏标志 - 防止预览对象出现在场景中
        previewInstance.hideFlags = HideFlags.HideAndDontSave;
        
        // 将预览对象添加到渲染系统
        previewRenderUtility.AddSingleGO(previewInstance);
        
        // 自动计算初始相机距离
        CalculateInitialDistance();
    }

    /// <summary>
    /// 计算初始相机距离 - 根据对象大小自动调整
    /// </summary>
    private void CalculateInitialDistance()
    {
        // 创建初始边界框
        Bounds bounds = new Bounds();
        
        // 获取所有子物体的渲染器
        Renderer[] renderers = previewInstance.GetComponentsInChildren<Renderer>();
        
        // 遍历所有渲染器,计算总边界
        foreach (Renderer renderer in renderers)
        {
            bounds.Encapsulate(renderer.bounds);
        }

        // 根据边界大小设置初始距离
        float size = bounds.size.magnitude;
        distance = Mathf.Clamp(size * 1.5f, 2f, 10f);
    }

    /// <summary>
    /// 当编辑器禁用时调用 - 清理资源
    /// </summary>
    private void OnDisable()
    {
        // 清理预览渲染工具
        if (previewRenderUtility != null)
        {
            previewRenderUtility.Cleanup();
        }

        // 销毁预览对象
        if (previewInstance != null)
        {
            DestroyImmediate(previewInstance);
        }
    }

    /// <summary>
    /// 是否显示预览窗口 - 仅在预览对象存在时返回 true
    /// </summary>
    public override bool HasPreviewGUI()
    {
        return previewInstance != null;
    }

    /// <summary>
    /// 绘制预览窗口 - 核心渲染方法
    /// </summary>
    /// <param name="r">预览窗口的矩形区域</param>
    /// <param name="background">背景样式</param>
    public override void OnPreviewGUI(Rect r, GUIStyle background)
    {
        // 如果预览对象不存在,直接返回
        if (previewInstance == null)
            return;

        // 处理用户输入(旋转和缩放)
        HandleInput(r);

        // 在重绘事件中执行渲染
        if (Event.current.type == EventType.Repaint)
        {
            // 开始预览渲染
            previewRenderUtility.BeginPreview(r, background);
            
            // 获取预览相机
            Camera camera = previewRenderUtility.camera;
            
            // 设置相机旋转(基于鼠标拖拽的角度)
            camera.transform.rotation = Quaternion.Euler(new Vector3(-rotation.y, -rotation.x, 0));
            
            // 设置相机位置(以对象为中心,根据距离计算)
            camera.transform.position = previewInstance.transform.position +
                                       camera.transform.forward * -distance;
            
            // 执行渲染
            camera.Render();
            
            // 结束并绘制预览
            previewRenderUtility.EndAndDrawPreview(r);
        }
    }

    /// <summary>
    /// 处理预览窗口的用户输入
    /// </summary>
    /// <param name="area">预览窗口的矩形区域</param>
    private void HandleInput(Rect area)
    {
        // 获取当前事件
        Event e = Event.current;

        // 鼠标左键拖拽 - 旋转视图
        if (e.type == EventType.MouseDrag && e.button == 0)
        {
            // 更新旋转角度(根据鼠标移动增量)
            rotation -= e.delta * 0.5f;
            
            // 标记事件已处理
            e.Use();
        }

        // 滚轮滚动 - 缩放视图
        if (e.type == EventType.ScrollWheel)
        {
            // 根据滚轮增量调整距离
            distance += e.delta.y * 0.1f;
            
            // 限制距离范围
            distance = Mathf.Clamp(distance, 1f, 20f);
            
            // 标记事件已处理
            e.Use();
        }
    }
}

效果
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到