Unity Editor中的内置图标

作者: Gabo | 来源:发表于2016-07-23 16:35 被阅读2817次

    首先贴两个链接
    【Unity编辑器】Unity内置GUI风格和资源
    Unity3D研究院之系统内置系统图标大整理

    这第一篇的作者是传说中的 号称 A神 的大神写的。。shader方面相当666。

    写这篇文章的起因

    这两个链接都是介绍Unity Editor中的内置图标的文章。。但是看来看去都只说了个实现步骤。。却没给出具体的提取图标的代码之类的。。感到很困惑。。所以打算自己实现了一遍

    经过

    通过上面两篇文章的介绍,刚开始我有点摸不着头脑。。首先一点是最近才开始尝试着看一点Editor部分的API,自己做几个没有实际功能的demo。。。

    接下来开始说一下过程,不过前提先看一下上面第一篇文章,可能看着会有点晕。。可以跟着我这里贴出的代码看

    一:导出代码文件

    先用工具ILSpy(其他工具也可以,只要能导出反编译的结果就行),打开Editor.dll,并导出文件

    导出的时候记得先选中根节点。。才能将所有代码导出来;如果只选中一个类,则只会导出一个cs文件

    ILSpy导出代码

    二:了解加载内置图标的一些api

    主要有三个地方

    1. 一个是窗口标题上的图标
    2. 内部函数 EditorGUIUtility.LoadIcon
    3. EditorGUIUtility.FindTexture(这个不是内部函数,所以我们可以调用的到)
    二(1)窗口上的图标

    我们可以通过 ILSpy,搜索类型ProjectBrowser。。然后可以看到ProjectBrowser这个类的签名如下

    [EditorWindowTitle(title = "Project", icon = "Project")]
    internal class ProjectBrowser : EditorWindow, IHasCustomMenu
    

    一眼看过去就看到 icon,当然是跟到 EditorWindowTitle 这个特性中去看看,发现只有三个属性(title, icon, useTypeNameAsIconName),而且属性名看起来意思很明显。所已接下来要找到使用这个特性的地方。

    看到OnEnable 方法中 通过 base.GetLocalizedTitleContent() 初始化 titleContent,然后一直跟进去就可以了。

    private void OnEnable()
    {
        base.titleContent = base.GetLocalizedTitleContent();
        ....
    }
    
    EditorWindowTitle 初始化 titleContent

    也可以通过Sublime 打开刚才的ILSpy导出的文件夹(直接把文件夹拖到Sublime中就可以了),然后在Sublime中对根节点右键 -> 点击 Find in Folder...

    Sublime在文件中查找...

    然后用 EditorWindowTitle 这个作为关键字搜索就可以搜到所有使用到的地方了,然后可以看到在 EditorWindowGetLocalizedTitleContent() 方法中有引用到。

    Sublime搜索结果-EditorWindow.cs

    然后跟 到 GUIContent GetLocalizedTitleContentFromType(Type t) 这个函数中:

    
    internal GUIContent GetLocalizedTitleContent()
    {
        return EditorWindow.GetLocalizedTitleContentFromType(base.GetType());
    }
    
    internal static GUIContent GetLocalizedTitleContentFromType(Type t)
    {
        // 得到 EditorWindowTitleAttribute 特性
        EditorWindowTitleAttribute editorWindowTitleAttribute = EditorWindow.GetEditorWindowTitleAttribute(t);
        if (editorWindowTitleAttribute == null)
        {
            // 若没有特性,则窗口类名作为窗口的title
            return new GUIContent(t.ToString());
        }
        string text = string.Empty;
        if (!string.IsNullOrEmpty(editorWindowTitleAttribute.icon))
        {
            // 若存在特性,若 icon属性 不为空,则用该属性 的值作为 图标名用于后面加载图标
            text = editorWindowTitleAttribute.icon;
        }
        else if (editorWindowTitleAttribute.useTypeNameAsIconName)
        {
            // 如果 icon属性 为空,若属性 useTypeNameAsIconName 为true,则用窗口类型名来加载图标
            text = t.ToString();
        }
        if (!string.IsNullOrEmpty(text))
        {
            return EditorGUIUtility.TextContentWithIcon(editorWindowTitleAttribute.title, text);
        }
        return EditorGUIUtility.TextContent(editorWindowTitleAttribute.title);
    }
    
    

    继续跟进 EditorGUIUtility.TextContentWithIcon

    // UnityEditor.EditorGUIUtility
    internal static GUIContent TextContentWithIcon(string textAndTooltip, string icon)
    {
        // ...
            gUIContent.image = EditorGUIUtility.LoadIconRequired(icon);
        // ...
        return gUIContent;
    }
    

    然后进入EditorGUIUtility.LoadIconRequired

    // UnityEditor.EditorGUIUtility
    internal static Texture2D LoadIconRequired(string name)
    {
        Texture2D texture2D = EditorGUIUtility.LoadIcon(name);
        // ...
        return texture2D;
    }
    

    到这里就够了。。。可以看出最后是调用 EditorGUIUtility.LoadIcon 这个函数来得到图标的。。

    提取图标

    因为上面涉及到的API基本都是 internal 修饰的,所以我们需要通过反射来得到所有继承自 EditorWindow 且具有 EditorWindowTitleAttribute 特性的类。然后分析特性中属性值来得到 图标名字。最后通过反射调用 EditorGUIUtility.LoadIcon 加载出图片。。代码在最后贴出

    二(2)通过内部函数 EditorGUIUtility.LoadIcon 直接加载的图标

    也就是 通过如下形式调用 LoadIcon,只要提取所有 实参,然后通过反射调用 LoadIcon 加载出来就可以了。。这里提取实参要通过 正则 来提取。。

    EditorGUIUtility.LoadIcon("icon name");
    
    二(3)EditorGUIUtility.FindTexture(这个不是内部函数,所以我们可以调用的到)

    这个和上面的 EditorGUIUtility.LoadIcon 一样的思路,不过 FindTexture 不是内部函数,所以可以不通过反射直接调用。。。我的代码里面为了 代码的复用,所以 也通过反射来调用 FindTexture的

    结果(贴代码)

    代码中涉及的面还是有点广的。。。有 反射、Editor 的Api使用、正则、简单的Linq。。。
    我主要是想把这些平时比较少用的趁这之后练练手,所以能用上的我都尽量尝试一下。。。

    本来想 看看能不能通过 部分类(partial)来扩展 EditorGUIUtility 来添加一个 LoadIconEx,方法中通过反射调用,这样我就可以把反射代码集中到这个部分类里面去了。。但是 部分类的前提是 每个部分都要有 partial 修饰类的声明,而 UnityEditor.dll 中却没有。之前也想过用扩展方法来做,但是扩展方法需要有实例调用。。但是这里反射调用的API 都是 静态的。。

    具体的代码中都有注释。。看不懂的可以问我。。

    废话有点多。。。

    窗口菜单 结果图
    using UnityEngine;
    using System.Collections;
    using UnityEditor;
    using System.IO;
    using System.Collections.Generic;
    using System.Reflection;
    using System;
    using System.Text.RegularExpressions;
    using System.Linq;
    using System.Text;
    
    public class ListInternalIconWindow : EditorWindow
    {
        [MenuItem("Window/Open Internal Icon Window")]
        static void Open()
        {
            GetWindow<ListInternalIconWindow>();
        }
    
        private List<GUIContent> lstWindowIcons, lstLoadIconParmContents, lstFindTextureParmContents;
        private Vector2 vct2LoadIconParmScroll;
        private Rect rectScrollViewPos = new Rect(), rectScrollViewRect = new Rect();
        private Rect headerRct = new Rect();
        private Rect rectLoadIcon = new Rect(0, 0, 300, 35);
    
    
        private MethodInfo loadIconMethodInfo, findTextureMethodInfo;
        private IEnumerator enumeratorLoadIcon, enumeratorFindTexture;
    
        void Awake()
        {
            lstWindowIcons = new List<GUIContent>();
            lstLoadIconParmContents = new List<GUIContent>();
            lstFindTextureParmContents = new List<GUIContent>();
    
            loadIconMethodInfo = typeof(EditorGUIUtility).GetMethod("LoadIcon", BindingFlags.Static | BindingFlags.NonPublic);
            findTextureMethodInfo = typeof(EditorGUIUtility).GetMethod("FindTexture", BindingFlags.Static | BindingFlags.Public);
    
            InitWindowsIconList();
            enumeratorLoadIcon = MethodParamEnumerator("EditorGUIUtility.LoadIcon", loadIconMethodInfo);
            enumeratorFindTexture = MethodParamEnumerator("EditorGUIUtility.FindTexture", findTextureMethodInfo);
    
            // LoadIcon 的实参有的是字符串拼接的。。这种我就没有加载出来,可以到UnityEditor.dll源码中查看如何凭借
            // 这里我用一个源码中拼接的图标作为该窗口的图标
            titleContent = new GUIContent("InternalIcon", loadIconMethodInfo.Invoke(null, new object[] { "WaitSpin00" }) as Texture);
            minSize = new Vector2(512, 320);
        }
    
        void OnGUI()
        {
            // Don't use yield in OnGUI() between GUILayout.BeginArea() and GUILayout.EndArea()
            if (null != enumeratorLoadIcon && enumeratorLoadIcon.MoveNext() && null != enumeratorLoadIcon.Current)
            {
                lstLoadIconParmContents.Add(enumeratorLoadIcon.Current as GUIContent);
                Repaint();
            }
            if(null != enumeratorFindTexture && enumeratorFindTexture.MoveNext() && null != enumeratorFindTexture.Current)
            {
                lstFindTextureParmContents.Add(enumeratorFindTexture.Current as GUIContent);
                Repaint();
            }
    
            headerRct.x = headerRct.y = 0;
            headerRct.width = position.width;
            headerRct.height = 30;
    
            int colCount = Mathf.Max(1, (int)(position.width / rectLoadIcon.width));
            int rowCount = (lstWindowIcons.Count + lstLoadIconParmContents.Count + lstFindTextureParmContents.Count) / colCount + 2;
    
            rectScrollViewRect.width = colCount * rectLoadIcon.width;
            rectScrollViewRect.height = rowCount * rectLoadIcon.height + 3 * headerRct.height;
            rectScrollViewPos.width = position.width;
            rectScrollViewPos.height = position.height;
    
            vct2LoadIconParmScroll = GUI.BeginScrollView(rectScrollViewPos, vct2LoadIconParmScroll, rectScrollViewRect);
            {
                float offsetY = 0;
                string headerText = "添加EditorWindowTitleAttribute 特性的窗口的图标:" + lstWindowIcons.Count + " 个";
                offsetY = DrawList(headerText, offsetY, colCount, lstWindowIcons, false);
    
                headerRct.y = offsetY;
                headerText = "传递给 EditorGUIUtility.LoadIcon 的参数:" + lstLoadIconParmContents.Count + " 个";
                offsetY = DrawList(headerText, offsetY, colCount, lstLoadIconParmContents, true);
    
                headerRct.y = offsetY;
                headerText = "传递给 EditorGUIUtility.FindTexture 的参数:" + lstFindTextureParmContents.Count + " 个";
                offsetY = DrawList(headerText, offsetY, colCount, lstFindTextureParmContents, true);
            }
            GUI.EndScrollView();
        }
    
        /// <summary>
        /// 绘制 GUIContent list
        /// </summary>
        /// <param name="headerText">标头</param>
        /// <param name="offsetY">绘制区域的垂直偏移量</param>
        /// <param name="colCount">一行绘制几个</param>
        /// <param name="lstGUIContent">将要绘制的 GUIContent list</param>
        /// <returns>返回 结束后的偏移量</returns>
        private float DrawList(string headerText, float offsetY, int colCount, List<GUIContent> lstGUIContent, bool isRemoveReturn)
        {
            GUI.Label(headerRct, headerText);
            offsetY += headerRct.height;
            for (int i = 0; i < lstGUIContent.Count; ++i)
            {
                rectLoadIcon.x = (int)(rectLoadIcon.width * (i % colCount));
                rectLoadIcon.y = (int)(rectLoadIcon.height * (i / colCount)) + offsetY;
    
                if(GUI.Button(rectLoadIcon, lstGUIContent[i]))
                {
                    string str = lstGUIContent[i].text;
                    if(isRemoveReturn)
                    {
                        str = str.Replace("\r", "");
                        str = str.Replace("\n", "");
                    }
                    Debug.Log(str);
                }
            }
            return offsetY + (lstGUIContent.Count / colCount + 1) * rectLoadIcon.height;
        }
    
        /// <summary>
        /// 通过反射得到 EditorWindowTitleAttribute 特性标记的 EditorWindow 子类
        /// 并通过这个特性中的属性得到 图标的名字,
        /// 然后继续通过反射调用内部方法 EditorGUIUtility.LoadIcon 来得到 图标的 Texture 实例
        /// </summary>
        private void InitWindowsIconList()
        {
            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
    
            Type editorWindowTitleAttrType = typeof(EditorWindow).Assembly.GetType("UnityEditor.EditorWindowTitleAttribute");
    
            foreach (Assembly assembly in assemblies)
            {
                Type[] types = assembly.GetTypes();
                foreach (Type type in types)
                {
                    if (!type.IsSubclassOf(typeof(EditorWindow)))
                        continue;
                    
                    object[] attrs = type.GetCustomAttributes(editorWindowTitleAttrType, true);
                    for(int i=0; i<attrs.Length; ++i)
                    {
                        if(attrs[i].GetType() == editorWindowTitleAttrType)
                        {
                            string icon = GetPropertyValue<string>(editorWindowTitleAttrType, attrs[i], "icon");
                            if (string.IsNullOrEmpty(icon))
                            {
                                bool useTypeNameAsIconName = GetPropertyValue<bool>(editorWindowTitleAttrType, attrs[i], "useTypeNameAsIconName");
                                if (useTypeNameAsIconName)
                                    icon = type.ToString();
                            }
    
                            if(!string.IsNullOrEmpty(icon) && null != loadIconMethodInfo)
                            {
                                var iconTexture = loadIconMethodInfo.Invoke(null, new object[] { icon }) as Texture2D;
                                if (null != iconTexture)
                                    lstWindowIcons.Add(new GUIContent(type.Name + "\n" + icon, iconTexture));
                            }
                        }
                    }
                }
            }
        }
    
        /// <summary>
        /// 通过将 Editor.dll 反编译出来,遍历反编译出来的所有文件,
        /// 通过正则找出所有 调用 EditorGUIUtility.LoadIcon 时传递 的参数
        /// </summary>
        /// <param name="methodName">加载贴图的函数名</param>
        /// <param name="loadTextureAction">加载贴图的函数</param>
        /// <returns></returns>
        private IEnumerator MethodParamEnumerator(string methodName, MethodInfo loadTextureMethodInfo)
        {
            Type editorResourcesUtility = typeof(EditorWindow).Assembly.GetType("UnityEditorInternal.EditorResourcesUtility");
    
            //Regex regex = new Regex(@"(?<=EditorGUIUtility.LoadIcon\("")[^""]+(?=""\))");
            Regex regex = new Regex(@"(?<=" + methodName + @"\()[^\(\)]*(((?'Open'\()[^\(\)]*)+((?'-Open'\))[^\(\)]*)+)*(?=\))(?(Open)(?!))");
            Regex quatRegex = new Regex(@"(?<=^"")[^""]+(?=""$)");
    
            // 这里是反编译 UnityEditor.dll 导出来的文件夹
            string[] files = Directory.GetFiles(@"D:\Unity5\UnityEditor", "*.cs", SearchOption.AllDirectories);
    
            var enumerable = from matchCollection in
                                (from content in
                                    (from file in files select File.ReadAllText(file))
                                 select regex.Matches(content))
                             select matchCollection;
            
            foreach (MatchCollection matchCollection in enumerable)
            {
                for(int i=0; i<matchCollection.Count; ++i)
                {
                    Match match = matchCollection[i];
                    string iconName = ((Match)match).Groups[0].Value;
    
                    if (string.IsNullOrEmpty(iconName) || null == loadTextureMethodInfo)
                        continue;
    
                    bool isDispatchMethod = false;
                    Texture iconTexture = null;
                    if (quatRegex.IsMatch(iconName))
                    {
                        isDispatchMethod = true;
                        iconName = iconName.Replace("\"", "");
                    }
                    else if(iconName.StartsWith("EditorResourcesUtility."))
                    {
                        string resName = GetPropertyValue<string>(editorResourcesUtility, null, iconName.Replace("EditorResourcesUtility.", ""));
                        if(!string.IsNullOrEmpty(resName))
                        {
                            isDispatchMethod = true;
                            iconName = resName;
                        }
                    }
    
                    if(isDispatchMethod)
                    {
                        try
                        {
                            iconTexture = loadTextureMethodInfo.Invoke(null, new object[] { iconName }) as Texture2D;
                        }
                        catch (Exception e)
                        {
                            Debug.LogError(iconName + "\n" + e);
                        }
                    }
    
                    if (null != iconTexture)
                        yield return new GUIContent(InsertReturn(iconName, 20), iconTexture);
                    else
                        yield return new GUIContent(InsertReturn(iconName, 30));
                }
            }
        }
    
        /// <summary>
        /// 反射得到属性值
        /// </summary>
        /// <typeparam name="T">属性类型</typeparam>
        /// <param name="type">属性所在的类型</param>
        /// <param name="obj">类型实例,若是静态属性,则obj传null即可</param>
        /// <param name="propertyName">属性名</param>
        /// <returns>属性值</returns>
        private T GetPropertyValue<T>(Type type, object obj, string propertyName)
        {
            T result = default(T);
            PropertyInfo propertyInfo = type.GetProperty(propertyName);
            if(null != propertyInfo)
            {
                result = (T)propertyInfo.GetValue(obj, null);
            }
            return result;
        }
    
        /// <summary>
        /// 对字符串插入 换行符
        /// </summary>
        /// <param name="str">待处理的字符串</param>
        /// <param name="interval">每几个字符插入一个 换行符</param>
        /// <returns></returns>
        private string InsertReturn(string str, int interval)
        {
            if (string.IsNullOrEmpty(str) || str.Length <= interval)
                return str;
    
            StringBuilder sb = new StringBuilder();
            int index = 0;
            while(index < str.Length)
            {
                if (0 != index)
                    sb.Append("\r\n");
    
                int len = index + interval >= str.Length ? str.Length-index : interval;
                sb.Append(str.Substring(index, len));
                index += len;
            }
            return sb.ToString();
        }
    }
    

    相关文章

      网友评论

        本文标题:Unity Editor中的内置图标

        本文链接:https://www.haomeiwen.com/subject/axlwjttx.html