Unity编辑器制作多级下拉菜单

Unity编辑器下拉菜单

大家好,我是阿赵。

在Unity引擎里面编写工具插件,有时候会用到一些特殊的菜单形式,比如下拉选项。

通过下拉菜单,给用户选择不同的选项。

如果只是一层的下拉列表,可以用EditorGUILayout.Popup来实现

简单的代码示例如下:

    string[] tempList = new string[] { "菜单1", "菜单2", "菜单3" };
    private int tempIndex = 0;
    private void ShowPopMenu3()
    {
        tempIndex = EditorGUILayout.Popup(tempIndex, tempList, GUILayout.Width(100));
}

但如果是多级的下拉菜单,像这样:

EditorGUILayout.Popup是做不到的。

这里介绍2种方式在Unity的EditorWindow里面制作多层下拉选项。

一、 通过MenuItem实现

在using UnityEditor;之后,就可以在代码里面给某个方法添加MenuItem标签,比如这样:

[MenuItem("Tools/测试下拉菜单")]
static void TestFun()
{

}

这时候,在编辑器的菜单栏上面,就会出现菜单选项

注意,上面MenuItem里面只设置了一个参数,就是菜单的路径。这个路径是有特殊含义的,现在是"Tools/测试下拉菜单",那么出现在菜单栏里面的将会是"Tools"入口,然后再下拉就会出现"测试下拉菜单"的选项。

如果再复杂一点,把路径改成

    [MenuItem("Tools/测试下拉菜单/下拉菜单选项1")]
    static void TestFun1()
    {

    }
    [MenuItem("Tools/测试下拉菜单/下拉菜单选项2")]
    static void TestFun2()
    {
        PopMenuTestWin.Instance.Show();
	}

这时候,显示会变成:

所以我们可以知道,如果路径相同的情况下,MenuItem选项会合并显示。

然后路径的根目录的不同,也会出现不同的效果,比如改成

    [MenuItem("Assets/测试下拉菜单/下拉菜单选项1")]
    static void TestFun1()
    {

    }
    [MenuItem("Assets/测试下拉菜单/下拉菜单选项2")]
    static void TestFun2()
    {

	}

把根目录改成"Assets",那么菜单会出现在Project栏里面,鼠标右键点击某个文件夹,就会出现这组菜单。

如果把根目录改成GameObject,并且把最后的属性值设置1-49

[MenuItem("GameObject/测试下拉菜单",false,1)]
static void TestFun1()
{

}

这时候MenuItem的菜单会显示在Hierarchy面板的鼠标右键菜单里面。

2、 多级MenuItem显示

假如我现在需要显示的是多层的下拉菜单,通过MenuItem的自动合并路径的特性,就可以这样:

[MenuItem("testMenu/测试菜单1/测试菜单1-1",false,101)]
static void TestMenu1_1()
{

}
[MenuItem("testMenu/测试菜单1/测试菜单1-2",false,102)]
static void TestMenu1_2()
{

}
[MenuItem("testMenu/测试菜单1/测试菜单1-3",false,103)]
static void TestMenu1_3()
{

}
[MenuItem("testMenu/测试菜单2/测试菜单2-1",false,201)]

static void TestMenu2_1()
{

}
[MenuItem("testMenu/测试菜单2/测试菜单2-2",false,202)]

static void TestMenu2_2()
{

}
[MenuItem("testMenu/测试菜单3/测试菜单3-1",false,301)]

static void TestMenu3_1()
{

}
[MenuItem("testMenu/测试菜单4", false, 401)]

static void TestMenu4_1()
{

}

当这样定义了之后,那么在菜单上面就可以看到:

想继续添加子菜单,只需要继续扩展路径就行了。在MenuItem的最后有一个数字的参数,其实是一个排序显示的参数。数字越大,同级菜单的排序就会在越下面。

3、 把MenuItem显示到EditorWindow里面

刚才说了很久MenuItem的用法,接下来要把MenuItem显示在EditorWindow里面。

代码很简单,在需要显示的地方,加上:

if (GUI.Button(new Rect(0,0,100,30), "test"))
{
	EditorUtility.DisplayPopupMenu(new Rect(0, 30, 0, 0), "testMenu/", null);
}

这段代码的意思很简单,在按下按钮的时候,调用EditorUtility.DisplayPopupMenu,在指定的区域Rect(0, 30, 0, 0)内,显示菜单路径为"testMenu"作为根目录的所有菜单。

所以现在的效果会变成这样:

4、 优缺点

1.优点

这个方式制作编辑器下拉菜单,最大的好处是简单,添加得也很随意。当想添加或者删除菜单的时候,基本上可以做到互不影响。

2. 缺点

这个方式的缺点也很明显。

首先,这种方式制作的菜单,除了会显示在想要的编辑器面板里面,还会显示在顶部菜单栏或者其他的右键菜单里面,很难看。

然后,由于注册的代码是需要先写死的,所以很难通过配表之类的方式动态修改菜单选项。

二、 通过GenericMenu实现

1、GenericMenu的基本使用方法

GenericMenu的实现,需要先创建一个GenericMenu对象,然后给这个Menu对象添加Item。核心的代码大概就是这样:

GenericMenu menu = new GenericMenu();
menu.AddItem(new GUIContent("测试菜单1/测试菜单1-1"), bool, OnSelectMenu,obj);

主要看看AddItem的三个参数:

第一个是路径,也就是显示菜单的层级

第二个是一个布尔值,如果想显示打钩,可以穿true。一般要配合别的选择逻辑去实现

第三OnSelectMenu是选项被触发时的回调参数,比如这样:

private void OnSelectMenu(object val)
{
}

第四个参数是一个object,对应前面的回调函数的参数,当选项被触发之后,会把指定的值传给回调函数。

最后,调用一下

menu.ShowAsContext();

把这个菜单显示出来。

2、 GenericMenu的扩展使用

如果需要添加很多菜单,当然也是可以一个一个手动的去AddItem,不过GenericMenu的自由度是比MenuItem高很多的,所以我们也可以根据实际情况来配表显示,或者用数组的方式来添加。

比如我现在指定一个简单的规则,我这里只有2级菜单,每个根菜单使用一个字符串数组来表示。

    private string[] menu1 = new string[] { "测试菜单1", "测试菜单1-1", "测试菜单1-2", "测试菜单1-3", "测试菜单1-4" };
    private string[] menu2 = new string[] { "测试菜单2", "测试菜单2-1", "测试菜单2-2", "测试菜单2-3" };
    private string[] menu3 = new string[] { "测试菜单3", "测试菜单3-1", "测试菜单3-2"};
	private string[] menu4 = new string[] { "测试菜单4"};

是这样的规则,我就可以写个通用的添加菜单方法,把所有菜单生成出来:

比如这样:

private GenericMenu menu;
private string[] menu1 = new string[] { "测试菜单1", "测试菜单1-1", "测试菜单1-2", "测试菜单1-3", "测试菜单1-4" };
private string[] menu2 = new string[] { "测试菜单2", "测试菜单2-1", "测试菜单2-2", "测试菜单2-3" };
private string[] menu3 = new string[] { "测试菜单3", "测试菜单3-1", "测试菜单3-2"};
private string[] menu4 = new string[] { "测试菜单4"};
private string currentStr = "";
private void ShowPopMenu2()
{

    if (GUI.Button(new Rect(0, 50, 100, 30), "test2"))
    {
        ShowGenericMenu();
    }

}

private void ShowGenericMenu()
{
    if(menu==null)
    {
        CreateMenu();
    }
    menu.ShowAsContext();
}

private void CreateMenu()
{
    menu = new GenericMenu();
    CreateSubMenu(menu1);
    menu.AddSeparator("");
    CreateSubMenu(menu2);
    menu.AddSeparator("");
    CreateSubMenu(menu3);
    menu.AddSeparator("");
    CreateSubMenu(menu4);
}

private void CreateSubMenu(string[] strs)
{
    if(strs == null||strs.Length==0)
    {
        return;
    }
    string key = strs[0];
    List<string> subStrList = new List<string>();
    if(strs.Length>1)
    {
        for(int i = 1;i<strs.Length;i++)
        {
            AddToMenu(key + "/" + strs[i], strs[i]);
        }
    }
    else
    {
        AddToMenu(key, key);
    }
}

private void AddToMenu(string path,string content)
{
    menu.AddItem(new GUIContent(path), false, OnSelectMenu,content);
}

private void OnSelectMenu(object val)
{
    currentStr = (string)val;
	//后续处理
}

这样,后续需要修改菜单的选项,就可以单纯的修改定义的变量就可以了。

当然,不同的规则会让生成的代码不一样。我这里只是2层菜单,如果是多层的,只要想好生成的逻辑,都可以统一生成。

如果想实现选中了就打钩的效果,可以在上面的基础上修改一下:

    private GenericMenu menu;
    private string[] menu1 = new string[] { "测试菜单1", "测试菜单1-1", "测试菜单1-2", "测试菜单1-3", "测试菜单1-4" };
    private string[] menu2 = new string[] { "测试菜单2", "测试菜单2-1", "测试菜单2-2", "测试菜单2-3" };
    private string[] menu3 = new string[] { "测试菜单3", "测试菜单3-1", "测试菜单3-2"};
    private string[] menu4 = new string[] { "测试菜单4"};
    private string currentStr = "";
    private void ShowPopMenu2()
    {

        if (GUI.Button(new Rect(0, 50, 100, 30), "test2"))
        {
            ShowGenericMenu();
        }

    }

    private void ShowGenericMenu()
    {
        if(menu==null)
        {
            CreateMenu();
        }
        menu.ShowAsContext();
    }

    private void CreateMenu()
    {
        menu = new GenericMenu();
        CreateSubMenu(menu1);
        menu.AddSeparator("");
        CreateSubMenu(menu2);
        menu.AddSeparator("");
        CreateSubMenu(menu3);
        menu.AddSeparator("");
        CreateSubMenu(menu4);
    }

    private void CreateSubMenu(string[] strs)
    {
        if(strs == null||strs.Length==0)
        {
            return;
        }
        string key = strs[0];
        List<string> subStrList = new List<string>();
        if(strs.Length>1)
        {
            for(int i = 1;i<strs.Length;i++)
            {
                AddToMenu(key + "/" + strs[i], strs[i]);
            }
        }
        else
        {
            AddToMenu(key, key);
        }
    }

    private void AddToMenu(string path,string content)
    {
        menu.AddItem(new GUIContent(path), currentStr == content, OnSelectMenu,content);
    }

    private void OnSelectMenu(object val)
    {
        currentStr = (string)val;
        CreateMenu();
	}

主要是修改了在AddItem的时候,对比了传入的content是否和当前选择的currentStr相等,然后在OnSelectMenu回调时候,重新创建了一下菜单而已。

3、 优缺点

1. 优点

很明显,这个方式显示和逻辑方面都比较清晰,也可以通过规则统一生成和处理回调,感觉比较的规范。

2. 缺点

对比起MenuItem来说,GenericMenu的代码复杂程度会高一点,没有那么直观。不过我感觉也只是相对而已。

相关推荐
异次元的归来9 小时前
Unity DOTS中的share component
unity·游戏引擎
jinhuazhe201310 小时前
如何解决vscode powershell乱码
ide·vscode·编辑器
向宇it12 小时前
【从零开始入门unity游戏开发之——C#篇25】C#面向对象动态多态——virtual、override 和 base 关键字、抽象类和抽象方法
java·开发语言·unity·c#·游戏引擎
paintstar13 小时前
vscode 快速切换cangjie版本
ide·vscode·编辑器·仓颉·cangjie
科协软件201813 小时前
vscode+latex快捷键
ide·vscode·编辑器
_oP_i13 小时前
unity webgl部署到iis报错
unity
Go_Accepted13 小时前
Unity全局雾效
unity
向宇it14 小时前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
半糖112214 小时前
【VSCode】常用插件汇总
vscode·编辑器
cnnews14 小时前
在vscode中的ESP-IDF插件中使用Arduino框架作为组件
ide·vscode·编辑器