1. 程式人生 > >【Unity編輯器】使用反射和Attribute實現自定義右鍵選單

【Unity編輯器】使用反射和Attribute實現自定義右鍵選單

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");
    }
}

效果如下:


在自定義視窗任何位置右鍵就可以出來了。