首先貼兩個(gè)鏈接
【Unity編輯器】Unity內(nèi)置GUI風(fēng)格和資源
Unity3D研究院之系統(tǒng)內(nèi)置系統(tǒng)圖標(biāo)大整理
這第一篇的作者是傳說中的 號稱 A神 的大神寫的轧房。午笛。shader方面相當(dāng)666邻奠。
寫這篇文章的起因
這兩個(gè)鏈接都是介紹Unity Editor中的內(nèi)置圖標(biāo)的文章峰档。窒百。但是看來看去都只說了個(gè)實(shí)現(xiàn)步驟症杏。。卻沒給出具體的提取圖標(biāo)的代碼之類的伪节。光羞。感到很困惑。怀大。所以打算自己實(shí)現(xiàn)了一遍
經(jīng)過
通過上面兩篇文章的介紹纱兑,剛開始我有點(diǎn)摸不著頭腦。化借。首先一點(diǎn)是最近才開始嘗試著看一點(diǎn)Editor部分的API潜慎,自己做幾個(gè)沒有實(shí)際功能的demo。蓖康。铐炫。
接下來開始說一下過程,不過前提先看一下上面第一篇文章蒜焊,可能看著會有點(diǎn)暈倒信。∮景穑可以跟著我這里貼出的代碼看
一:導(dǎo)出代碼文件
先用工具ILSpy(其他工具也可以鳖悠,只要能導(dǎo)出反編譯的結(jié)果就行),打開Editor.dll优妙,并導(dǎo)出文件
導(dǎo)出的時(shí)候記得先選中根節(jié)點(diǎn)竞穷。。才能將所有代碼導(dǎo)出來鳞溉;如果只選中一個(gè)類瘾带,則只會導(dǎo)出一個(gè)cs文件
二:了解加載內(nèi)置圖標(biāo)的一些api
主要有三個(gè)地方
- 一個(gè)是窗口標(biāo)題上的圖標(biāo)
- 內(nèi)部函數(shù) EditorGUIUtility.LoadIcon
- EditorGUIUtility.FindTexture(這個(gè)不是內(nèi)部函數(shù),所以我們可以調(diào)用的到)
二(1)窗口上的圖標(biāo)
我們可以通過 ILSpy熟菲,搜索類型ProjectBrowser看政。。然后可以看到ProjectBrowser這個(gè)類的簽名如下
[EditorWindowTitle(title = "Project", icon = "Project")]
internal class ProjectBrowser : EditorWindow, IHasCustomMenu
一眼看過去就看到 icon抄罕,當(dāng)然是跟到 EditorWindowTitle 這個(gè)特性中去看看允蚣,發(fā)現(xiàn)只有三個(gè)屬性(title, icon, useTypeNameAsIconName),而且屬性名看起來意思很明顯呆贿。所已接下來要找到使用這個(gè)特性的地方嚷兔。
看到OnEnable 方法中 通過 base.GetLocalizedTitleContent() 初始化 titleContent森渐,然后一直跟進(jìn)去就可以了。
private void OnEnable()
{
base.titleContent = base.GetLocalizedTitleContent();
....
}
也可以通過Sublime 打開剛才的ILSpy導(dǎo)出的文件夾(直接把文件夾拖到Sublime中就可以了)冒晰,然后在Sublime中對根節(jié)點(diǎn)右鍵 -> 點(diǎn)擊 Find in Folder...
然后用 EditorWindowTitle 這個(gè)作為關(guān)鍵字搜索就可以搜到所有使用到的地方了同衣,然后可以看到在 EditorWindow 的 GetLocalizedTitleContent() 方法中有引用到。
然后跟 到 GUIContent GetLocalizedTitleContentFromType(Type t) 這個(gè)函數(shù)中:
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屬性 不為空,則用該屬性 的值作為 圖標(biāo)名用于后面加載圖標(biāo)
text = editorWindowTitleAttribute.icon;
}
else if (editorWindowTitleAttribute.useTypeNameAsIconName)
{
// 如果 icon屬性 為空蒋情,若屬性 useTypeNameAsIconName 為true埠况,則用窗口類型名來加載圖標(biāo)
text = t.ToString();
}
if (!string.IsNullOrEmpty(text))
{
return EditorGUIUtility.TextContentWithIcon(editorWindowTitleAttribute.title, text);
}
return EditorGUIUtility.TextContent(editorWindowTitleAttribute.title);
}
繼續(xù)跟進(jìn) EditorGUIUtility.TextContentWithIcon
// UnityEditor.EditorGUIUtility
internal static GUIContent TextContentWithIcon(string textAndTooltip, string icon)
{
// ...
gUIContent.image = EditorGUIUtility.LoadIconRequired(icon);
// ...
return gUIContent;
}
然后進(jìn)入EditorGUIUtility.LoadIconRequired
// UnityEditor.EditorGUIUtility
internal static Texture2D LoadIconRequired(string name)
{
Texture2D texture2D = EditorGUIUtility.LoadIcon(name);
// ...
return texture2D;
}
到這里就夠了。棵癣。辕翰。可以看出最后是調(diào)用 EditorGUIUtility.LoadIcon 這個(gè)函數(shù)來得到圖標(biāo)的狈谊。喜命。
提取圖標(biāo)
因?yàn)樯厦嫔婕暗降腁PI基本都是 internal 修飾的,所以我們需要通過反射來得到所有繼承自 EditorWindow 且具有 EditorWindowTitleAttribute 特性的類的畴。然后分析特性中屬性值來得到 圖標(biāo)名字渊抄。最后通過反射調(diào)用 EditorGUIUtility.LoadIcon 加載出圖片尝胆。丧裁。代碼在最后貼出
二(2)通過內(nèi)部函數(shù) EditorGUIUtility.LoadIcon 直接加載的圖標(biāo)
也就是 通過如下形式調(diào)用 LoadIcon,只要提取所有 實(shí)參含衔,然后通過反射調(diào)用 LoadIcon 加載出來就可以了煎娇。。這里提取實(shí)參要通過 正則 來提取贪染。缓呛。
EditorGUIUtility.LoadIcon("icon name");
二(3)EditorGUIUtility.FindTexture(這個(gè)不是內(nèi)部函數(shù),所以我們可以調(diào)用的到)
這個(gè)和上面的 EditorGUIUtility.LoadIcon 一樣的思路杭隙,不過 FindTexture 不是內(nèi)部函數(shù)哟绊,所以可以不通過反射直接調(diào)用。痰憎。票髓。我的代碼里面為了 代碼的復(fù)用,所以 也通過反射來調(diào)用 FindTexture的
結(jié)果(貼代碼)
代碼中涉及的面還是有點(diǎn)廣的铣耘。洽沟。。有 反射蜗细、Editor 的Api使用裆操、正則怒详、簡單的Linq。踪区。昆烁。
我主要是想把這些平時(shí)比較少用的趁這之后練練手,所以能用上的我都盡量嘗試一下朽缴。善玫。。
本來想 看看能不能通過 部分類(partial)來擴(kuò)展 EditorGUIUtility 來添加一個(gè) LoadIconEx密强,方法中通過反射調(diào)用茅郎,這樣我就可以把反射代碼集中到這個(gè)部分類里面去了。或渤。但是 部分類的前提是 每個(gè)部分都要有 partial 修飾類的聲明系冗,而 UnityEditor.dll 中卻沒有。之前也想過用擴(kuò)展方法來做薪鹦,但是擴(kuò)展方法需要有實(shí)例調(diào)用掌敬。。但是這里反射調(diào)用的API 都是 靜態(tài)的池磁。奔害。
具體的代碼中都有注釋。地熄』伲看不懂的可以問我。端考。
廢話有點(diǎn)多雅潭。。却特。
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 的實(shí)參有的是字符串拼接的扶供。。這種我就沒有加載出來裂明,可以到UnityEditor.dll源碼中查看如何憑借
// 這里我用一個(gè)源碼中拼接的圖標(biāo)作為該窗口的圖標(biāo)
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 特性的窗口的圖標(biāo):" + lstWindowIcons.Count + " 個(gè)";
offsetY = DrawList(headerText, offsetY, colCount, lstWindowIcons, false);
headerRct.y = offsetY;
headerText = "傳遞給 EditorGUIUtility.LoadIcon 的參數(shù):" + lstLoadIconParmContents.Count + " 個(gè)";
offsetY = DrawList(headerText, offsetY, colCount, lstLoadIconParmContents, true);
headerRct.y = offsetY;
headerText = "傳遞給 EditorGUIUtility.FindTexture 的參數(shù):" + lstFindTextureParmContents.Count + " 個(gè)";
offsetY = DrawList(headerText, offsetY, colCount, lstFindTextureParmContents, true);
}
GUI.EndScrollView();
}
/// <summary>
/// 繪制 GUIContent list
/// </summary>
/// <param name="headerText">標(biāo)頭</param>
/// <param name="offsetY">繪制區(qū)域的垂直偏移量</param>
/// <param name="colCount">一行繪制幾個(gè)</param>
/// <param name="lstGUIContent">將要繪制的 GUIContent list</param>
/// <returns>返回 結(jié)束后的偏移量</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 特性標(biāo)記的 EditorWindow 子類
/// 并通過這個(gè)特性中的屬性得到 圖標(biāo)的名字椿浓,
/// 然后繼續(xù)通過反射調(diào)用內(nèi)部方法 EditorGUIUtility.LoadIcon 來得到 圖標(biāo)的 Texture 實(shí)例
/// </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 反編譯出來,遍歷反編譯出來的所有文件闽晦,
/// 通過正則找出所有 調(diào)用 EditorGUIUtility.LoadIcon 時(shí)傳遞 的參數(shù)
/// </summary>
/// <param name="methodName">加載貼圖的函數(shù)名</param>
/// <param name="loadTextureAction">加載貼圖的函數(shù)</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 導(dǎo)出來的文件夾
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">類型實(shí)例扳碍,若是靜態(tài)屬性,則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">每幾個(gè)字符插入一個(gè) 換行符</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();
}
}