【Unity編輯器】使用反射和Attribute實現自定義右鍵選單
阿新 • • 發佈:2019-01-27
unity提供了許多Attribute,比如[MenuItem]和[ContextMenu],一個是在編輯器選單欄中新增選單按鈕,一個是在檢視面板新增上下文選單,由於其原理是基於C#的Attribute功能,我們自然可以想到,是否可以自己編寫一套自定義Attribute,來實現一個自定義的選單呢,效果如下:
實現的方式主要用了反射和C#的Attribute,(Attribute實際上還可以做更多功能,這裡只是舉一個例子)
首先自定義一個Attribute類,內容很簡單,這裡我在我的Attribute裡實現了兩個屬性,一個是選單上顯示的內容,一個是優先順序:
using System; [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class InspectorContextMenuItemAttribute : Attribute { public string menuItem; public int priority; public InspectorContextMenuItemAttribute(string menuItem, int priority) { this.menuItem = menuItem; this.priority = priority; } public InspectorContextMenuItemAttribute(string menuItem) : this(menuItem, 0) { } }
然後是子選單,子選單通過GenericMenu來實現:
using UnityEngine; using UnityEditor; using System.Collections.Generic; using System.Reflection; public class InspectorContextMenu { public GenericMenu menu; private List<AttributeData> attributeList; public InspectorContextMenu() { LoadCustomContextMenuItem(); CreateContextMenuItem(); } public void ShowMenu() { menu.ShowAsContext(); } void LoadCustomContextMenuItem() { if (attributeList != null) return; attributeList = new List<AttributeData>(); Assembly assembly = GetType().Assembly; if (assembly != null) { System.Type[] types = assembly.GetTypes(); for (int i = 0; i < types.Length; i++) { if (!types[i].IsClass) continue; //獲取靜態方法 MethodInfo[] methods = types[i].GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); for (int j = 0; j < methods.Length; j++) { var attributes = methods[j].GetCustomAttributes(typeof (InspectorContextMenuItemAttribute), false); foreach (var attribute in attributes) { attributeList.Add(new AttributeData(methods[j], (InspectorContextMenuItemAttribute) attribute)); } } } } //通過優先順序排序 attributeList.Sort(Comparison); } void CreateContextMenuItem() { if (menu != null) return; if (attributeList == null) return; menu = new GenericMenu(); int level = 1; for (int i = 0; i < attributeList.Count; i++) { //每1000優先順序為一組,不同組之間用橫線隔開 if (i > 0 && attributeList[i].attribute.priority - level * 1000 >= 0) { string line = ""; int id = attributeList[i].attribute.menuItem.LastIndexOf('/'); if (id >= 0) line = attributeList[i].attribute.menuItem.Substring(0, id); menu.AddSeparator(line + "/"); level += 1; } menu.AddItem(new GUIContent(attributeList[i].attribute.menuItem), false, attributeList[i].Invoke); } } int Comparison(AttributeData A, AttributeData B) { return A.attribute.priority - B.attribute.priority; } class AttributeData { public MethodInfo method; public InspectorContextMenuItemAttribute attribute; public AttributeData(MethodInfo method, InspectorContextMenuItemAttribute attribute) { this.method = method; this.attribute = attribute; } public void Invoke() { if (method != null) method.Invoke(null, null); } } }
之後就可以在自定義的Inspector或是EditorWindow中使用了,這裡寫了個EditorWindow中使用的例子:
using UnityEngine; using UnityEditor; using System.Collections; public class TestWindow : EditorWindow { private InspectorContextMenu menu { get { if (m_Menu == null) m_Menu = new InspectorContextMenu(); return m_Menu; } } private InspectorContextMenu m_Menu; [MenuItem("Test/TestWindow")] static void Init() { TestWindow window = EditorWindow.GetWindow<TestWindow>(); } void OnGUI() { if (Event.current.type == EventType.MouseDown && Event.current.button == 1) { menu.ShowMenu(); Event.current.Use(); } } [InspectorContextMenuItem("TestButtonA")] static void TestButtonA() { Debug.Log("ClickTestButtonA"); } [InspectorContextMenuItem("TestButtonB", 1000)] static void TestButtonB() { Debug.Log("ClickTestButtonB"); } [InspectorContextMenuItem("TestButtonC", 1001)] static void TestButtonC() { Debug.Log("ClickTestButtonC"); } [InspectorContextMenuItem("TestButtonD")] static void TestButtonD() { Debug.Log("ClickTestButtonD"); } }
效果如下:
在自定義視窗任何位置右鍵就可以出來了。