Unity Editor中的内置图标
首先贴两个链接
【Unity编辑器】Unity内置GUI风格和资源
Unity3D研究院之系统内置系统图标大整理
这第一篇的作者是传说中的 号称 A神 的大神写的。。shader方面相当666。
写这篇文章的起因
这两个链接都是介绍Unity Editor中的内置图标的文章。。但是看来看去都只说了个实现步骤。。却没给出具体的提取图标的代码之类的。。感到很困惑。。所以打算自己实现了一遍
经过
通过上面两篇文章的介绍,刚开始我有点摸不着头脑。。首先一点是最近才开始尝试着看一点Editor部分的API,自己做几个没有实际功能的demo。。。
接下来开始说一下过程,不过前提先看一下上面第一篇文章,可能看着会有点晕。。可以跟着我这里贴出的代码看
一:导出代码文件
先用工具ILSpy(其他工具也可以,只要能导出反编译的结果就行),打开Editor.dll,并导出文件
导出的时候记得先选中根节点。。才能将所有代码导出来;如果只选中一个类,则只会导出一个cs文件
![](https://img.haomeiwen.com/i1310035/05badc233a9c75f4.png)
二:了解加载内置图标的一些api
主要有三个地方
- 一个是窗口标题上的图标
- 内部函数 EditorGUIUtility.LoadIcon
- 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();
....
}
![](https://img.haomeiwen.com/i1310035/80065b81f94bc170.png)
也可以通过Sublime 打开刚才的ILSpy导出的文件夹(直接把文件夹拖到Sublime中就可以了),然后在Sublime中对根节点右键 -> 点击 Find in Folder...
![](https://img.haomeiwen.com/i1310035/dbd02d944cb310b4.png)
然后用 EditorWindowTitle 这个作为关键字搜索就可以搜到所有使用到的地方了,然后可以看到在 EditorWindow 的 GetLocalizedTitleContent() 方法中有引用到。
![](https://img.haomeiwen.com/i1310035/e1fcc6c5a215b2e2.png)
然后跟 到 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 都是 静态的。。
具体的代码中都有注释。。看不懂的可以问我。。
废话有点多。。。
![](https://img.haomeiwen.com/i1310035/3171cf2bcc2f150e.png)
![](https://img.haomeiwen.com/i1310035/54a4ca4bafb10351.png)
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();
}
}