3D 世界里的 UI 比屏幕 UI 更容易出错
很多 3D 游戏会把 UI 放进世界里:门禁面板、飞船驾驶台、商店招牌、角色头顶菜单、VR 风格按钮、训练场操作台。Godot 可以用 Mesh、Label3D、SubViewport、ViewportTexture、Area3D 和 RayCast3D 实现这些效果。问题是,世界空间 UI 同时受相机、光照、遮挡、距离、输入和焦点影响,比普通 Control 更复杂。
世界 UI 的目标是沉浸,但不能牺牲交互清晰。玩家要知道哪个按钮可点、射线打到了哪里、面板是否被遮挡、手柄如何选择、低分辨率下文字是否可读。
flowchart TD
A[玩家输入/鼠标/手柄] --> B[屏幕射线或准星射线]
B --> C[Physics RayCast3D]
C --> D{命中世界 UI Area?}
D -->|否| E[清除 hover]
D -->|是| F[转换到面板局部坐标]
F --> G[SubViewport/Control 派发事件]
G --> H[按钮状态反馈]
H --> I[业务命令]
先决定是真 3D 还是 Viewport UI
世界 UI 有两种常见做法。第一种是真 3D:用 Mesh、Label3D、Sprite3D、Area3D 组合,按钮就是世界对象。第二种是 SubViewport 里放普通 Control,再把 ViewportTexture 贴到 3D 平面上,射线命中后转换坐标派发给 Control。
真 3D 适合简单按钮、招牌、头顶标签。它和场景光照融合好,但复杂布局、滚动列表、输入框会很麻烦。Viewport UI 适合复杂面板,可以复用 Godot Control 系统,但要处理分辨率、纹理清晰度、输入转发和性能。
项目不要混乱使用。复杂可操作面板优先 Viewport UI,简单提示优先 Label3D/Sprite3D。这样维护成本更低。
射线交互要有明确层
世界 UI 的点击通常来自相机射线或准星射线。射线应该只检测交互层,不要被普通装饰物误挡,除非设计上遮挡确实应该阻止交互。碰撞层和 mask 要单独配置。
命中 UI 面板后,要把世界坐标转换到面板局部坐标,再映射到 SubViewport 像素坐标。这个映射要考虑面板尺寸、翻转、缩放和 UV。坐标错一点,按钮边缘就会点不到。
Hover 状态也要稳定。射线每帧检测,如果命中目标抖动,按钮会闪。可以做轻微滞后或只在目标变化时更新。输入按下和释放最好命中同一面板,否则拖动或抖动会触发错误点击。
可读性受距离和角度影响
世界 UI 不是永远正对玩家。面板角度太斜,文字会难读;距离太远,纹理会糊;场景太亮或太暗,按钮状态不清楚。设计世界 UI 时,要给可读距离和角度范围。超出范围可以隐藏交互提示或自动转为屏幕 UI。
ViewportTexture 分辨率要按面板大小和预期距离设置。分辨率太低,文字糊;太高,内存和渲染成本增加。动态面板不应每帧重绘复杂 UI,除非内容真的变化。
重要交互最好有多重反馈:准星变化、按钮高亮、音效、轻震、说明文本。只靠颜色变化,在 3D 光照里可能不明显。
手柄和键盘需要焦点模式
鼠标射线适合 PC,手柄玩家可能需要准星、最近目标选择或焦点导航。世界 UI 可以在玩家看向面板并按交互键后进入“面板焦点模式”,此时左摇杆或方向键在 Control 内导航,取消键退出。
焦点模式要锁定输入上下文,避免玩家操作面板时角色还在移动。退出时恢复 gameplay 输入。这个过程和普通屏幕 UI 类似,但要考虑角色是否仍在危险场景中。
如果支持 VR 或触控,交互射线来源又不同。架构上应让输入层提供“指针射线”,世界 UI 不关心它来自鼠标、手柄准星还是 VR 控制器。
遮挡和状态同步
世界 UI 可能被门、角色、特效遮挡。是否允许隔墙操作要由射线层决定。通常交互射线应该被 World 阻挡,再检测 UI。若 UI 是全息投影,可能允许穿过部分装饰。规则要清楚。
面板业务状态也要和模型同步。玩家在世界面板上购买物品,结果仍由商店模型或服务端确认。面板关闭或玩家走远时,未完成事务要取消或继续等待。不要让世界 UI 成为业务事实来源。
小结
Godot 世界空间 UI 要在沉浸和清晰之间取舍。简单提示用 3D 节点,复杂面板用 SubViewport,射线层和坐标映射要稳定,距离角度影响可读性,手柄需要焦点模式,业务状态仍回到模型。世界 UI 做得好会很自然,做得乱会比普通菜单更难用。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
我会准备一个世界 UI 测试房间,放不同距离、角度、光照和遮挡的面板,用鼠标、手柄、触控分别操作。只有在这张房间里都可靠,才把同类 UI 放进正式关卡。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。