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

news2024/10/28 11:42:49

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.coloradmin.cn/o/2225405.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

西南大学软件专硕考研难度分析!

C哥专业提供——计软考研院校选择分析专业课备考指南规划 西南大学软件工程学硕近三年呈现出招生规模稳定、复试线稳中有升的特点。2024届实际录取8人&#xff0c;复试分数线305分&#xff0c;复试录取率67%&#xff0c;相比去年复试线略有下降但仍高于2022届&#xff0c;显示出…

Maven 项目构建打包,如何引入本地 Jar 包?

上一篇讲到 Maven 离线仓库的使用&#xff0c;反响不错很多人收藏&#xff0c;这一篇还是继续聊 Maven 。假如你发现某开源项目有个 bug 影响到自己的系统&#xff0c;但官方还没修复&#xff0c;自己定位到了本地修改打了包先应急用&#xff0c;那么如何在其他项目上使用该包&…

【动态规划】力扣198.打家劫舍

目录 一、题目二、思路1.递归2.递推 三、代码 一、题目 二、思路 1.递归 题目中指出不可以选相邻的房间&#xff0c;说明如果选了第 1 间&#xff0c;那么第 2 间一定不可以选&#xff0c;第 3 间房间可以选&#xff0c;也可以不选……假设是按照从第 1 间房间开始依次往后选…

【测试】——Fiddler入门

&#x1f4d6; 前言&#xff1a;本文介绍Fiddler&#xff0c;一个强大的HTTP协议调试代理工具。文章详细讲解了Fiddler的安装步骤、基础操作、抓包技巧以及模拟测试等内容。 目录 &#x1f552; 1. Fiddler基础介绍&#x1f558; 1.1 安装&#x1f558; 1.2 基础操作&#x1f5…

[oeasy]python038_ range函数_大小写字母的起止范围_start_stop

range函数_大小写字母的起止范围_start_stop 回忆上次内容 所有字符 都有序号 就连 空格 也不例外 空格 序号32是 print函数中 sep参数的 默认值 字符 在计算机中 用数字序号 来 存储表示 字符序号 有规律 a 对应 97b 对应 98c 对应 99 连续字母 对应 连续序号 似乎应该是 天经…

SOLID - 接口隔离原则(Interface Segregation Principle)

SOLID - 接口隔离原则&#xff08;Interface Segregation Principle) 定义 接口隔离原则&#xff08;Interface Segregation Principle&#xff0c;ISP&#xff09;是面向对象设计中的五个基本原则之一&#xff0c;通常缩写为SOLID中的I。这一原则由Robert C. Martin提出&…

校园气膜体育馆:学生锻炼与成长的新空间—轻空间

在现代教育中&#xff0c;学生的身心健康日益受到重视&#xff0c;校园体育设施的建设成为学校发展的重要一环。为更好地满足学生在节假日锻炼与学习的需求&#xff0c;校园气膜体育馆应运而生&#xff0c;成为校园内一处崭新的活力空间。 打破场地限制&#xff0c;打造优质运动…

LeetCode 热题 100之矩阵

1.矩阵置0 思路分析&#xff1a;使用标记数组 记录需要置为 0 的行和列&#xff1a;使用两个布尔数组 zeroRows 和 zeroCols 来记录需要置为 0 的行和列两次遍历 第一遍遍历整个矩阵&#xff0c;找到所有为0的元素&#xff0c;并更新zeroRows和zeroCols&#xff1b;第二遍遍历…

快速入门HTML

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗 如有错误&#xff0c;欢迎指出~ 目录 第一个html文件 标签 h1~h6 p >段落标签 br > 换行标签 img >图片标签 a >超链接标签 表格标签 表单标签 表单控件 form表单 ⽆语义标签:div&span 综…

虚拟现实在制造业中的应用

当你想到制造业中的虚拟现实技术时&#xff0c;你脑海中闪过的第一个念头是什么&#xff1f;从目前来看&#xff0c;只需几年时间&#xff0c;制造业就将离不开虚拟现实技术的帮助。实施虚拟现实应用对制造业来说都有诸多好处。通常情况下&#xff0c;制造设施都是由各种机器组…

右键以vscode打开目录的时候出现找不到应用程序

出现这个问题的主要原因&#xff0c;大概率可能是因为你移动了vscode的安装路径导致的。 解决办法 打开注册表&#xff1a;通过cmd 打开regedit 然后搜索&#xff1a;计算机\HKEY_CLASSES_ROOT\Directory\Background\shell 这个两个参数可以自己比对一下&#xff0c;主要需要检…

【再谈设计模式】单例模式~唯一性的守护者

一、引言 在软件工程中&#xff0c;软件开发&#xff0c;设计模式是提高代码复用性和可维护性的有效工具。单例模式&#xff08;Singleton Pattern&#xff09;作为一种创建型设计模式&#xff0c;旨在确保一个类只有一个实例&#xff0c;并提供对该实例的全局访问。这一模式在…

unity 导入的模型设置详谈

文章目录 1.Model 模型1.1 Scene&#xff1a;场景级属性&#xff0c;例如是否导入灯光和照相机&#xff0c;以及使用什么比例因子1.2 Mesh&#xff1a;网格的属性1.3 Generate &#xff1a;与几何相关的属性&#xff0c;用于处理拓扑&#xff0c;UV和法线 2.Rig 骨骼3.Animatio…

C语言小游戏3——扫雷

扫雷游戏 1. 扫雷游戏的功能说明 使用控制台实现经典的扫雷游戏游戏可以通过菜单实现继续玩或者退出游戏扫雷的棋盘是 9*9的格子默认随机布置 10个雷可以排查雷如果位置不是雷&#xff0c;就显示周围有几个雷如果位置是雷&#xff0c;就炸死游戏结束把除10个雷之外的所有雷都找…

C语言 | Leetcode C语言题解之第514题自由之路

题目&#xff1a; 题解&#xff1a; int findRotateSteps(char* ring, char* key) {int n strlen(ring), m strlen(key);int pos[26][n], posSize[26];memset(posSize, 0, sizeof(posSize));for (int i 0; i < n; i) {int x ring[i] - a;pos[x][posSize[x]] i;}int dp…

H3C vlan和trunk配置

vlan和trunk配置实验 实验拓扑图 实验需求 1.配置pc的IP地址 2.在sw1和sw2上分别创建vlan10和vlan20&#xff0c;要求pc3和pc5输入vlan10&#xff0c;pc4和pc6属于vlan20 3.sw1和sw2相连的接口配置为trunk类型&#xff0c;运行vlan10和vlan20通过 4.测试效果&#xff0c;同…

Springboot整合原生ES依赖

前言 Springboot整合依赖大概有三种方式&#xff1a; es原生依赖&#xff1a;elasticsearch-rest-high-level-clientSpring Data ElasticsearchEasy-es 三者的区别 1. Elasticsearch Rest High Level Client 简介: 这是官方提供的 Elasticsearch 客户端&#xff0c;支持…

大语言模型数据类型与环境安装(llama3模型)

文章目录 前言一、代码获取一、环境安装二、大语言模型数据类型1、基本文本指令数据类型2、数学指令数据类型3、几何图形指令数据类型4、多模态指令数据类型5、翻译指令数据类型三、vscode配置四、相关知识内容1、理解softmax内容2、torch相关函数nn.Embedding函数torch.nn.fun…

《手写Spring渐进式源码实践》实践笔记(第十一章 AOP-基于JDK、Cglib实现对象动态代理)

文章目录 第十一章 基于JDK、Cglib实现对象动态代理背景目标设计实现代码结构类图代理案例解析案例代码运行结果拆解案例 实现步骤 测试事先准备自定义拦截方法测试用例测试结果&#xff1a; 总结 第十一章 基于JDK、Cglib实现对象动态代理 背景 到本章节我们将要从 IOC 的实现…

Java版本的基于计算机视觉的跃动小子保卫主公自动通关计划之整体思路篇

系列文章 Java版本的基于计算机视觉的跃动小子保卫主公自动通关计划之图片分割篇 图片分类篇,执行计划生成篇,执行篇等后续篇章持续更新中,欢迎关注 文章目录 系列文章Java版本的基于计算机视觉的跃动小子保卫主公自动通关计划之整体思路篇构建二维数组构建消除时形态7个元素的…