屏幕 UI
了解如何为你场景中的玩家创建 UI。例如,这可用于显示与游戏相关的信息。
你可以为你的场景构建一个 UI,将其显示在屏幕的固定 2D 空间中,而不是 3D 世界空间中。
只有当玩家站在场景的 LAND 地块内时,UI 元素才可见,因为相邻场景可能有自己要显示的 UI。UI 的某些部分也可以在世界空间中发生特定事件时触发打开,例如玩家点击某个特定位置。
通过定义一个嵌套结构来构建 UI UiEntity 的 JSX 对象。UI 所使用的语法与以下内容非常相似: React (一个非常流行的基于 JavaScript 的 Web UI 构建库)。
📔 注:你只能在具有以下扩展名的文件中定义 UI 语法: .tsx 扩展名。 .tsx 文件支持 .ts .ts 文件支持的一切内容,外加 UI 语法。我们建议创建一个 ui.tsx 文件,并在那里定义你的 UI。记得从 index.ts 中调用你的 UI 渲染方法,使用 ReactEcsRenderer.setUiRenderer(yourUiMethodName),见下例。
带有静态元素的简单 UI 看起来很像 HTML,但当你加入会响应状态变化的动态元素时,就能实现强大得多的功能。
默认的 Decentraland 浏览器 UI 包括聊天组件、地图和其他元素。这些 UI 元素始终显示在最顶层,位于任何场景专属 UI 之上。因此,如果你的场景中有占据相同屏幕空间的 UI 元素,它们就会被遮挡。
参见 UX 指南 ,了解如何设计你的 UI 的外观和体验的建议。
当玩家点击屏幕右下角的 关闭 UI 按钮时,所有 UI 元素都会隐藏。
渲染 UI
要在你的场景中显示 UI,请使用 ReactEcsRenderer.setUiRenderer() 函数,并传入一个有效的实体结构,该结构在一个 .tsx 文件中描述。
每个实体都定义为一个类似 HTML 的节点,并通过属性为其各个组件进行设置。
ui.tsx 文件:
import { UiEntity, ReactEcs } from '@dcl/sdk/react-ecs'
import { Color4 } from '@dcl/sdk/math'
export const uiMenu = () => (
<UiEntity
uiTransform={{
width: 700,
height: 400,
margin: { top: '35px', left: '500px' },
}}
uiBackground={{ color: Color4.Red() }}
/>
)index.ts 文件:
你也可以将实体结构的定义和渲染放在同一个命令里完成, .tsx 文件中描述。
ui.tsx 文件:
index.ts 文件:
📔 注:你的所有 UI 元素都需要嵌套在同一结构中,并且该结构的根部只能有一个父级。你在场景中只能调用 ReactEcsRenderer.setUiRenderer() 一次。
UI 实体
UI 中的每个元素都必须定义为一个单独的 UiEntity,无论它是图像、文本、背景、不可见的对齐框等。就像场景的 3D 空间中一样,每个 UiEntity 都拥有自己的组件来为其提供位置、颜色等。
类似 React 的语法允许你将每个组件指定为内的一个属性,这使代码更简洁、更易读。 UiEntity,这使代码更简洁、更易读。
在一个 UiEntity 中使用的组件与常规实体使用的组件不同。你不能将 UI 组件应用到常规实体上,也不能将常规组件应用到 UI 实体上。
以下组件可在一个 UiEntity:
uiTransformuiBackgrounduiTextonMouseDownonMouseUponMouseEnteronMouseLeaveuiInputBinding—— 当元素被按住时保持输入动作,以构建自定义控件
像 HTML 标签一样,你可以将组件定义为自闭合,或者将一个组件嵌套在另一个组件中。
警告: 保持全屏布局包装器不要添加指针处理器。将任何 onMouse 处理器,或 pointerFilter: 'block',添加到一个 UiEntity 按尺寸设定的 100% 由 100% 组成的对象上,会使它捕获屏幕上的每一次点击,导致你其他所有 UI 以及整个 3D 世界都无法点击。请把处理器放到需要它们的面板或按钮上。参见 指针阻挡.
ui.tsx 文件:
index.ts 文件:
一个 UI 模块的定义只能有一个顶层父实体。你可以定义任意多的其他实体,但它们都必须位于一个顶层只有单一父实体的结构中。
屏幕虚拟缩放
为 UI 设置一个虚拟宽度和高度。这样可以确保你的 UI 在不同屏幕尺寸上看起来相同,而不受实际像素屏幕尺寸影响。
如果你将虚拟宽度设为 1920,虚拟高度设为 1080,UI 就会缩放以适配屏幕尺寸。如果屏幕是 1920x1080,UI 会以与虚拟尺寸相同的大小显示。如果屏幕更大或更小,任何像素值都会按比例缩放以适配虚拟尺寸。例如,如果屏幕是 3840x2160,那么一个定义为宽 100 像素的项目将显示为 200 个实际像素。
实际用于乘以像素值的 UI 缩放因子的计算公式是 Math.min(realWidth / virtualWidth, realHeight / virtualHeight).
默认虚拟尺寸
虚拟尺寸是可选的,但它的两个维度必须一起传入:要么同时传入两个,要么都不传。当你没有传入时,会应用平台默认值:
移动端
1600x720
桌面端和网页端
1920x1080
这意味着你的 UI 中的像素值始终会相对于一个参考分辨率进行缩放,即使你完全不传任何选项也是如此。即使场景只使用 addUiRenderer() 而从不调用 setUiRenderer().
三个特殊情况:
取消缩放:传入一个无效尺寸——任何值,只要它是
00 或更小——即可完全禁用虚拟屏幕。此时像素值会被直接当作原始画布像素使用,不进行缩放。这是关闭虚拟屏幕的文档化方式,因此不会记录任何内容。不完整的尺寸:只提供两个维度中的一个也是无效的,因此它会像上面的情况一样禁用虚拟屏幕——平台默认值 不会 介入。与上面的主动取消不同,这种情况会被报告:只给出一半的尺寸是错误,而不是有意的选择,因此 SDK 会记录
无效的虚拟屏幕尺寸(…):两个维度都必需,因此虚拟屏幕已被禁用,并且不会应用 UI 缩放。每种尺寸只会记录一次。请同时传入两个维度,或者都不传。在主渲染器上尤其值得再次检查,因为那里的不完整尺寸也是一个场景级开关:一个
setUiRenderer()提到 任一 维度的调用优先于任何addUiRenderer(),因此上面的示例会为整个场景禁用虚拟屏幕,并丢弃传递给附加渲染器的有效尺寸。移动端上的 16:9 尺寸:手机屏幕比 16:9 宽得多,因此 16:9 的虚拟画布会给 UI 加上上下黑边。如果你传入一个 16:9 尺寸(
1920x1080,1280x720,…),并且场景在移动端运行,那么它会被覆盖为1600x720,并且会向控制台记录一条消息。移动端上的非 16:9 尺寸,以及桌面端或网页端上的所有有效尺寸,都会原样保留。
📔 在此 SDK 版本之前编写的场景有哪些变化
有三项变更会影响现有场景。它们都不是可选启用的,所以即使一个场景完全不做任何改动,只要更新了 SDK 版本,外观也会有所不同:
虚拟屏幕现在默认启用。 过去,未传入任何选项的场景会将像素值按原始画布像素进行布局。现在它会相对于
1920x1080(1600x720(移动端)。若要恢复以前的行为,请显式使用setUiRenderer(ui, { virtualWidth: 0, virtualHeight: 0 }).screenInset默认为'device'. 你的 UI 现在位于设备安全区域内,因此在移动端会向内偏移,而且根级的100%不再覆盖整个屏幕——全屏背景、暗化层和覆盖层会在刘海和主页指示条之前停止。若要恢复以前的行为,请传入setUiRenderer(ui, { screenInset: 'none' })。参见 屏幕内嵌区域.devicePixelRatio不参与 UI 布局。 按像素大小设置的 UI 在高密度(视网膜和移动端)屏幕上现在会比以前大 2–3 倍。 这一项没有关闭选项 —— 请重新检查任何手工调过的尺寸,并移除场景自己计算出的任何缩放因子。
屏幕内嵌区域
屏幕并非完全可用:手机会为刘海、状态栏、主页指示条和圆角预留空间,而每个浏览器都会在画布的一部分上绘制自己的 HUD(小地图、聊天等)。渲染器选项中的可选 screenInset 属性用于选择你的 UI 所定位的屏幕区域:
'device' (默认)
设备安全区域,不包括刘海、状态栏和圆角。读取自 UiCanvasInformation.screenInsetArea.
'interactable'
浏览器报告为不受其自身 HUD(小地图、聊天等)占用的区域。读取自 UiCanvasInformation.interactableArea。它覆盖的范围由各个浏览器决定——请在你目标的平台上进行验证。
'none'
整个屏幕,且 0,0 位于其左上角。
在桌面端,设备内边距为零,因此 'device' 会将 UI 放置在整个屏幕上——效果与……相同 'none'。该区域会在每个 tick 重新读取,因此当内边距变化时 UI 会随之调整,例如屏幕旋转或系统栏出现与隐藏时。
📔 不要将 UI 锚定到左上角。 那个角落以及整个左边缘,都是 explorer 绘制小地图、聊天,以及在移动端绘制虚拟摇杆的地方。放在那里的场景 UI 虽然会渲染出来,但客户端自己的 UI 会画在它上面,所以它会被遮住,而且按钮通常无法点击。请将你的 UI 锚定到右侧或居中,或者使用 screenInset: 'interactable' ,这样 explorer 就会把它放到避开自身 HUD 的位置。参见 切勿将你的 UI 锚定到左上角.
📔 'interactable' 在桌面端并非无操作。 与设备内边距不同,可交互区域在 不会 桌面客户端中为零:它会为自己的 UI 预留大约屏幕左侧 25% 的区域,因此 screenInset: 'interactable' 会把你的 UI 放到剩余的 75% 区域。这正是该选项的目的,但这也意味着它同样会改变你的桌面布局——请用 isMobile() 如果你只想在手机上启用它。
客户端支持: 'interactable' 需要一个会报告该区域的 explorer。它在桌面端受支持,而在移动端则从客户端版本 1.12.1 起开始支持——在较旧的移动客户端上,该值会被报告为零,UI 则会回退为覆盖整个屏幕。该 1.12.1 版本还统一了 'device' Android 和 iOS 之间的区域,因此对于依赖任一内边距的布局,都应将它视为下限。 'none' 在各处的表现都相同。
📔 注:不要把你的 UI 包在 ScreenInsetArea 或 InteractableArea 组件中,同时还保留对应的 screenInset 渲染器上的值——否则内边距会被应用两次,把 UI 向内推入双倍的边距。要么依赖 screenInset,要么将其设为 'none' 并自行放置这个包装器。
真实设备上的三个区域
下面的截图是在同一台手机上的同一场景,仅通过更改 screenInset 值后分别渲染三次。洋红色矩形是 UI 本身;轮廓线是 explorer 报告的区域——青色是完整画布,琥珀色是 screenInsetArea,绿色是 interactableArea.

screenInset: 'none' ——UI 覆盖整个画布。它的角落位于刘海下方并被客户端自身的控件挡在后面。
screenInset: 'device' (默认)——UI 会被收进设备安全区域,避开刘海和圆角。它仍然会与客户端的控件重叠,因为这些控件不属于该区域。
screenInset: 'interactable' ——UI 被放置在客户端为场景 UI 指定的矩形内。在移动客户端 1.12.1,此处捕获的画面中,这不包括左侧列(个人资料、聊天、摇杆);右下角的操作按钮则是按设计绘制在该区域之上的。每个渲染器都会遵循自己的 screenInset,因此主 UI 以及通过 addUiRenderer() 添加的任何 UI 可以同时使用不同的区域。与虚拟尺寸不同,这个值不会在渲染器之间共享。
多个 UI 模块
如果你的场景包含多个各自定义了 UI 的系统或模块,你可以使用 ReactEcsRenderer.addUiRenderer()进行渲染。这在处理包含多个 UI 组件的复杂场景时尤其有用,或者在为 智能物品定义 UI 时也很有用,因为它应当能够独立于场景其余代码中的内容而使用。
该 ReactEcsRenderer.addUiRenderer() 函数要求你提供一个实体作为 UI 的所有者。这个实体可以是任何实体,甚至可以是仅为充当 UI 所有者而创建的虚拟实体。
这段代码片段可以独立于场景中的任何其他 UI 代码而存在。场景的其余部分可能包含一个 ReactEcsRenderer.setUiRenderer(),也可能完全没有,而 UI 仍然会被渲染。
一个 addUiRenderer() 调用也可以包含虚拟宽度和高度,就像 setUiRenderer()。不过,如果场景中有一个 setUiRenderer() 调用也定义了虚拟宽度和高度,那么 addUiRenderer() 调用中的虚拟宽度和高度将被忽略。
虚拟尺寸是一个整个场景范围内的单一值,其解析方式如下: setUiRenderer() 上的值优先;否则,最先的 addUiRenderer() 提供该值的调用获胜;如果没有任何渲染器提供该值,则 平台默认值 生效。仅携带 screenInset 的选项不算作已提供尺寸。
该 screenInset 则相反——它是按渲染器分别设置的,因此每个 UI 模块都可以位于不同的屏幕区域:
可以使用以下方式移除该 UI: ReactEcsRenderer.removeUiRenderer(dummyEntity) ,另外如果拥有 UI 的实体被销毁,UI 也会被移除。如果 ReactEcsRenderer.addUiRenderer() 再次针对同一实体调用,但使用了不同的 UiRenderer,之前的那个会被清理,新的是替换它,并保持其在堆叠顺序中的位置。再次调用它也是更改已渲染 UI 模块选项的方式,例如它的 zIndex.
UI 模块之间的堆叠顺序
默认情况下,UI 模块按它们首次出现的顺序堆叠,后出现的在前。首次出现在同一帧中的模块——通常是场景第一帧之前注册的所有内容——会与主 UI 一起绘制,其中 setUiRenderer() 位于最底层,其后按照添加顺序绘制其他模块。要显式控制这一点,请在渲染器选项中传入一个 zIndex :具有更高 zIndex 的模块会渲染在较低值模块前面,而不管它们出现的顺序如何。保留为 0 的模块会保持默认顺序。该选项在 setUiRenderer()上也是同样生效的,因此主 UI 也可以放到其他模块前面。
这与……的设置不同 zIndex 中的 UiEntity,它只会对……排序 同一 UI 模块内的兄弟元素。渲染器选项是按整个模块彼此排序,而每个模块内部的元素则保留各自的 zIndex.
共享一条 setUiRenderer 语句
而不是调用 ReactEcsRenderer.addUiRenderer() 对于每个 UI 模块,你可以调用 ReactEcsRenderer.setUiRenderer() 一次,传入一个 UI 模块数组,它们可以位于不同的文件中。
下面是一个更完整的示例:
ui.tsx 文件:
index.ts 文件:
最后更新于