第 21 课
交互优化:使系统菜单可被拖拽
第20课已经让菜单在隐藏时准备位置、打开后固定。本课将在这个基础上把位置控制权交给 XR 抓取系统:先制作专用拖拽栏和三维命中范围,再为 MenuUI 配置刚体、可抓取组件与两个抓取变换器。完成后,菜单既能被移动,又不会随着控制器前后倾斜或左右侧翻。
用拖拽栏移动系统菜单
为系统菜单增加清楚的拖拽入口,让你能够用手柄射线或手部调整菜单位置与水平方向,同时保持菜单直立、稳定,并保留按钮点击功能。
学完你将能够
学习目标
- 说明菜单自动定位与抓取系统为什么不能同时改写位置。
- 解释 UIManager 在菜单隐藏、显示、抓取、释放和再次隐藏时的职责。
- 导入 DragBar 图片,并在 MenuUI 底部建立清楚的拖拽提示。
- 区分界面矩形和三维碰撞体,建立容易命中的抓取范围。
- 说明为什么碰撞体放在 DragBar,而抓取组件放在 MenuUI。
- 正确设置 XR Grab Interactable 与 Rigidbody,使菜单释放后稳定停留。
- 解释动态附着点如何减少抓取瞬间的跳动。
- 区分 X、Y、Z 三个旋转轴,并说明为什么只允许 Y 轴旋转。
- 解释通用抓取变换器与旋转轴锁定变换器的职责和执行顺序。
- 完成射线、手部、按钮点击、抓取、释放、隐藏和再次打开的综合操作。
开始操作前
准备工作
- 打开虚拟博物馆主场景,确认 MenuUI、PageContainer、NavigatorBar 和独立的 Scripts 对象都存在。
- 确认第20课的左手 X 键能够稳定显示和隐藏系统菜单。
- 确认菜单打开后固定在空间中,转动头部时不会继续追随视线。
- 确认项目使用 XR Interaction Toolkit 3.1.2,并已导入 Starter Assets(起始资源)中的旋转轴锁定抓取变换器;如果添加组件时搜索不到,可在 Package Manager 的 XR Interaction Toolkit 示例中导入 Starter Assets。
- 确认手柄射线或手部已经能够选择普通三维交互对象。
- 打开 Hierarchy(层级面板)、Inspector(属性面板)、Scene(场景视图)和 Console(控制台)。
- 进入运行前保存场景,并等待当前脚本完成编译。
本课学习路线
按照学习模块逐步完成
每个模块都包含理解、跟随操作和独立实践。把点击区域和移动入口区分开
制作清楚的菜单拖拽栏
菜单正文、按钮和导航区域用于阅读与点击。专用 DragBar(拖拽栏)放在菜单底部,明确告诉你应从哪里抓住并移动菜单。为什么不能让整张菜单随处可抓
如果菜单任何位置都能开始抓取,你想点击按钮时可能误把菜单拖走,射线落在正文区域也可能触发移动。拖拽栏让“点击界面”和“移动菜单”具有不同入口。
DragBar 是 MenuUI 的直接子对象。移动父对象 MenuUI 时,PageContainer、NavigatorBar 和 DragBar 会一起移动。
下载 DragBar.png
这是一张细长的圆角横条图片。导入 Unity 后先设置为界面精灵,再用于 DragBar 的 Image 组件。
- 图片尺寸为 299 × 10 像素
- 可通过 Image 组件的颜色调整最终显示颜色
- 保持原始宽高比,避免横条变形
DragBar 图片导入设置
Sprite 可以理解为适合二维界面显示的图片资源。
左右滑动表格,可查看完整内容
| 属性 | 设置值 | 作用 |
|---|---|---|
| Texture Type | Sprite (2D and UI) | 允许图片用于界面 Image 组件 |
| Max Size | 512 | 为小型界面图片保留足够清晰度 |
| Wrap Mode | Clamp | 防止图片边缘重复铺开 |
| Filter Mode | Bilinear | 缩放时进行平滑采样 |
在 MenuUI 底部创建 DragBar
- 01
把 DragBar.png 导入项目的界面纹理目录并应用导入设置。
- 02
在 Hierarchy 中选中 MenuUI,创建 UI → Image。
- 03
把新对象命名为 DragBar,并保持它是 MenuUI 的直接子对象。
- 04
把 DragBar.png 指定到 Image 的 Source Image。
- 05
保持 Image Type 为 Simple,并点击 Set Native Size 恢复原始尺寸。
- 06
把 Image 颜色设置为与当前菜单风格一致的黑色。
- 07
把 DragBar 移到菜单底部,保持居中且不遮挡按钮。

观察重点:DragBar 位于 MenuUI 下;Source Image 已指定;Image Type 为 Simple;右侧可使用 Set Native Size 恢复图片原始比例。
点击图片可以查看完整截图当前菜单的 Rect Transform 参考值
数值只适用于当前菜单尺寸;真正的完成标准是位于底部、居中并且不遮挡按钮。
左右滑动表格,可查看完整内容
| 属性 | 参考值 | 观察标准 |
|---|---|---|
| Pos X | -3 | 横向接近居中 |
| Pos Y | -307 | 位于菜单底部 |
| Pos Z | 0 | 与菜单界面保持同一平面 |
| Width | 299 | 与原图宽度一致 |
| Height | 10 | 保持细横条外观 |
Simple 与 Set Native Size 分别做什么
Image Type 为 Simple 时,整张图片直接显示,适合尺寸固定的细横条。Sliced(九宫格拉伸)更适合需要大幅改变尺寸的面板或边框,本课不需要。
Set Native Size 会把界面矩形恢复到图片原始尺寸。它只改变可见图片的矩形,不会自动创建三维抓取范围。
完成可见拖拽提示
把 DragBar 做成清楚但不过分抢眼的移动入口。
- 确认 DragBar 是 MenuUI 的直接子对象。
- 确认 Source Image、Simple 和原始尺寸均已设置。
- 让横条位于菜单底部并接近居中。
- 确认 PageContainer 和 NavigatorBar 没有被遮挡。
可见横条负责提示,碰撞体负责命中
为拖拽栏建立三维抓取范围
选中 DragBar 时看到的界面边框并不是三维碰撞体。要让手柄射线或手部真正命中拖拽入口,还需要添加 Box Collider(盒形碰撞体)。界面矩形与三维碰撞体的区别
两种范围可能在场景视图中同时出现,但用途不同。
左右滑动表格,可查看完整内容
| 范围 | 由什么产生 | 主要用途 |
|---|---|---|
| 界面矩形 | Rect Transform 与 Image | 显示图片并参与界面点击 |
| 三维抓取范围 | Box Collider | 让物理射线或手部交互命中 |
添加并扩大 Box Collider
- 01
选中 DragBar,不要选中 MenuUI。
- 02
添加 Box Collider,并保持 Is Trigger 关闭。
- 03
先把 Size 设为容易观察的较大数值,再点击 Edit Collider。
- 04
拖动各个面的控制点,让碰撞体覆盖拖拽栏。
- 05
调整单侧时可按住 Alt,让两侧围绕中心对称变化。
- 06
让范围比可见横条略宽、略高,并保留一定厚度。
- 07
确认碰撞体没有向上覆盖菜单按钮。

观察重点:绿色三维盒形范围略大于黑色横条,并具有一定厚度;右侧 Box Collider 的 Is Trigger 保持关闭。
点击图片可以查看完整截图当前场景的碰撞体参考值
这些是 DragBar 的局部坐标,不是直接以米为单位的世界尺寸。
左右滑动表格,可查看完整内容
| 属性 | X | Y | Z |
|---|---|---|---|
| Center | 9.74 | 1.31 | 0 |
| Size | 388.82 | 27.05 | 8.83 |
为什么数值很大,场景中的范围却不大
MenuUI 是世界空间画布,当前缩放约为 0.001。碰撞体的 Center 和 Size 使用对象的局部坐标,还会乘以父对象缩放,因此数百的局部数值进入场景后会缩小约一千倍。
不要只比较数字大小,应在场景视图中观察最终范围。碰撞体可以比可见横条稍大,提高远距离射线命中率,但不能覆盖上方按钮。
完成易命中且不误触的范围
把抓取响应范围调整到容易命中,同时保留按钮点击区域。
- 让碰撞体完整覆盖可见 DragBar。
- 把高度适度扩大,方便远距离射线命中。
- 保留一定 Z 轴厚度,形成真正的三维范围。
- 从场景正面确认碰撞体没有盖住菜单按钮。
- 保持 Is Trigger 关闭。
先观察问题,再决定要限制什么
观察菜单旋转并确定允许的轴
基础抓取会读取控制器的完整旋转,因此菜单可能前仰、后倒或侧翻。先通过一次有目的的操作观察三个轴,再确定只保留水平方向转动。分别移动和旋转控制器
- 01
进入运行模式并按左手 X 键显示菜单。
- 02
先用射线指向菜单按钮,确认按钮仍然可以点击。
- 03
再指向 DragBar,按住选择键并移动控制器。
- 04
前后倾斜控制器,观察菜单是否前仰或后倒。
- 05
左右侧翻控制器,观察菜单是否向一侧倾倒。
- 06
水平转动控制器,观察菜单朝向是否改变。
- 07
释放选择键,确认菜单停留在当前位置。
菜单的三个旋转轴
以下判断以菜单初始保持直立为前提。
左右滑动表格,可查看完整内容
| 旋转轴 | 直观动作 | 本课是否允许 | 原因 |
|---|---|---|---|
| X 轴 | 向前或向后倾斜 | 否 | 文字会前仰或后倒 |
| Y 轴 | 水平方向左转或右转 | 是 | 可以调整面向你的方向 |
| Z 轴 | 向左或向右侧翻 | 否 | 文字会歪斜,影响阅读 |
为什么不能直接关闭 Track Rotation
完全关闭 Track Rotation 会让菜单无法根据控制器调整任何方向。你把菜单移动到侧面后,可能无法让它重新面向自己。
本课需要的是选择性限制:保留 Y 轴的水平转向,锁定 X、Z 轴的倾斜。这需要抓取变换器继续处理旋转结果。
用动作辨认三个轴
保持抓取状态,分别做前后倾斜、水平转向和左右侧翻。
- 指出哪一种动作对应 X 轴。
- 指出哪一种动作对应 Y 轴。
- 指出哪一种动作对应 Z 轴。
- 说明为什么菜单只应保留 Y 轴旋转。
通用变换器负责产生结果,轴向锁定负责修改结果
理解抓取变换器与默认补充规则
Grab Transformer 中文可称为抓取变换器,也可以理解为“抓取姿态处理器”。它会读取位置、旋转和缩放结果,再按照自己的规则生成新的结果。这里的 Transformer 不是人工智能模型
本课中的 Transformer 表示“把一种抓取姿态转换成另一种抓取姿态”,与人工智能课程中的 Transformer 模型不是同一个概念。
XR Grab Interactable 会把目标姿态依次交给列表中的变换器。每个变换器读取上一个结果,再继续处理。
抓取姿态包含什么
姿态不仅是位置,还包括方向和对象大小。
左右滑动表格,可查看完整内容
| 数据 | 中文含义 | 本课处理方式 |
|---|---|---|
| Position | 三维空间位置 | 由通用抓取变换器更新 |
| Rotation | 旋转方向 | 先计算,再锁定 X、Z 轴 |
| Scale | 局部缩放 | 本课不允许改变 |
两个变换器的职责
轴向锁定只会修改已有旋转结果,不能独立产生完整移动。
左右滑动表格,可查看完整内容
| 组件 | 主要职责 |
|---|---|
| XR General Grab Transformer | 计算基础位置、旋转和可能的缩放 |
| Rotation Axis Lock Grab Transformer | 在已有结果上锁定不允许的旋转轴 |
列表为空时为什么仍然能够抓取
当 Add Default Grab Transformers 开启,并且运行时的单手变换器列表仍为空时,工具包会自动补入 XR General Grab Transformer,所以空列表也可能具有基础移动能力。
一旦手动加入轴向锁定变换器,列表不再为空,工具包就不会继续自动补入通用变换器。此时如果列表里只有轴向锁定,菜单缺少生成基础位移的组件,可能出现“抓住了却不能移动”。
两个相近的变换器列表
本课 Select Mode 为 Single,因此只配置单交互器列表。Starting 表示进入运行时最先使用的配置。
左右滑动表格,可查看完整内容
| 列表 | 何时使用 | 本课处理 |
|---|---|---|
| Starting Single Grab Transformers(初始单手抓取变换器列表) | 一个交互器选择对象 | 需要配置 |
| Starting Multiple Grab Transformers(初始多手抓取变换器列表) | 两个或更多交互器同时选择对象 | 本课不使用 |
先观察默认补充,再观察缺少通用变换器
- 01
退出运行模式,确认 Add Default Grab Transformers 开启,并让单手列表保持为空。
- 02
进入运行模式,观察工具包自动加入的 XR General Grab Transformer。
- 03
退出运行模式,添加 Rotation Axis Lock Grab Transformer。
- 04
把 Permitted Rotation Axis 设为只允许 Y。
- 05
在单手列表中暂时只把轴向锁定放到 Element 0(列表第 1 项)。
- 06
再次运行并尝试抓取,观察菜单是否缺少正常位移。
- 07
观察完成后立即退出运行模式,不保存这个中间配置,并在下一模块恢复完整列表。
解释一个有价值的中间现象
不要只记住最终配置,要说清为什么“只有轴向锁定”会失败。
- 说明列表为空时是谁提供基础移动。
- 说明列表加入轴向锁定后为什么不再为空。
- 说明轴向锁定为什么不能独立产生位移。
- 写出最终还缺少的通用抓取变换器名称。
先生成完整姿态,再限制不允许的旋转轴
按正确顺序组合两个抓取变换器
变换器列表具有执行顺序。Element 0(列表第 1 项)先产生基础移动和旋转,Element 1(列表第 2 项)再把 X、Z 轴恢复为初始角度,只保留 Y 轴转向。顺序为什么会改变最终结果
前一个变换器写入的目标姿态,会成为后一个变换器继续处理的输入。如果轴向锁定先执行,后面的通用抓取又重新计算完整旋转,限制结果可能被覆盖。
真正决定先后的是 Starting Single Grab Transformers(初始单手抓取变换器列表)中的 Element 0 和 Element 1,不是两个组件在属性面板中的上下位置。
观察重点:Element 0 先生成位置与旋转,Element 1 再保留 Y 轴并恢复 X、Z 初始角度;下方对比了空列表、缺少通用变换器和最终正确列表。
手机上可左右滑动;点击图片可以查看完整图最终单手抓取列表
两个组件都添加在 MenuUI 上,并按照下面的顺序放入列表。
左右滑动表格,可查看完整内容
| 列表位置 | 指定组件 | 作用 |
|---|---|---|
| Element 0(列表第 1 项) | XR General Grab Transformer | 生成基础移动与旋转 |
| Element 1(列表第 2 项) | Rotation Axis Lock Grab Transformer | 只保留 Y 轴旋转 |
完成两个变换器和列表顺序
- 01
退出运行模式并选中 MenuUI。
- 02
添加 XR General Grab Transformer。
- 03
保持它的基础位移参数为当前默认值。
- 04
确认 Rotation Axis Lock Grab Transformer 只允许 Y 轴。
- 05
在 Starting Single Grab Transformers 中准备两个元素。
- 06
把通用抓取变换器指定给 Element 0(列表第 1 项)。
- 07
把旋转轴锁定变换器指定给 Element 1(列表第 2 项)。
- 08
保存场景。
一次抓取的完整执行链
抓取期间,每一帧都会重复读取交互器姿态并生成新的菜单姿态。
左右滑动表格,可查看完整内容
| 顺序 | 负责对象 | 发生的事情 |
|---|---|---|
| 1 | 手柄射线或手部 | 命中 DragBar 的 Box Collider |
| 2 | XR 交互管理器 | 让一个交互器选择 MenuUI |
| 3 | Use Dynamic Attach | 根据命中处建立临时附着点 |
| 4 | XR Grab Interactable | 准备本帧的位置、旋转和缩放结果 |
| 5 | Element 0(列表第 1 项) | 通用抓取变换器生成基础移动与旋转 |
| 6 | Element 1(列表第 2 项) | 轴向锁定保留 Y,恢复 X、Z 初始角度 |
| 7 | MenuUI.Transform | 应用最终位置与旋转 |
| 8 | UIManager.LateUpdate | 发现菜单显示并提前结束,不覆盖抓取结果 |
| 9 | 释放选择键 | 停止抓取计算并保留最终姿态 |
轴向锁定怎样处理每个轴
变换器建立运行时关联时会记录初始角度,之后用它恢复不允许的轴。
左右滑动表格,可查看完整内容
| 轴 | 是否允许 | 处理结果 |
|---|---|---|
| X | 否 | 恢复记录的初始 X 角度 |
| Y | 是 | 保留通用抓取计算出的 Y 角度 |
| Z | 否 | 恢复记录的初始 Z 角度 |
运行确认最终抓取姿态
分别测试移动、水平转向和两种倾斜,不能只观察菜单是否能被抓住。
- 从 DragBar 抓住并向不同方向移动菜单。
- 水平转动控制器,确认菜单可以绕 Y 轴转向。
- 前后倾斜控制器,确认菜单不随 X 轴倾倒。
- 左右侧翻控制器,确认菜单不随 Z 轴倾倒。
- 释放后确认菜单保留最终位置和水平朝向。
把知识变成作品
本课练习
请按顺序完成以下任务,并保存自己的学习成果。- 01
画出“命中 DragBar → 选择 MenuUI → 通用抓取 → 轴向锁定 → 应用姿态”的完整链路。
- 02
不查看属性表,说出 Rigidbody、XR Grab Interactable 和 Box Collider 分别放在哪个对象上。
- 03
用自己的话解释为什么菜单显示后 UIManager 必须停止自动定位。
- 04
把 X、Y、Z 三个旋转轴分别对应到前后倾斜、水平转向和左右侧翻。
- 05
故意交换两个变换器的顺序,观察现象后恢复为通用抓取在前、轴向锁定在后。
- 06
调整 DragBar 碰撞体大小,找到容易命中又不会干扰按钮点击的范围。
把本课进度保存下来
登录后可以保存浏览内容、有效学习时间、完成状态和答题参与记录。