Inspector中预览预制体

发布时间 2023-10-09 23:42:16作者: yanghui01

原理:

用相机拍摄预制体,生成一张预览图

 

需要注意:

1) GameObject.Instantiate实例化预制体后,要等待几帧才能获取到正确的Transform参数,刚创建就获取可能是不对的。 

2) 预制体预览有两种实现方式,一种是继承Editor然后重写OnPreviewGUI;另一种是继承ObjectPreview然后重写OnPreviewGUI;

这边选择后面这种,因为第1种,对于场景下的嵌套预制体,预览界面不起作用。

3) 如果预制体的根节点有Image组件,只有在Project窗口中选择预制体时才能预览;其他方式只有Image图片的预览,目前未找到解决方式,猜测可能ImageEditor的OnPreviewGUI优先级更高?

 

#if UNITY_EDITOR

using System.IO;
using UnityEditor;
using UnityEditor.Experimental.SceneManagement;
using UnityEngine;

[CustomPreview(typeof(GameObject))]
public class PrefabPreview : ObjectPreview
{
    private string m_UGUIPrefabPath;
    private int m_Frame = -1;
    private string m_PreviewFilePath;

    private void OnEditorUpdate()
    {
        if (m_Frame > 0)
        {
            m_Frame--;
            if (0 == m_Frame)
            {
                var previewGo = GameObject.Find("___preview");
                if (null != previewGo)
                {
                    if (string.IsNullOrEmpty(m_PreviewFilePath))
                    {
                        Debug.LogError($"m_PreviewFilePath none, PreviewEditor Change?");
                        GameObject.DestroyImmediate(previewGo);
                    }
                    else
                    {
                        previewGo.name = "preview_ready";
                        var tex = AssetPreviewHelper.GetAssetPreview(previewGo);
                        AssetPreviewHelper.SaveTexture2D(tex, m_PreviewFilePath);
                    }
                }
                m_Frame = -1;
                EditorApplication.update -= OnEditorUpdate;
AssetPreviewHelper.RepaintInspector(); } } }
private bool IsUGUIPrefab() { m_UGUIPrefabPath = ""; var prefabPath = AssetDatabase.GetAssetPath(target); var targetGo = (GameObject)target; if (string.IsNullOrEmpty(prefabPath)) { if (PrefabUtility.IsAnyPrefabInstanceRoot(targetGo)) //场景中的嵌套预制体 prefabPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(targetGo); else { var stage = PrefabStageUtility.GetCurrentPrefabStage(); if (null != stage) //预制体编辑界面 prefabPath = stage.prefabAssetPath; } } if (string.IsNullOrEmpty(prefabPath)) return false; var rtf = targetGo.GetComponent<RectTransform>(); if (null != rtf) { m_UGUIPrefabPath = prefabPath; return true; } return false; } public override bool HasPreviewGUI() { if (IsUGUIPrefab()) return true; return base.HasPreviewGUI(); } public override void OnPreviewGUI(Rect r, GUIStyle background) { if (string.IsNullOrEmpty(m_UGUIPrefabPath)) { base.OnPreviewGUI(r, background); return; } var preview = AssetPreview.GetAssetPreview(target); if (null == preview) { var guid = AssetDatabase.AssetPathToGUID(m_UGUIPrefabPath); var previewFilePath = Path.Combine("_Preview", $"{guid}.png"); if (File.Exists(previewFilePath)) { var imgBytes = File.ReadAllBytes(previewFilePath); Texture2D tex = new Texture2D(2, 2); tex.LoadImage(imgBytes); preview = tex; } } if (preview) PreviewEditor.OnGUI_PreviewImage(r, preview); } //生成预览图 private void RefreshPreviewImage(string previewFilePath) { var previewGo = GameObject.Find("___preview"); if (null != previewGo) { Debug.LogWarning("last preview not finish, force terminate!"); GameObject.DestroyImmediate(previewGo); } previewGo = GameObject.Instantiate((GameObject)target); //previewGo.hideFlags = HideFlags.HideAndDontSave; previewGo.name = "___preview"; previewGo.transform.position = new Vector3(-1000, -1000, -1000); //远一点, 这样才看不到 m_Frame = 2; //等待几帧 m_PreviewFilePath = previewFilePath; EditorApplication.update -= OnEditorUpdate; EditorApplication.update += OnEditorUpdate; } public override void OnPreviewSettings() { base.OnPreviewSettings(); if (GUILayout.Button("刷新", "preButton")) { var guid = AssetDatabase.AssetPathToGUID(m_UGUIPrefabPath); var previewFilePath = Path.Combine("_Preview", $"{guid}.png"); RefreshPreviewImage(previewFilePath); } } } #endif

场景嵌套预制体

预制体编辑界面

资源管理其中选择预制体时

 

 

#if UNITY_EDITOR

using System;
using System.IO;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;

public static class AssetPreviewHelper
{

    public static Texture2D GetAssetPreview(GameObject cloneGo)
    {
        GameObject rootGo = null;

        var cameraGo = new GameObject("render camera", typeof(Camera));
        var cameraTransform = cameraGo.transform;
        var renderCamera = cameraGo.GetComponent<Camera>();
        renderCamera.backgroundColor = new Color(0.8f, 0.8f, 0.8f, 0.1f);
        renderCamera.clearFlags = CameraClearFlags.Color;
        renderCamera.cameraType = CameraType.SceneView;
        renderCamera.cullingMask = 1 << 31;

        Bounds bounds;
        var cloneTransform = cloneGo.transform;
        bool isUINode = false;
        if (cloneTransform is RectTransform)
        {
            //如果是UGUI节点的话就要把它们放在Canvas下了
            var canvasGo = new GameObject("render canvas", typeof(Canvas));
            var canvasRtf = canvasGo.GetComponent<RectTransform>();
            SetAsDesignSize(canvasRtf);
            canvasRtf.position = new Vector3(-1000, -1000, -1000); //远一点, 这样才看不到
            canvasGo.layer = 31;//放在31层,摄像机也只渲染此层的,避免混入了奇怪的东西
            rootGo = canvasGo;

            var canvas = canvasGo.GetComponent<Canvas>();
            canvas.renderMode = RenderMode.WorldSpace;
            canvas.worldCamera = renderCamera;

            cameraTransform.SetParent(canvasRtf, false);

            cloneTransform.SetParent(canvasRtf, false);
            cloneTransform.localPosition = Vector3.zero;

            if (cloneTransform.GetComponent<Canvas>()) //根节点有Canvas组件, 只考虑根节点的大小
            {
                var cloneRtf = (RectTransform)cloneTransform;
                var anchorDiff = cloneRtf.anchorMax - cloneRtf.anchorMin;
                bool haveStretch = !Mathf.Approximately(anchorDiff.sqrMagnitude, float.Epsilon);
                if (haveStretch)
                {
                    var cavs = cloneTransform.GetComponent<CanvasScaler>();
                    if (cavs && cavs.uiScaleMode == CanvasScaler.ScaleMode.ScaleWithScreenSize)
                    {
                        var size = cavs.referenceResolution;
                        cloneRtf.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, size.x);
                        cloneRtf.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, size.y);
                    }
                }

                var tempWorldCorners = new Vector3[4];
                cloneRtf.GetWorldCorners(tempWorldCorners);
                bounds = new Bounds(tempWorldCorners[0], Vector3.zero); //左下
                bounds.Encapsulate(tempWorldCorners[2]); //右上            
            }
            else
            {
                bounds = GetBounds(cloneGo);
            }

            isUINode = true;
        }
        else
        {
            rootGo = cloneGo;
            cameraTransform.SetParent(cloneTransform, false);
            bounds = GetBounds(cloneGo);
        }

        var allTransforms = cloneGo.GetComponentsInChildren<Transform>();
        foreach (Transform trans in allTransforms)
            trans.gameObject.layer = 31;

        var vMin = bounds.min;
        var vMax = bounds.max;

        float texWidth = 128;
        float texHeight = 128;
        var center = bounds.center;
        var pos = cameraTransform.position;
        pos.x = center.x;
        pos.y = center.y;
        if (isUINode)
        {
            pos.z -= 100;
            cameraTransform.position = pos;

            renderCamera.orthographic = true;
            float width = vMax.x - vMin.x;
            float height = vMax.y - vMin.y;
            if (width > height) //宽不变, 调整高
            {
                if (width < 64) //原来就比64小, 直接用原大小
                    texWidth = width;
                else
                    texWidth = Mathf.Max(64, width * 0.5f); //最小64
                texWidth = Mathf.Min(768, texWidth); //最大768

                renderCamera.orthographicSize = height * 0.5f;
                texHeight = texWidth * height / width;
            }
            else
            {
                if (height < 64) //原来就比64小, 直接用原大小
                    texHeight = height;
                else
                    texHeight = Mathf.Max(64, height * 0.5f); //最小64
                texHeight = Mathf.Min(768, texHeight); //最大768

                renderCamera.orthographicSize = height * 0.5f;
                texWidth = texHeight * width / height;
            }
            Debug.Log($"ui:{width}x{height}, tex:{texWidth}x{texHeight}");
        }
        else
        {
            pos.z -= vMax.z + (vMax.z - vMin.z); //z往前一点
            cameraTransform.position = pos;
            cameraTransform.LookAt(center);

            var size = bounds.size;
            int angle = (int)(Mathf.Atan2(size.y * 0.5f, size.z) * Mathf.Rad2Deg * 2); //Camera在(0,0,0)处, 拍摄Bounds包围盒
            renderCamera.fieldOfView = angle;
        }

        var camRT = new RenderTexture((int)texWidth, (int)texHeight, 0, RenderTextureFormat.Default);
        renderCamera.targetTexture = camRT;
        renderCamera.Render();

        var tex = RTToTexture2D(camRT);

        UnityEngine.Object.DestroyImmediate(rootGo); 

        return tex;
    }

    public static Texture2D RTToTexture2D(RenderTexture rt)
    {
        var oldActiveRT = RenderTexture.active;
        RenderTexture.active = rt;

        var tex = new Texture2D(rt.width, rt.height, TextureFormat.RGBAHalf, false);
        tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
        tex.Apply();

        RenderTexture.active = oldActiveRT;

        return tex;
    }

    public static Bounds GetBounds(GameObject go)
    {
        var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
        var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);

        var renders = go.GetComponentsInChildren<MeshRenderer>();
        if (renders.Length > 0)
        {
            for (int i = 0; i < renders.Length; i++)
            {
                var bounds = renders[i].bounds;
                vMin = Vector3.Min(vMin, bounds.min);
                vMax = Vector3.Max(vMax, bounds.max);
            }
        }
        else
        {
            var rectTrans = go.GetComponentsInChildren<RectTransform>();
            var tempWorldCorners = new Vector3[4];
            for (int i = 0; i < rectTrans.Length; i++)
            {
                var rtf = rectTrans[i];
                var s = rtf.lossyScale;
                //if (s.x <= 3 && s.y <= 3 && s.z <= 3)
                {
                    //获取节点的四个角的世界坐标,分别按顺序为左下, 左上, 右上, 右下
                    rtf.GetWorldCorners(tempWorldCorners);
                    vMin = Vector3.Min(vMin, tempWorldCorners[0]);
                    vMax = Vector3.Max(vMax, tempWorldCorners[2]);
                }
            }
        }

        var bounds_2 = new Bounds(vMin, Vector3.zero);
        bounds_2.Encapsulate(vMax);
        //Debug.Log($"vMin:{vMin}, vMax:{vMax}, size:{bounds_2.size}");
        return bounds_2;
    }

    public static void SaveTexture2D(Texture2D tex, string filePath)
    {
        var dirPath = Path.GetDirectoryName(filePath);
        if (!Directory.Exists(dirPath))
            Directory.CreateDirectory(dirPath);

        byte[] texBytes = tex.EncodeToPNG();
        File.WriteAllBytes(filePath, texBytes);
    }

    //设置为设计分辨率
    private static void SetAsDesignSize(RectTransform rtf)
    {
        var cavs = GameObject.FindObjectOfType<CanvasScaler>();
        if (cavs && cavs.uiScaleMode == CanvasScaler.ScaleMode.ScaleWithScreenSize)
        {
            var size = cavs.referenceResolution;
            rtf.sizeDelta = size;
            return;
        }

        rtf.sizeDelta = GetGameViewSize();
    }

    public static Vector2 GetGameViewSize()
    {
        try
        {
            var type_GameView = System.Type.GetType("UnityEditor.GameView,UnityEditor");
            var mi_GetMainGameView = type_GameView.GetMethod("GetMainGameView", BindingFlags.NonPublic | BindingFlags.Static);
            var gameView = (UnityEditor.EditorWindow)mi_GetMainGameView.Invoke(null, null);
            var prop_currentGameViewSize = gameView.GetType().GetProperty("currentGameViewSize", BindingFlags.NonPublic | BindingFlags.Instance);
            object[] emptyObjArr = null; // new object[0] { };
            var gameViewSize = prop_currentGameViewSize.GetValue(gameView, emptyObjArr);
            var type_GameViewSize = gameViewSize.GetType();

            var w = (int)type_GameViewSize.GetProperty("width", BindingFlags.Public | BindingFlags.Instance).GetValue(gameViewSize, emptyObjArr);
            var h = (int)type_GameViewSize.GetProperty("height", BindingFlags.Public | BindingFlags.Instance).GetValue(gameViewSize, emptyObjArr);
            return new Vector2(w, h);
        }
        catch (Exception ex)
        {
        }
        return new Vector2(Screen.width, Screen.height);
    }

    public static void RepaintInspector()
    {
        var windows = UnityEngine.Resources.FindObjectsOfTypeAll<UnityEditor.EditorWindow>();
        foreach (var win in windows)
        {
            if (win.titleContent.text == "Inspector")
                win.Repaint();
        }
    }

}

#endif

 

 

参考

[Unity 3d] UGUI-Editor(UGUI编辑器增强工具) - GitHub - 简书 (jianshu.com)

GitHub - liuhaopen/UGUI-Editor: Unity UGUI editor tools,improve the efficiency of ui development.

获取unity prefab的预览图像 - 露夕逝 - 博客园 (cnblogs.com)

为Unity的新版ugui的Prefab生成预览图_unity ui prefab 预览图-CSDN博客

Unity 预览窗口_unity预览窗口-CSDN博客

Unity3D 在 Inspector 中预览场景_unity3d怎么预览-CSDN博客

Unity 预览窗口 - 简书 (jianshu.com)

GitHub - liuhaopen/UGUI-Editor: Unity UGUI editor tools,improve the efficiency of ui development.