资讯动态

Unity UGUI零代码搭建多级下拉菜单:Button+Panel+UnityEvent实战

发布时间:2026/9/13 7:40:43 来源:尧图企业网站定制
做UI时遇到一个很常见的需求多级下拉菜单。网上搜一圈大多是用Dropdown组件加脚本或者写一套自定义逻辑对不会写代码的策划、刚入门的Unity新手来说不太友好。其实用UGUI自带的Button、Panel加UnityEvent纯靠Inspector拖拽配置就能做出一套支持任意层级的下拉菜单一个脚本都不用写。这篇文章就把我踩过的坑和完整的搭建步骤整理出来适合想在Unity里快速做原型、又不希望被代码逻辑绑住的人。看完你能搭出一套点击展开、点外部关闭的一级二级甚至三级菜单并且能处理掉菜单被遮挡、点击没反应、位置不对这类常见问题。1. 先想清楚你要的“下拉菜单”是哪种1.1 下拉选择器与多级导航菜单不是一回事在动手之前我建议先分清楚“下拉菜单”到底指什么。UGUI里自带的Dropdown组件做出来的效果是“下拉选择器”点击一个框弹出选项列表选完一项列表收起显示值变成你选中的那项。它的核心交互是“选择”不是“导航”而且它天然只支持一级选项再多也只能平铺在一个列表里。如果你试图用Dropdown去做网站那种“鼠标移到菜单项旁边弹出子菜单”的效果会非常别扭。而“多级下拉菜单”通常指的是界面顶部的菜单栏例如“文件 - 导出 - 导出为PDF”这种层级关系点击或悬停“文件”出现一级菜单“导出”再指向“导出”旁边继续出现二级菜单“导出为PDF”“导出为图片”。这种菜单解决的是“功能入口的导航和收纳”不是“选择器”。两者的交互逻辑和视觉形态都有区别所以技术选型也应该分开。UGUI自带的Dropdown适合做前者而今天要讲的多级菜单更适合用Button Panel UnityEvent来搭。1.2 用UGUI的Button、Panel加UnityEvent为什么可行Unity的UGUI从设计上就把“事件绑定”做成了可视化操作。任何一个Button组件都带有一个OnClick事件列表你可以把场景里任意对象的公开方法拖到列表里。比如GameObject.SetActive(bool)就是一个很典型的公开方法我们在Inspector里给它一个参数true或false就能控制任意UI对象的显示和隐藏。这一套操作完全不需要写脚本但它本质上还是调用了引擎的方法只是把代码执行变成了编辑器配置。这种方式的优点很明显搭建速度快改起来直观。以前我做一个菜单流程先写脚本、挂引用、再逐个拖对象现在只要把Panel当成普通物件B按钮负责打开C按钮负责关闭事件列表里拖一拖就完成了。对于原型验证、策划调界面、培训机构做Demo或者是美术同学想自己搭一套完整的交互流程都非常省事。不过它也有局限如果你需要从服务器读取菜单数据、动态生成菜单项或者菜单项数量不固定那光靠配置就不够了还是得上脚本。所以下面的方案更适合“菜单结构已经确定、纯前端展示和交互”的场景。2. 整体设计Canvas层级与UI结构规划2.1 Canvas、EventSystem和CanvasScaler准备工作在Unity里做UI第一件事就是确保Canvas存在。如果你是用菜单栏创建UI控件Unity会自动帮你生成Canvas和EventSystem如果场景里已经有Canvas那就直接在里面加Panel和Button。这里我建议大家把Canvas的渲染模式设置为Screen Space - Overlay这是UI最常用的模式不受摄像机遮挡影响做菜单类界面非常稳定。接着要处理CanvasScaler。很多新手不知道这个组件的作用其实它就是负责UI在不同分辨率屏幕上的缩放效果。我一般把UI Scale Mode设置为Scale With Screen Size参考分辨率按项目来PC项目常见1920x1080移动端常见1080x1920。这样就算游戏窗口被拖大拖小UI的缩放比例也基本一致不容易出现菜单在某种分辨率下跑偏的情况。最后检查场景里有没有EventSystem。很多按钮点了没反应第一个想到的就是脚本对不对但八成是场景里没有EventSystem或者EventSystem被意外删了。EventSystem是UGUI处理输入事件的入口没有它Button、Toggle、Dropdown全部“失灵”。Unity默认生成的EventSystem包含EventSystem组件和Standalone Input Module如果没有自己改Input System保持默认就行。2.2 决定UI物体的渲染顺序和层级结构这个部分是整个方案的核心我单独拎出来讲。UGUI的渲染顺序不是看Z坐标而是看Hierarchy面板里的从上到下的顺序。更准确地说在同一个Canvas下越靠后的节点渲染时越靠前也就是会盖住前面的UI。我们可以利用这个规则来处理菜单的遮挡关系。我习惯把层级结构规划成下面这个样子背景UI比如游戏主界面、说明文字全屏遮罩Button透明或半透明点击后关闭菜单一级菜单Panel也就是菜单栏的主菜单容器二级菜单Panel从一级菜单的某个项弹出三级菜单Panel继续往下弹这个顺序的关键是遮罩放在背景UI后面但要放在所有菜单Panel前面。渲染的时候背景UI最先画遮罩接着画上去盖住背景菜单Panel最后画所以菜单可以正常显示在遮罩之上。如果遮罩不小心放在菜单Panel后面那菜单会被遮罩盖住点都点不到。可能有朋友会问为什么不把二级菜单Panel直接放到一级菜单Panel里其实也可以两者各有利弊。子物体方式的最大好处是关闭一级菜单时二级菜单会自动跟着被隐藏不需要额外绑定关闭事件坏处是如果二级菜单Panel超出一级Panel的显示范围在某些情况下会让人困惑而且如果一级菜单本身带Mask组件二级菜单就会被裁剪掉。所以我建议在没有Mask的情况下优先用平级Panel如果项目UI层级比较复杂更操心的还是“父物体隐藏会带走子物体”的特性那也可以放到子物体下。两种方式搭配今天的事件绑定都能实现。2.3 菜单Panel的尺寸与锚点规划Panel的尺寸和锚点决定了菜单出现在屏幕哪里。很多教程会直接给你一组固定坐标但实际项目里菜单栏位置千变万化所以更重要的是理解锚点。我做一个常见示例菜单栏整体在屏幕左上角“文件”按钮作为一级菜单入口。那么一级菜单Panel可以这样设置RectTransform的Anchor Min和Anchor Max都设为(0, 1)也就是锚定在父节点的左上角Pivot设为(0, 1)意思是这个Panel自己的原点在左上角SizeDelta设为(200, 300)表示菜单宽度200、高度300AnchoredPosition设为(0, 0)这样面板左上角刚好贴在Canvas左上角然后向右向下展开二级菜单Panel我建议也锚定在左上角或者更精确一点锚定在一级菜单Panel的右上角右侧。如果是平级关系可以让二级MenuLevel2的Anchor同样设为(0,1)Pivot设为(0,1)AnchoredPosition设为(210, 0)也就是离一级菜单Panel右边10像素的位置往下弹出的方向和一级菜单一致。如果需要二级菜单从一级菜单的某个具体按钮旁边冒出来可以再微调Pivot和Position。这些数值不是死记硬背的改起来也很容易右键场景里的Panel用RectTransform工具拖动也能看到效果。最重要的是明白锚点是“对齐参考系”Pivot是“自身旋转缩放原点”Position是“相对锚点的偏移”。把这几个概念理清楚后续做三级、四级菜单都不会迷路。3. 实操搭建从一级菜单到二级菜单3.1 创建一级菜单Panel并添加菜单项现在开始正式搭建。我假设你已经有一个Canvas并且EventSystem存在。右键Canvas选择UI - Panel把它命名为MenuLevel1。这个Panel就是一级菜单容器。默认生成的Panel自带一个Image颜色是半透明白。我们保留Image把颜色改成自己想要的背景色比如深灰色 #2D2D2DAlpha不透明度设为255。这样菜单在界面上会有一块明显的深色背景比较像传统菜单栏。接着在MenuLevel1下面继续右键创建Button命名为Item_Export并把按钮上的Text改为“导出”。重复操作创建几个菜单项比如“新建”“打开”“导出”“设置”“退出”。这些菜单项就是一级菜单里的具体功能入口。这里有个非常容易被忽略的点Button里面的Image的Raycast Target默认是开启的。这个勾选状态决定了这个Image会不会参与UI点击检测。如果把它关了按钮的点击区域会失效就算按钮本身是Interactable也会点不动。所以做UI时尽量不要乱关Image的Raycast Target特别是Button、Toggle这类需要接收点击的组件。菜单项之间建议留一点间距可以用Layout组件来实现。在MenuLevel1下面挂一个Vertical Layout Group并把子物体按钮的Position都交给布局组件管理这样菜单项会自动垂直排列。再配合Content Size Fitter把Panel的高度设为Auto Size就免去了手动调每个按钮Y坐标的麻烦。这个纯配置操作依然不需要代码。3.2 创建二级菜单Panel并调整位置一级菜单做出来后接着做二级菜单。还是右键Canvas创建新的Panel命名为MenuLevel2。这个Panel就是点击“导出”后出现的子菜单。调整MenuLevel2的RectTransformAnchor Min和Anchor Max设为(0,1)Pivot设为(0,1)SizeDelta设为(160, 120)AnchoredPosition设为(210, 0)这里的210是MenuLevel1的宽度200加上10像素的间隔。如果你的MenuLevel1不在左上角而是放在中间那MenuLevel2的锚点就要参考MenuLevel1的实际位置来调整。这个步骤没有标准答案核心思路是“二级菜单贴近一级菜单的右侧边缘两者留几像素间距避免视觉上贴在一起”。然后在这个MenuLevel2下面创建几个Button分别命名为“导出为PDF”“导出为图片”“导出为Excel”。这些按钮的配置和一级菜单项完全一样只需要把Text改成对应内容。3.3 通过UnityEvent配置显隐逻辑菜单界面都搭好了接下来就是最关键的“不需要代码”的部分配置Button的事件。先给场景里加一个能在点击菜单栏时打开一级菜单的入口。这个入口可以是一个在Canvas顶部区域的普通Button比如“文件”。选中“文件”Button看Inspector里的OnClick列表点加号新增一个条目。把MenuLevel1物体拖到Object那一栏然后在下拉方法列表里选择GameObject.SetActive(bool)。这时候条目旁边会出现一个bool勾选框如果想让MenuLevel1显示就勾上它如果想让MenuLevel1隐藏就取消勾选。再选中一级菜单里的“导出”按钮在它的OnClick里增加一个条目把MenuLevel2拖进去选择GameObject.SetActive(bool)并勾选true。这样点击“导出”二级菜单就会打开。现在还有一个问题如果已经打开了二级菜单再点一次“导出”它还是保持打开状态不会关闭。想要“再点一次二级菜单收起”的效果纯靠一个Button的OnClick调用一次SetActive是做不到的因为SetActive只有true/false两种状态没有“取反”的逻辑。但我可以提供另一个思路在“导出”按钮的OnClick里增加多个条目先执行MenuLevel2.SetActive(false)再执行MenuLevel2.SetActive(true)。这样每次点击都会先收起再展开视觉上就等于“重置”了菜单。如果需要二级菜单关闭就要靠后面说的遮罩方案。3.4 加入全屏遮罩实现点击外部关闭菜单如果不支持点击外部关闭用起来会很折磨人。我们可以用一个全屏Button当作遮罩实现“点击菜单外任意区域把菜单全部关掉”。创建一个全屏的Button命名为GlobalOverlay。将它放在Canvas下、所有菜单Panel之前但要在背景UI之后。注意Hierarchy顺序背景UI在上GlobalOverlay其次MenuLevel1和MenuLevel2在下面。把GlobalOverlay的RectTransform拉满整个CanvasAnchor Min设为(0,0)Anchor Max设为(1,1)SizeDelta保持(0,0)。Image的颜色建议设为黑色Alpha设为80/255左右这样菜单弹出时背景会变暗用户视线会更集中。如果你不想要蒙层效果可以把Alpha设为0但Image的Raycast Target一定要保持勾选否则这个Button接收不到点击事件。然后在GlobalOverlay的Button组件的OnClick里加三个条目MenuLevel1GameObject.SetActive不勾选boolMenuLevel2GameObject.SetActive不勾选boolGlobalOverlay自身GameObject.SetActive不勾选bool这样点击遮罩菜单全部关闭遮罩自己也隐藏。当点击“文件”按钮打开菜单时需要额外在“文件”按钮的OnClick里增加一个GlobalOverlay.SetActive(true)的条目并且让它先于MenuLevel1的打开动作执行。这样每次打开菜单时遮罩都会先出现点击遮罩后整个世界清净了。再补充一个小技巧如果担心遮罩把自己关闭后下次点“文件”时又得重新配置可以在“文件”按钮的OnClick里把GlobalOverlay.SetActive(true)放在第一条然后才是MenuLevel1.SetActive(true)、MenuLevel2.SetActive(false)。这样不管遮罩当前是什么状态打开菜单时都会被激活足够安全。4. 三级菜单扩展与细节优化4.1 继续往深了做三级、四级菜单二级做完三级其实就是一个重复劳动。再创建一个MenuLevel3锚定在MenuLevel2右侧Pivot设置好AnchoredPosition设置为(170, 0)然后在里面放若干按钮。事件配置也很简单在二级菜单的“导出为图片”按钮的OnClick里把MenuLevel3拖进去SetActive(true)。当点击遮罩关闭时记得继续在遮罩OnClick里补一个MenuLevel3.SetActive(false)。如果MenuLevel3是放在MenuLevel1或者MenuLevel2下面作为子物体并且父物体被SetActive(false)它就会自动隐藏不需要额外追加事件。不过为了保险我一般会在遮罩里把每一级菜单的关闭事件都显式写上这样即使以后调整了层级结构也不会出现一个漏网菜单挂在那里。如果你要的是四级、五级菜单方法一模一样继续新建Panel继续配置按钮事件继续在遮罩关闭列表里追加一条。这套方案的扩展成本几乎是线性的每多一级只是多几次拖拽配置。4.2 菜单方向、屏幕边缘和位置修正菜单弹出方向也是实际开发里经常需要调整的。一级菜单在屏幕左侧时二级菜单往右弹出很自然但如果你把菜单栏放在右上角二级菜单再往右弹就会溢出屏幕跑到可视范围外面去。解决办法是调整二级菜单Panel的锚点和Pivot。比如让MenuLevel2锚定到MenuLevel1的左边可以用Anchor Min和Anchor Max设为(1,1)Pivot设为(1,1)AnchoredPosition设为(-10,0)。这样MenuLevel2会贴着MenuLevel1的左边缘向左展开不会跑出屏幕。另外还要注意CanvasScaler。如果你的参考分辨率是1920x1080但实际运行窗口可能更宽或更窄UGUI会自动缩放整个Canvas所以只要你是在Canvas坐标系里做的相对定位大多数情况下都不会差太多。但如果你用绝对像素标注了面板之间的间隔在极端宽高比下还是可能贴边。建议多开几个屏幕比例测试一下特别是做PC端时常见的16:9和16:10差异不大但21:9的超宽屏就要考虑菜单栏居中和子菜单方向的问题了。4.3 给菜单加上悬停展开与简单的入场动画点击展开已经够用了但如果你想要鼠标悬停就自动展开也不难只是换一个事件组件。在Button之外UGUI还提供了EventTrigger我们可以给菜单项挂一个EventTrigger在Pointer Enter事件里配置MenuLevel2.SetActive(true)这样鼠标移上去菜单就会展开。由于EventTrigger的配置方式和Button.OnClick几乎一样仍然不需要写代码。不过悬停展开有一个坑鼠标从一级菜单项移动到二级菜单时中间会经过一段空白区域如果这段空白区域没有UI可点击对象鼠标一旦离开一级菜单项Pointer Exit就会触发二级菜单可能立刻关闭。处理方式有两个一是保证一级菜单Panel和二级菜单Panel之间没有太大缝隙二是干脆用点击展开而不是悬停展开。我自己的经验是原型阶段用悬停比较炫但正式项目里如果菜单层级很深点击展开反而更可控用户不容易误操作。想要一点入场动画也可以不写Animator脚本。给MenuLevel2加上Animator组件创建两个Animation Clip一个叫Open一个叫Close分别记录Panel从透明到不透明、从缩小到正常大小的动画。然后在遮罩关闭事件里不直接SetActive(false)而是调用Animator.Play(Close)并在Animation窗口的动画事件末尾再SetActive(false)。但这些就涉及到Animation Event的配置比纯SetActive要复杂一些。如果你只是想加一个缩放效果用一个CanvasGroup做淡入淡出的效率也不错但CanvasGroup的alpha没法直接在UnityEvent里拖出来所以还是那句纯配置能不做复杂动画就别硬啃优先保证菜单功能稳定。5. 常见问题与排查技巧实录5.1 按钮点击无反应这个问题我遇到太多次了最后总结下来无非这几类场景缺少EventSystem、按钮的Interactable被关了、Button所在的Image Raycast Target被关了、或者有其他透明全屏UI挡在前面。排查顺序建议从EventSystem开始选中EventSystem看Inspector里有没有EventSystem组件和Standalone Input Module没有就补上。然后选中按钮确认Interactable是勾选状态再确认按钮Image的Raycast Target是勾选状态。最后看Hierarchy里按钮的上层是不是有全屏Panel抢占了点击事件可以把那些Panel暂时隐藏再试。5.2 菜单被其他UI遮挡UGUI的渲染顺序就是Hierarchy顺序越靠后的节点显示越靠前。所以如果菜单被遮挡先把它在Hierarchy里的位置往下移移到Canvas子物体列表的末尾。这个操作不改变位置坐标但会改变渲染先后立刻能看到效果。如果菜单上面还有别的独立Canvas比如Debug信息Canvas、新手引导Canvas它们各自的Sort Order可能会影响整体遮挡这种情况就要去调Canvas的Sort Order值了值越大越靠前。菜单Panel所在的CanvasSort Order应该大于普通背景UI的Canvas否则就算菜单Panel在Hierarchy后面也可能被高Sort Order的Canvas盖住。5.3 SetActive状态冲突有时候你会发现菜单一会儿开一会儿关或者打开后立刻被关掉多半是同一个按钮的OnClick里配置了互相矛盾的事件。UnityEvent列表是从上到下按顺序执行的所以如果你先加了一条MenuLevel1.SetActive(true)又在下面加了一条MenuLevel1.SetActive(false)最终结果一定是隐藏。解决方案是把OnClick列表里的条目看成一个线性流程先执行的先影响后执行的覆盖前执行。想要“打开菜单”就只留置true的那一条想要“关闭菜单”就只留置false的那一条不要图方便把两个状态都写上去。5.4 二级菜单位置错乱二级菜单出现在奇怪的位置大多是锚点和Pivot的问题。很多新手直接改AnchoredPosition却发现改了几百像素菜单还是在原地纹丝不动因为那是相对于锚点的偏移而锚点本身可能已经跑到很远的角落去了。我的建议是先把Anchor Min和Anchor Max设清楚再设Pivot最后再调AnchoredPosition。这三个参数有严格的先后依赖关系锚点决定参考系Pivot决定面板自身对齐点AnchoredPosition决定最终偏移。如果你不想动脑最简单的方法是把Panel拖到目标Button下面变成它的子物体然后把Anchor设为(1,1)Pivot设为(0,1)AnchoredPosition设为(0,0)这样二级菜单会自动对齐到目标Button的左上角微调几像素间距就完事。5.5 纯配置方案的局限性前面一直在说不写代码但这里我得说点实在话。纯配置方案有一个很尴尬的点当你把菜单系统的逻辑全都放在各个按钮的OnClick列表里时项目里的引用关系会变得很碎。比如一个遮罩关闭菜单OnClick里有十几个关闭条目一旦菜单项改名或者重构这些引用会断排查起来很麻烦。而且如果你有几十个按钮每个按钮都手动拖引用效率并不高。所以更合理的做法是小项目、原型、演示Demo用纯配置正式项目做菜单系统建议还是写一个简单的MenuController脚本用代码统一管理Panel显隐和层级关系。这两种方案不冲突甚至可以混用UI结构用Panel手动搭交互逻辑用脚本来驱动维护起来会更舒服。6. 再聊聊Dropdown组件什么时候用它更合适6.1 UGUI自带的Dropdown适合什么场景如果你只是在做一个设置界面要从“低、中、高、极高”里选一个画质或者从分辨率列表里选一项那问题的本质是“单选”这种情况直接拖一个UGUI Dropdown组件是最快的。你不需要写任何代码在Dropdown的Options里一条一条添加选项文字就行了。运行时点击下拉框弹出列表选择后自动收起回填内容UI的细节都是现成的。但如果你想用Dropdown去做“分类 - 子项目”这样的联动选择比如选了“广东”下一级Dropdown自动刷新成“广州、深圳、珠海”那纯快捷键配置就行不通了。因为Dropdown的选项内容不能在Inspector里动态更新必须靠代码去改Dropdown.options列表。所以我的结论是单选设置用Dropdown导航层级菜单用ButtonPanel。两者不要在同一个功能里生搬硬套。6.2 Dropdown与ButtonPanel方案对比维度UGUI自带DropdownButtonPanelUnityEvent核心用途单选选项菜单导航与层级展示多级支持不支持可以逐级展开动态数据需要脚本改options需要脚本生成Panel和按钮零代码配置可以可以自定义视觉需要重构模板完全自由维护复杂度低中事件引用比较分散适合场景设置项、分类选择菜单栏、右键菜单、导航说实话我现在做项目时已经很少纠结“UE和UGUI哪个更强”这种问题更多是看需求选工具。Dropdown负责“从一堆东西里挑一个”ButtonPanel负责“把一堆功能入口组织起来”两者配合好整个UI的开发效率会高很多。最后再分享一点个人经验。我一开始做多级菜单时也执着于“不写代码”这个噱头后来发现纯配置方案真正厉害的地方不是让你永远不写代码而是让你在动手写代码之前先用最笨最直白的方式把交互流程验证清楚。等到菜单结构、层级关系、弹出方向都确定之后再决定要不要把逻辑抽成脚本会比一上来就埋头写代码稳得多。如果你也正在做Unity UI建议先按今天这套步骤搭一个两级的菜单原型出来亲手点一点感受一下UGUI的事件托拽流程。真到了三级菜单甚至四、五级菜单的时候你会对层级和状态控制有完全不一样的理解。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价