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的代码复杂程度会高一点,没有那么直观。不过我感觉也只是相对而已。

相关推荐
Sunny_GMF1 小时前
所见即所得编辑器原理实战:源码与渲染永不失真的三层一致性设计(Markdown/CodeMirror 装饰)
microsoft·编辑器·ai编程·harmonyos
警醒与鞭策10 小时前
【无标题】
android·unity·性能优化·游戏引擎·perforce
一路向北he10 小时前
trae或者vscode定义了文件,但是文件不亮是灰的
ide·vscode·编辑器
zhchyun200812 小时前
【Unity UI 进阶】仿 Element UI 打造企业级 Unity UI 组件库(10)
ui·unity·游戏引擎
地狱为王16 小时前
Unity使用NVIDIA的Audio2Face驱动口型
unity·游戏引擎
ebiobiz18 小时前
STM32F4 Unity 单元测试与多次测试失败率统计详细步骤
stm32·unity·单元测试
bro_Java66620 小时前
《栈与队列:数据结构的“双生花”》
java·数据结构·编辑器
u0111026751 天前
网页图片编辑器如何添加文字:字体、换行与导出一致性
图像处理·人工智能·ai作画·编辑器
Pointer Pursuit1 天前
编辑器Vim
linux·编辑器·vim
淡海水1 天前
16-01-C#数据结构源码-附录A-术语表
unity·c#·游戏引擎·il2cpp