Unity Editor中的内置图标

首先贴两个链接
【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();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容