第 12 课
制作UI并实现交互
本课将制作一个能够在虚拟现实环境中使用的视频控制按钮。你会先理解普通屏幕界面与世界空间界面的区别,再创建 XR UI Canvas、导入播放图标,并用一个临时立方体确认按钮点击事件已经接通。
在虚拟博物馆中制作可点击的播放按钮
为蒙娜丽莎展区建立世界空间界面,完成按钮图标、XR 射线点击和事件调用,为下一课连接视频播放器做好准备。
学完你将能够
学习目标
- 解释 UI、Canvas 和 World Space 在虚拟现实界面中的作用。
- 区分 Screen Space - Overlay、Screen Space - Camera 和 World Space。
- 说出 XR 界面点击从射线输入到 On Click 的完整顺序。
- 创建包含 XR 射线检测能力的世界空间 Canvas。
- 调整 Canvas 的整体大小、位置和朝向。
- 创建只显示图标的按钮,并理解 Rect Transform 的主要属性。
- 把普通图片设置为 Sprite,并正确指定给 Source Image。
- 使用 On Click 调用 GameObject.SetActive(false)。
- 用最小测试任务判断界面交互链路是否已经接通。
- 移除临时测试内容,保留下一课需要的正式界面结构。
开始操作前
准备工作
- 确认 MainScene 可以正常运行,场景中的 XR Origin 包含可用的手柄或手部射线。
- 确认上一课的手部射线与界面戳按能够正常工作。
- 确认 XR Interaction Toolkit 已安装,Hierarchy 右键菜单中可以看到 XR 分类。
- 确认 Project 窗口中存在 _Textures 文件夹,或建立一个用于保存界面图片的文件夹。
- 保存 MainScene,并打开 Hierarchy、Scene、Game、Project 和 Inspector 窗口。
本课学习路线
按照学习模块逐步完成
每个模块都包含理解、跟随操作和独立实践。按钮可见和按钮可点击是两件事
理解世界空间界面与点击链路
Canvas 决定界面怎样进入场景,XR 射线检测和输入模块决定手柄或双手能否操作它。先分清显示与交互,后面的排错会更清楚。UI 与 Canvas 分别是什么
UI 是 User Interface 的缩写,可以理解为“用户界面”。它负责呈现信息和功能选择,例如作品说明、播放按钮、关闭按钮、菜单和操作提示。
Canvas 可以理解为“画布”,它是 Button、Image 和 Text 等界面元素的共同容器。按钮放在 Canvas 下,Unity 才能按照界面系统的规则布局和绘制它。
三种 Canvas 渲染模式
本课按钮要放在蒙娜丽莎画像附近,因此需要成为三维场景中的对象。
左右滑动表格,可查看完整内容
| 渲染模式 | 中文理解 | 主要特点 | 常见用途 |
|---|---|---|---|
| Screen Space - Overlay | 屏幕空间覆盖 | 界面直接覆盖最终画面 | 普通电脑游戏菜单和固定状态栏 |
| Screen Space - Camera | 屏幕空间摄像机 | 界面位于指定摄像机前方并跟随屏幕 | 由某台摄像机控制的界面 |
| World Space | 世界空间 | Canvas 像三维对象一样拥有位置、旋转和缩放 | 虚拟现实面板、说明牌和场景按钮 |
为什么本课使用 World Space
播放按钮需要位于画像附近,而不是一直贴在使用者视野上。切换为 World Space 后,Canvas 才能通过 X、Y、Z 位置进入场景,并根据使用者方向调整朝向。
World Space 只解决界面怎样显示。要让射线命中按钮,还需要射线交互器、界面射线检测、事件系统、XR 界面输入模块和 Button 共同工作。
沿箭头从左向右阅读:射线先命中世界空间 Canvas,XR 界面输入模块再把选择输入转换成点击,Button 最后执行 On Click 中的方法。
手机上可左右滑动;点击图片可以查看完整图点击链路中的关键对象
缺少任何一项,都可能出现“按钮看得见但点不到”的现象。
左右滑动表格,可查看完整内容
| 对象或组件 | 主要作用 | 缺少时的现象 |
|---|---|---|
| XR Ray Interactor | 从手柄或手部发出远距离射线 | 无法从远处指向按钮 |
| Tracked Device Graphic Raycaster | 让跟踪设备射线命中 Canvas 图形 | 射线无法把按钮识别为界面目标 |
| EventSystem | 管理当前界面的指针和选择事件 | 界面事件无法被统一分发 |
| XR UI Input Module | 把 XR 指针与选择输入转换成界面事件 | 射线可能命中,但按钮不产生正常点击 |
| Button | 接收点击并触发 On Click | 没有最终方法调用 |
复述一次完整点击链路
从手柄或双手开始,说明一个选择动作怎样让按钮执行方法。
- 说出射线交互器、Canvas、界面射线检测和输入模块。
- 明确 World Space 负责显示位置,不直接负责点击。
- 指出 Button 收到点击后会执行 On Click 中配置的方法。
把界面放到蒙娜丽莎展区
创建 XR 世界空间画布
先让使用者启动后直接看到目标展区,再通过 XR Interaction Toolkit 创建已经具备世界空间和 XR 射线检测基础的 Canvas。让使用者启动后面向蒙娜丽莎
- 01
在 Scene 窗口找到蒙娜丽莎画像位置。
- 02
选中 XR Origin Hands 根对象,不要选 Main Camera。
- 03
移动 XR Origin Hands,让起始位置靠近画像前方。
- 04
调整根对象的 Y 轴旋转,使使用者面向画像。
- 05
同时观察 Scene 和 Game 窗口,确认位置、高度和朝向。
- 06
保存场景。
先认识普通 Canvas 的 World Space
通过 UI → Canvas 创建的普通画布通常使用 Screen Space。它只有把 Render Mode 改成 World Space 后,才能像三维对象一样摆放到画像前方。
这一步能够帮助你理解渲染模式,但不会自动补齐 XR 界面输入组件。本课正式界面使用 XR → UI Canvas 创建。

World Space 让 Canvas 拥有可用于三维场景的 Rect Transform。注意它只改变界面摆放方式,还没有自动补齐 XR 输入设置。
点击图片可以查看完整截图使用 XR → UI Canvas
- 01
在 Hierarchy 空白处单击右键。
- 02
展开 XR 分类。
- 03
选择 UI Canvas。
- 04
等待 Unity 创建 Canvas 和需要的界面事件对象。
- 05
选中新 Canvas,在 Inspector 中查看组件。

选择 XR → UI Canvas,让 XR Interaction Toolkit 建立适合虚拟现实射线操作的世界空间画布。
点击图片可以查看完整截图创建后需要确认的四项设置
工具能够帮助建立基础结构,但仍要知道场景中实际出现了什么。
左右滑动表格,可查看完整内容
| 确认位置 | 正确状态 | 作用 |
|---|---|---|
| Canvas / Render Mode | World Space | 让画布进入三维场景 |
| Canvas 对象 | 具有 Tracked Device Graphic Raycaster | 接收手柄和手部射线 |
| Hierarchy | 存在 EventSystem | 管理界面事件 |
| EventSystem | 使用 XR UI Input Module | 把 XR 输入转换成界面点击 |
场景中只保留一套界面事件系统
如果场景之前已经存在 EventSystem,XR UI Canvas 可能沿用或调整已有对象。通常只保留一个负责当前界面的 EventSystem 和一个正在工作的输入模块。
两套事件系统同时处理同一界面,可能带来重复点击或输入行为不一致。
建立可被 XR 射线识别的画布
完成观察位置与画布创建,并用组件状态确认结果。
- XR Origin Hands 根对象已移动到适合观察画像的位置。
- Main Camera 没有被手动设置成固定位置。
- Canvas 的 Render Mode 为 World Space。
- Canvas 具有 Tracked Device Graphic Raycaster。
- 场景中只有一套 EventSystem 和 XR UI Input Module。
先控制整个界面模块,再调整子元素
调整画布并创建图标按钮
Canvas 使用像素进行布局,而三维场景使用米作为空间尺度。先缩放和摆放 Canvas 根对象,再在其中创建尺寸明确的按钮。为什么新画布看起来非常大
世界空间 Canvas 仍然使用像素尺寸排版。一个宽度上千像素的画布直接进入场景时会显得巨大,因此通常需要较小的整体 Scale。
本场景可以从 X、Y、Z 均为 0.005 开始,再根据观看距离和按钮可读性调整。这个数值只适用于当前界面,不是所有项目的固定答案。
控制整体大小、位置和朝向
- 01
选中 Canvas,按 T 切换到矩形工具,观察原始覆盖范围。
- 02
在 Rect Transform 中锁定缩放比例。
- 03
把 X、Y、Z Scale 先设置为 0.005。
- 04
按 W 移动画布,把它放到蒙娜丽莎画像下方。
- 05
根据使用者所在方向旋转 Canvas 根对象,让正面朝向使用者。
- 06
同时观察 Scene 和 Game 窗口,必要时继续调整。
Canvas 与按钮分别控制什么
整体大小与单个元素尺寸应由不同层级负责。
左右滑动表格,可查看完整内容
| 对象 | 主要控制内容 | 推荐做法 |
|---|---|---|
| Canvas 根对象 | 整组界面的场景大小、位置和朝向 | 整体等比例缩放和移动 |
| Button 的 Rect Transform | 按钮在 Canvas 中的位置与宽高 | 使用 Width、Height、Pos X 和 Pos Y |
| 图标图片 | 按钮的视觉内容和原始比例 | 使用 Sprite、Source Image 和 Set Native Size |
在 Canvas 下建立 Legacy Button
- 01
选中 Canvas,在它下面单击右键。
- 02
选择 UI → Legacy → Button。
- 03
确认 Button 成为 Canvas 的子对象。
- 04
展开 Button,删除默认的 Text 子对象。
- 05
保留 Button 根对象上的 Image 与 Button 组件。
- 06
让按钮暂时保持在 Canvas 中心。
Legacy Button 与 TextMeshPro Button
本课按钮只显示图标,不显示文字,因此使用结构更简单的 Legacy Button。
左右滑动表格,可查看完整内容
| 类型 | 文字组件 | 适合情况 |
|---|---|---|
| Button - Legacy | Unity 内置 Text | 简单界面或只显示图标的按钮 |
| Button - TextMeshPro | TextMeshPro | 需要清晰文字、字体资源和更多排版能力 |
Rect Transform 的常用属性
界面元素不只拥有位置、旋转和缩放,还具有矩形布局属性。
左右滑动表格,可查看完整内容
| 属性 | 作用 | 本课使用方式 |
|---|---|---|
| Width / Height | 界面元素的宽度和高度 | 设置按钮最终尺寸 |
| Anchor | 元素相对父对象的定位基准 | 保持中心锚点 |
| Pivot | 旋转和缩放中心 | 保持中心轴心 |
| Pos X / Pos Y | 相对锚点的位置 | 把按钮放在画布中心 |
完成画布与空白按钮布局
让使用者能够正面看到大小合适的画布和位于中心的按钮。
- Canvas 整体缩放统一,没有逐个缩放子元素。
- Canvas 位于画像附近且正面朝向使用者。
- Button 是 Canvas 的子对象。
- 不需要的 Text 子对象已经删除。
- Button 根对象仍保留 Image 和 Button 组件。
让图片成为清晰、可点击且不变形的界面图标
导入图标并设置按钮外观
VideoPlay 用作按钮的默认外观,VideoCancel 留给下一课表示停止或关闭状态。两张图片都需要转换为 Sprite,才能用于 Image 组件。下载播放图标
VideoPlay 用作本课按钮的默认外观。下载后把图片导入项目的 _Textures 文件夹。
- 128 × 128 像素
- 透明背景
- 按钮默认图标
下载停止图标
VideoCancel 将在下一课用于表示停止或关闭状态。本课先一起导入并完成相同设置。
- 128 × 128 像素
- 透明背景
- 下一课切换图标
把两个图标放进 _Textures
- 01
在 Project 窗口打开 _Textures 文件夹。
- 02
把 VideoPlay.png 和 VideoCancel.png 拖入该文件夹。
- 03
等待 Unity 完成导入。
- 04
确认 Project 窗口中出现两个独立图标资源。

两个图标应作为独立资源进入 _Textures 文件夹,方便按钮默认显示和下一课切换状态。
点击图片可以查看完整截图为什么普通图片不能直接作为 Source Image
图片导入 Unity 后通常先被当作模型表面的纹理,而 Button 上的 Image 组件需要 Sprite,也就是专门用于二维图形和界面的图片资源。
如果 Texture Type 仍是 Default,图片可能无法拖入 Source Image,或根本不会出现在选择列表中。
把两个图标设置为 Sprite
- 01
在 Project 窗口同时选中 VideoPlay 和 VideoCancel。
- 02
把 Texture Type 设置为 Sprite (2D and UI)。
- 03
把 Sprite Mode 保持为 Single。
- 04
把 Max Size 设置为 256。
- 05
点击 Apply,使设置生效。

同时处理两个图标:Texture Type 选择 Sprite (2D and UI),Max Size 设为 256,最后点击 Apply。
点击图片可以查看完整截图图标导入属性
这些设置决定图片能否用于界面,以及导入时使用多大分辨率。
左右滑动表格,可查看完整内容
| 属性 | 设置值 | 作用 |
|---|---|---|
| Texture Type | Sprite (2D and UI) | 让图片可用于 Image 和 Button |
| Sprite Mode | Single | 每张图片作为一个完整图标 |
| Max Size | 256 | 限制导入后的最大边长 |
| Apply | 点击应用 | 让当前设置真正生效 |
Max Size 不会把小图强行放大
Max Size 表示允许使用的最大边长。原图只有 128 × 128 像素时,设置 256 不会强行把它放大,只会限制更大图片导入时使用的上限。
小型按钮图标没有必要使用 2048 或 4096 的纹理尺寸,合理限制能够减少内存占用。
指定 Source Image 并恢复原始比例
- 01
选中 Button,在 Image 组件中找到 Source Image。
- 02
把 VideoPlay 拖到 Source Image。
- 03
确认按钮已经显示播放图标。
- 04
点击 Set Native Size,让图片先恢复原始宽高比。
- 05
保持 Image 的 Raycast Target 启用。

把 VideoPlay 指定给 Source Image 后先点击 Set Native Size。注意 Raycast Target 仍然保持勾选。
点击图片可以查看完整截图把按钮设置为 64 × 64
- 01
在 Button 的 Rect Transform 中找到 Width 和 Height。
- 02
把 Width 设置为 64。
- 03
把 Height 设置为 64。
- 04
确认图标保持正方形,没有被拉伸成椭圆。
- 05
在设备中观察按钮是否容易看清和命中,必要时等比例增大。

Width 和 Height 同时设为 64,使播放图标保持正方形。实际项目仍应根据观看距离调整。
点击图片可以查看完整截图完成按钮图标
从下载资源开始,完成两个图标的导入和播放按钮外观设置。
- VideoPlay 和 VideoCancel 已进入 _Textures 文件夹。
- 两个图标都是 Sprite (2D and UI),Sprite Mode 为 Single。
- Max Size 为 256,导入设置已经应用。
- VideoPlay 已指定给 Button 的 Source Image。
- 播放图标保持原始比例,按钮宽高均为 64。
- Image 的 Raycast Target 保持启用。
先证明按钮能调用方法,再连接视频功能
用最小任务测试 On Click
视频播放还会涉及播放器、渲染纹理和播放状态。先让按钮隐藏一个临时立方体,可以把点击链路和视频设置分开检查。为什么先使用临时立方体
如果点击按钮后立方体立即隐藏,就能证明 Canvas、射线、事件系统、Button、对象引用和 On Click 已经接通。
这个结果不能证明视频已经可以播放,也不能证明图标会自动切换。它只回答一个更简单的问题:按钮是否真正执行了配置的方法。
创建一个可见的临时 Cube
- 01
在 Hierarchy 中创建 Cube。
- 02
把 Cube 放在按钮旁边,确保 Game 窗口能够看到。
- 03
锁定 Scale 比例。
- 04
把 X、Y、Z Scale 都设置为 0.5。
- 05
保持 Cube 在运行前处于启用状态。
让按钮调用 SetActive(false)
- 01
选中 VideoControlBtn,在 Button 组件中找到 On Click()。
- 02
点击事件列表右下角的加号。
- 03
把场景中的临时 Cube 拖入对象引用框。
- 04
打开方法列表,选择 GameObject → SetActive (bool)。
- 05
保持布尔复选框不勾选,也就是传入 false。
- 06
保存场景。
SetActive 的布尔参数
On Click 每触发一次,Unity 就会使用当前参数调用一次目标方法。
左右滑动表格,可查看完整内容
| 参数 | 含义 | 运行结果 |
|---|---|---|
| true | 启用对象 | 对象显示并且组件参与运行 |
| false | 停用对象 | 对象隐藏并且组件停止工作 |
按现象顺序检查点击结果
- 01
进入运行模式,确认按钮和 Cube 都可见。
- 02
使用模拟器鼠标、真实手柄或双手指向按钮。
- 03
观察射线是否停在按钮上。
- 04
观察按钮是否产生悬停和按下变化。
- 05
执行选择输入,观察 Cube 是否立即隐藏。
- 06
确认 Console 没有出现新的错误。
第一处异常现象对应什么
从上到下观察,优先处理第一项不符合预期的现象。
左右滑动表格,可查看完整内容
| 现象 | 优先检查 |
|---|---|
| 按钮不可见 | Canvas 位置、朝向、缩放和按钮图片 |
| 射线不出现 | 手柄或手部射线交互器 |
| 射线穿过按钮 | Graphic Raycaster、Raycast Target 和正面方向 |
| 按钮没有悬停变化 | EventSystem、XR UI Input Module 和 Target Graphic |
| 按钮有按下变化,Cube 不隐藏 | On Click 对象、方法和 false 参数 |
完成一次可观察的点击测试
用临时 Cube 证明按钮能够通过 On Click 调用目标方法。
- Cube 在运行前可见,且位于按钮附近。
- 射线能够命中播放按钮。
- 按钮出现悬停和按下反馈。
- On Click 引用场景中的 Cube。
- 方法为 GameObject.SetActive(bool),参数为 false。
- 点击后 Cube 隐藏,Console 没有新增错误。
让场景只留下下一课真正需要的结构
清理测试内容并保留正式界面
临时 Cube 和 SetActive(false) 已经完成使命。移除测试内容后,保留命名清楚的 Canvas、Button 和两张图标,下一课再连接视频播放逻辑。移除临时对象和测试事件
- 01
退出运行模式。
- 02
选中 VideoControlBtn。
- 03
在 On Click 列表中移除 Cube 对应的事件。
- 04
删除临时 Cube。
- 05
确认 Canvas 和 Button 仍然存在。
保留清楚的界面层级
- 01
把 Canvas 命名为 VideoControllerUI。
- 02
把 Button 命名为 VideoControlBtn。
- 03
确认 VideoControlBtn 位于 VideoControllerUI 下面。
- 04
确认 VideoPlay 和 VideoCancel 仍保存在 _Textures 中。
- 05
按 Ctrl + S 保存 MainScene。
本课结束时保留与移除的内容
正式结构继续使用,最小测试内容完成后立即清理。
左右滑动表格,可查看完整内容
| 内容 | 处理方式 | 原因 |
|---|---|---|
| VideoControllerUI | 保留 | 下一课的视频控制界面容器 |
| VideoControlBtn | 保留 | 下一课连接视频播放方法 |
| VideoPlay / VideoCancel | 保留 | 表示播放与停止状态 |
| 临时 Cube | 删除 | 只用于观察点击结果 |
| SetActive(false) 测试事件 | 移除 | 避免与正式视频方法同时触发 |
把最小测试方法用于其他按钮
关闭按钮、菜单按钮和重置按钮都可以先连接一个简单、可观察的结果,确认点击链路后再替换为完整功能。
这样出现问题时,可以清楚判断是按钮没有收到点击,还是最终功能本身的设置有误。
综合操作:从空白画布完成可点击按钮
按照真实学习顺序独立完成整个界面,不跳过中间的点击测试。
- 把 XR Origin Hands 调整到能够观察蒙娜丽莎的起始位置。
- 创建 XR UI Canvas,并确认世界空间和 XR 输入组件。
- 缩放、移动和旋转 Canvas,在其下创建无文字按钮。
- 下载并导入两个图标,把它们设置为 Sprite。
- 为按钮设置 VideoPlay、原始比例、64 × 64 和 Raycast Target。
- 用 Cube 与 SetActive(false) 完成一次点击测试。
- 测试成功后删除 Cube 和测试事件。
- 按 VideoControllerUI → VideoControlBtn 整理层级并保存场景。
常见现象与处理方法
根据最早出现的问题回到相应对象,不要一次修改多处设置。
左右滑动表格,可查看完整内容
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
| Canvas 占满整个场景 | 世界空间画布尚未整体缩放 | 确认 World Space,并从 0.005 开始等比例调整 Canvas |
| Canvas 不能沿 Z 轴摆放 | 仍在使用 Screen Space | 改为 World Space 或重新创建 XR UI Canvas |
| 按钮可见但射线无法命中 | 缺少 XR 射线检测、Raycast Target 关闭或方向相反 | 检查 Graphic Raycaster、Raycast Target 和 Canvas 正面 |
| 射线停在按钮上但按下无反应 | 输入模块、Button 或 On Click 未接通 | 检查 XR UI Input Module、Interactable 和事件配置 |
| 点击一次触发两次 | 存在两套 EventSystem、输入模块或重复事件 | 只保留一套界面事件系统并移除重复 On Click |
| 图标无法拖入 Source Image | Texture Type 不是 Sprite | 改为 Sprite (2D and UI) 并点击 Apply |
| 圆形图标变成椭圆 | 宽高比例不一致 | 先 Set Native Size,再把 Width 和 Height 设为相同值 |
| 设备中按钮很难命中 | 按钮太小、距离太远或射线不稳定 | 等比例增大按钮,并放到自然抬手能够指向的位置 |
| 点击后 Cube 没有隐藏 | 对象、方法或布尔参数错误 | 确认引用场景 Cube,使用 SetActive(false) |
| 只有鼠标能点,设备输入不能点 | 射线、UI Press 或平台输入配置未接通 | 回看上一课手部输入,并检查射线的 UI Interaction |
本课成果清单
逐项确认后,视频控制界面已经具备下一课连接播放器所需的完整基础。
- 我能区分屏幕空间与世界空间 Canvas。
- 我能说出 XR 界面点击事件的完整顺序。
- VideoControllerUI 是 World Space,并具有 XR 界面射线检测。
- 场景中只有一套 EventSystem 和 XR UI Input Module。
- VideoControlBtn 位于 Canvas 中,方向和大小适合当前观察位置。
- VideoPlay 与 VideoCancel 都已设置为 Sprite。
- 按钮显示未变形的播放图标,Raycast Target 保持启用。
- 播放按钮能够接收手柄或手部射线点击。
- 临时 Cube 测试已经成功,测试对象和事件已经移除。
- 场景只保留 VideoControllerUI、VideoControlBtn 和两张图标。
- MainScene 已保存,可以继续连接视频播放功能。
把知识变成作品
本课练习
请按顺序完成以下任务,并保存自己的学习成果。- 01
在另一个展品旁复制 VideoControllerUI,重新调整位置、朝向和按钮大小,使它在新的观看距离下仍然容易操作。
- 02
暂时关闭 Button Image 的 Raycast Target,观察射线和点击发生什么变化,再恢复设置并说明原因。
- 03
把最小测试方法应用到一个新的关闭按钮:先让它隐藏一个测试对象,成功后再移除临时事件。
把本课进度保存下来
登录后可以保存浏览内容、有效学习时间、完成状态和答题参与记录。