当前位置: 首页 > article >正文

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实现

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


http://www.kler.cn/news/368243.html

相关文章:

  • Tongweb7049m4+THS6010-6012版本 传真实ip到后端(by yjm+lwq)
  • js纯操作dom版购物车(实现购物车功能)
  • Python 判断键是否存在字典中(新手入门、实战案例)
  • Flutter鸿蒙next 中如何实现 WebView【跳、显、适、反】等一些基础问题
  • 《复旦学报(自然科学版)》
  • 用接地气的例子趣谈 WWDC 24 全新的 Swift Testing 入门(一)
  • C++二级2023.9题及答案 -- 部分题
  • PHP短视频实训平台系统小程序源码
  • c# Solidworks二次开发---添加属性
  • 【mod分享】极品飞车10魔改模组,全新UI,全新道路,全新建筑,高清植被,全新的道路围栏,全新的天空,体验另一种速度与激情
  • DNS系统和ICMP协议
  • UVM初学篇 -(22)UVM field_automation 域的自动化机制
  • nfs服务器实验
  • 基于深度学习的语音情感识别与响应
  • C语言 - GNU C 和 ANSI C 之间的差异
  • spring boot 整合Knife4j
  • OpenStack将运行的系统导出 QCOW2 镜像并导入阿里云
  • D - Many Segments 2(AtCoder Beginner Contest 377)
  • 【Flask】二、Flask 路由机制
  • 三种SPI机制的了解及使用
  • linux创建自定义服务部署项目
  • 如何使用Golang的gomail库实现邮件发送功能
  • 将CSDN博客转换为PDF的Python Web应用开发--Flask实战
  • logback日志导入使用
  • 基于docker-compose编排部署微服务快速开发框架
  • GPIO按键驱动分析与使用:input_dev层