build-ui
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBuilding UI with React-ECS
使用 React-ECS 构建 UI
Decentraland SDK7 uses a React-like JSX system for 2D UI overlays.
Decentraland SDK7 使用类 React 的 JSX 系统来构建 2D UI 叠加层。
When to Use Which UI Approach
何时使用哪种 UI 方案
| Need | Approach | Component |
|---|---|---|
| Screen-space HUD, menus, buttons | React-ECS (this skill) | |
| 3D text floating in the world | TextShape + Billboard | See advanced-rendering skill |
| Open a web page | | See scene-runtime skill |
| Clickable objects in 3D space | Pointer events | See add-interactivity skill |
Use React-ECS for any 2D overlay: scoreboards, health bars, dialogs, inventories, settings menus. Use TextShape for labels above NPCs or objects in the 3D world.
| 需求 | 方案 | 组件 |
|---|---|---|
| 屏幕空间 HUD、菜单、按钮 | React-ECS(此 skill) | |
| 悬浮在 3D 世界中的 3D 文本 | TextShape + Billboard | 参见 advanced-rendering skill |
| 打开网页 | | 参见 scene-runtime skill |
| 3D 空间中的可点击对象 | 指针事件 | 参见 add-interactivity skill |
对于任何 2D 叠加层,都应使用 React-ECS,例如记分板、生命值条、对话框、背包和设置菜单。对于 NPC 或对象上方位于 3D 世界中的标签,则应使用 TextShape。
Setup
设置
Create with your UI component and call from . Call from in . The SDK template already includes the required JSX settings in tsconfig.json — do NOT modify it.
src/ui.tsxReactEcsRenderer.setUiRenderer(MyUI, { virtualWidth: 1920, virtualHeight: 1080 })setupUi()setupUi()main()src/index.ts在 中创建 UI 组件,并在 中调用 。再从 中的 调用 。SDK 模板已经在 tsconfig.json 中包含所需的 JSX 设置——请勿修改它。
src/ui.tsxsetupUi()ReactEcsRenderer.setUiRenderer(MyUI, { virtualWidth: 1920, virtualHeight: 1080 })src/index.tsmain()setupUi()DEFAULT RULE: Always Set Virtual Screen Size to 1920x1080
默认规则:始终将虚拟屏幕尺寸设为 1920x1080
Whenever you generate UI code, you MUST pass to and by default — without waiting for the user to ask. Only deviate if the user explicitly requests a different reference resolution.
{ virtualWidth: 1920, virtualHeight: 1080 }setUiRendereraddUiRendererWhy: Without a virtual size, UI is laid out in raw screen pixels and renders inconsistently across different resolutions and aspect ratios — fonts, spacing, and absolute-positioned elements drift between displays. Setting a virtual screen size makes the engine scale the UI proportionally to a fixed reference frame, so layouts look the same on every screen. 1920x1080 is the safe default — it matches the most common displays and the assumption made by most community examples.
The options argument is optional at the API level — is valid, and several engine test scenes omit it. Passing it is still the default rule here; only omit it if the user explicitly wants raw-pixel layout.
setUiRenderer(ui)API (verified against 7.22.5, file ):
@dcl/react-ecsdist/system.d.tsts
type UiRendererOptions = { virtualWidth: number; virtualHeight: number }
setUiRenderer(ui: UiComponent, options?: UiRendererOptions): void
addUiRenderer(entity: Entity, ui: UiComponent, options?: UiRendererOptions): voidCanonical snippet (use this verbatim unless the user specifies otherwise):
tsx
import { ReactEcsRenderer } from '@dcl/sdk/react-ecs'
export function setupUi() {
ReactEcsRenderer.setUiRenderer(MyUI, { virtualWidth: 1920, virtualHeight: 1080 })
}**每当你生成 UI 代码时,都必须默认向 和 传入 ,无需等待用户提出要求。**只有当用户明确要求使用不同的参考分辨率时,才可以偏离此规则。
setUiRendereraddUiRenderer{ virtualWidth: 1920, virtualHeight: 1080 }原因:如果不设置虚拟尺寸,UI 会按原始屏幕像素进行布局,在不同分辨率和宽高比下呈现不一致——字体、间距和绝对定位元素会在不同显示器之间发生偏移。设置虚拟屏幕尺寸后,引擎会按固定参考画框对 UI 进行等比例缩放,使布局在所有屏幕上保持一致。1920x1080 是安全的默认值——它与最常见的显示器相匹配,也是大多数社区示例所采用的基准。
在 API 层面,options 参数是可选的—— 是有效用法,而且多个引擎测试场景都省略了该参数。但此处仍将其作为默认规则;只有当用户明确需要原始像素布局时,才可以省略。
setUiRenderer(ui)API(已对照 7.22.5 的 验证):
@dcl/react-ecsdist/system.d.tsts
type UiRendererOptions = { virtualWidth: number; virtualHeight: number }
setUiRenderer(ui: UiComponent, options?: UiRendererOptions): void
addUiRenderer(entity: Entity, ui: UiComponent, options?: UiRendererOptions): void规范代码片段(除非用户另有说明,否则请原样使用):
tsx
import { ReactEcsRenderer } from '@dcl/sdk/react-ecs'
export function setupUi() {
ReactEcsRenderer.setUiRenderer(MyUI, { virtualWidth: 1920, virtualHeight: 1080 })
}Core Components
核心组件
UiEntity — Container element. Key props: (width, height, positionType, position, flexDirection, justifyContent, alignItems, alignContent, alignSelf, padding, margin, display, overflow, flexWrap, flexGrow, , , , , , ), (color, texture, textureMode, textureSlices, uvs, avatarTexture), (value, fontSize, color, textAlign, font). Events: , , , .
uiTransformopacityzIndexborderWidthborderColorborderRadiuspointerFilteruiBackgrounduiTextonMouseDownonMouseUponMouseEnteronMouseLeave-
These four are the complete set of UI event handlers, and each is. There is no
() => void/onMouseDrag, and no arguments — no pointer coordinates, no event object — are passed to a handler. All four are hardcoded toonMouseMove; you cannot bind a UI element to right-click or a key. Drag interactions are still fully possible viaInputAction.IA_POINTER— see "Sliders" below.PrimaryPointerInfo.screenDelta -
(number 0–1): fades the element. Set on the root to fade the whole UI; cascades multiplicatively to children.
opacity -
(number, incl. negative): controls stacking order among sibling elements. Higher = on top. Does not cross parent boundaries.
zIndex -
/
borderWidth(borderColor) /Color4: also valid onborderRadius,Button,Inputvia theirDropdown.uiTransform -
/
widthaccept a number (px),height,'50%', or'400px'.'auto'/position/paddingvalues accept the same string forms;marginalso accepts a CSS shorthand string, e.g.margin.margin: '16px 0 8px 270px'
Label — Text display. Key props: , , , (e.g. ), (||), .
valuefontSizecolortextAlign'middle-center'font'sans-serif''serif''monospace'uiTransformButton — Clickable button. Key props: , (|), , , .
valuevariant'primary''secondary'fontSizeonMouseDownuiTransformInput — Text input field. Key props: , , , , , .
placeholderfontSizecoloronChangeonSubmituiTransformDropdown — Selection dropdown. Key props: (string[]), , , , , .
optionsselectedIndexonChangefontSizeuiTransformdisabledScreenInsetArea — Wrapper that keeps children inside the device's hardware-reserved margins (notch, status bar, home indicator, rounded corners). On mobile, it positions itself absolutely using the insets the device reports. On desktop the insets are , so it's a no-op — safe to leave in cross-platform UI. It owns its own and ; any values you pass for those in are ignored. All other props (, , , …) and components (, , …) work as usual. Wrap any mobile-sensitive HUD in it; a child sized fills the safe area exactly. Distinct from the Decentraland system HUD reserved zones (joystick, chat, profile, interaction button) — those still need to be avoided manually; use both together. UI designed for desktop typically needs sizes scaled ~3× for mobile readability.
(0,0,0,0)positionTypepositionuiTransformuiTransformpaddingflexDirectionalignItemsuiBackgroundonMouseDownwidth: '100%', height: '100%'InteractableArea — Wrapper that keeps children inside the renderer-reported interactable area — the part of the screen NOT covered by the client's own UI (minimap, chat window, platform overlays). Reads and constrains children via absolute positioning; on the Unity desktop client the left ~25% of the screen is reserved, so children fill the remaining ~75%. Like , it owns / (values you pass are ignored) and falls back to zero insets (no-op) when unavailable. Import from ; usage . Distinct from (which avoids device hardware margins, not client UI). See → InteractableArea.
UiCanvasInformation.interactableAreaScreenInsetAreapositionTypeposition@dcl/sdk/react-ecs<InteractableArea><MyHud /></InteractableArea>ScreenInsetArea{baseDir}/references/ui-components.mdUiEntity — 容器元素。关键属性:(width、height、positionType、position、flexDirection、justifyContent、alignItems、alignContent、alignSelf、padding、margin、display、overflow、flexWrap、flexGrow、、、borderWidth、borderColor、borderRadius、pointerFilter)、(color、texture、textureMode、textureSlices、uvs、avatarTexture)、(value、fontSize、color、textAlign、font)。事件:、、、。
uiTransformopacityzIndexuiBackgrounduiTextonMouseDownonMouseUponMouseEnteronMouseLeave- 这四个属性构成完整的 UI 事件处理函数集合,且每个处理函数都是 。不存在
() => void/onMouseDrag,也不会向处理函数传递任何参数——没有指针坐标,也没有事件对象。这四个处理函数均固定使用onMouseMove;无法将 UI 元素绑定到右键或按键。不过,仍可使用InputAction.IA_POINTER完整实现拖拽交互——参见下方“滑块”。PrimaryPointerInfo.screenDelta - (数值 0–1):淡出元素。在根元素上设置可淡出整个 UI;透明度会乘算并传递给子元素。
opacity - (数值,可为负数):控制同级元素之间的堆叠顺序。数值越高,元素越靠上,但不会跨越父级边界。
zIndex - /
borderWidth(borderColor)/Color4:也可通过borderRadius、Button、Input的Dropdown设置。uiTransform - /
width接受数值(px)、height、'50%'或'400px'。'auto'/position/padding的值接受相同的字符串形式;margin还接受 CSS 简写字符串,例如margin。margin: '16px 0 8px 270px'
Label — 文本显示。关键属性:、、、(例如 )、(||)、。
valuefontSizecolortextAlign'middle-center'font'sans-serif''serif''monospace'uiTransformButton — 可点击按钮。关键属性:、(|)、、、。
valuevariant'primary''secondary'fontSizeonMouseDownuiTransformInput — 文本输入字段。关键属性:、、、、、。
placeholderfontSizecoloronChangeonSubmituiTransformDropdown — 选择下拉菜单。关键属性:(string[])、、、、、。
optionsselectedIndexonChangefontSizeuiTransformdisabledScreenInsetArea — 包装器,用于将子元素保持在设备硬件预留边距内(刘海、状态栏、Home 指示条、圆角)。在移动端,它会使用设备报告的 insets 对自身进行绝对定位。在桌面端,insets 为 ,因此它是一个 no-op——可以安全地保留在跨平台 UI 中。它拥有自己的 和 ;传给这些 属性的值会被忽略。其他所有 属性(、、 等)和组件(、 等)均照常工作。请将所有对移动端敏感的 HUD 包裹在其中;一个设置为 的子元素会恰好填满安全区域。它不同于 Decentraland 系统 HUD 的保留区域(摇杆、聊天、资料、交互按钮)——这些区域仍需手动避开;应将两者结合使用。为桌面端设计的 UI 通常需要将尺寸放大约 3 倍,才能在移动端清晰显示。
(0,0,0,0)positionTypepositionuiTransformuiTransformpaddingflexDirectionalignItemsuiBackgroundonMouseDownwidth: '100%', height: '100%'InteractableArea — 包装器,用于将子元素保持在渲染器报告的可交互区域内,即未被客户端自身 UI 覆盖的屏幕部分(小地图、聊天窗口、平台叠加层)。它读取 ,并通过绝对定位限制子元素;在 Unity 桌面客户端中,屏幕左侧约 25% 会被保留,因此子元素填充其余约 75% 的区域。与 一样,它拥有自己的 /(传入的值会被忽略),并且在无法获取区域信息时回退为零 insets(no-op)。从 导入;用法为 。它与 不同,后者用于避开设备硬件边距,而不是客户端 UI。参见 中的 InteractableArea。
UiCanvasInformation.interactableAreaScreenInsetAreapositionTypeposition@dcl/sdk/react-ecs<InteractableArea><MyHud /></InteractableArea>ScreenInsetArea{baseDir}/references/ui-components.mdAdding Independent UI Renderers (addUiRenderer)
添加独立 UI 渲染器(addUiRenderer)
Use to render a UI module independently without replacing the main UI. Useful for smart items or modular scene components. Remove with . If the owner entity is destroyed, the UI is removed automatically.
ReactEcsRenderer.addUiRenderer(ownerEntity, MyWidget, { virtualWidth: 1920, virtualHeight: 1080 })ReactEcsRenderer.removeUiRenderer(owner)使用 可以独立渲染 UI 模块,而不替换主 UI。这对于智能物品或模块化场景组件非常有用。使用 移除它。如果 owner entity 被销毁,该 UI 也会自动移除。
ReactEcsRenderer.addUiRenderer(ownerEntity, MyWidget, { virtualWidth: 1920, virtualHeight: 1080 })ReactEcsRenderer.removeUiRenderer(owner)State Management
状态管理
Use module-level variables for UI state — React hooks (, , etc.) are NOT available. The UI renderer re-renders every frame, so state changes are reflected immediately. Export functions to update state from game logic.
useStateuseEffect使用模块级变量管理 UI 状态——React hooks(、 等)不可用。UI 渲染器会在每一帧重新渲染,因此状态变化会立即反映出来。导出函数,以便从游戏逻辑中更新状态。
useStateuseEffectCommon UI Patterns
常见 UI 模式
- Health bar — Nested UiEntity with width as percentage
- Image background — with
uiBackgroundandtexturetextureMode: 'stretch' - Screen dimensions — Read via
UiCanvasInformation.getOrNull(engine.RootEntity) - Nine-slice textures — with
textureMode: 'nine-slices'for scalable panelstextureSlices - Texture UVs / Sprite sheets — array (8 numbers) to select texture regions
uvs - Hover events — /
onMouseEnteron UiEntityonMouseLeave - Flex wrap — for grid layouts
flexWrap: 'wrap' - Scrollable containers — on a fixed-size parent to scroll through overflowing content (drag or mouse wheel). Use
overflow: 'scroll'to clip overflow without scrolling. Useoverflow: 'hidden'on scrollable entities to fill remaining spaceflexGrow: 1 - Texture tint — set alongside
colorintextureto tint the image (works withuiBackgroundandstretch)nine-slices - Multiple stacked layers — the renderer function may return an array of elements, e.g. ; later items in the array render on top of earlier ones
setUiRenderer(() => [PanelA(), PanelB()]) - Opacity / z-index — and
opacityonzIndex(see Core Components); rootuiTransformfades the whole HUDopacity
- 生命值条 — 嵌套 ,内层宽度使用百分比
UiEntity - 图片背景 — 在 中设置
uiBackground和texturetextureMode: 'stretch' - 屏幕尺寸 — 通过 读取
UiCanvasInformation.getOrNull(engine.RootEntity) - 九宫格纹理 — 使用 和
textureMode: 'nine-slices'创建可缩放面板textureSlices - 纹理 UV/Sprite sheet — 使用包含 8 个数值的 数组选择纹理区域
uvs - 悬停事件 — 在 UiEntity 上使用 /
onMouseEnteronMouseLeave - Flex 换行 — 使用 实现网格布局
flexWrap: 'wrap' - 可滚动容器 — 在固定尺寸的父元素上设置 来滚动溢出内容(拖拽或鼠标滚轮)。使用
overflow: 'scroll'仅裁剪溢出内容。在可滚动元素上使用overflow: 'hidden',使其填满剩余空间flexGrow: 1 - 纹理着色 — 在 中设置
uiBackground的同时设置texture(适用于color和stretch)nine-slices - 多个堆叠图层 — 渲染器函数可以返回元素数组,例如 ;数组中较后的元素会渲染在较前元素上方
setUiRenderer(() => [PanelA(), PanelB()]) - 透明度/z-index — 在 上设置
uiTransform和opacity(参见“核心组件”);根元素上的zIndex会淡出整个 HUDopacity
Gotchas (verified against engine test scenes)
常见问题(已对照引擎测试场景验证)
- and
Inputare uncontrolled.Dropdown/onChangefire with the current value, but the field does not read back from theonSubmit/valueprop you pass each frame the way React does. To programmatically clear anselectedIndex, briefly setInputto a non-empty sentinel (e.g.value) for one frame, then back to' '. Do not expect setting''to force the displayed text every frame.value - is per-sibling-group. It orders siblings within the same parent; it does not lift an element above elements in a different branch of the tree. Use array-return ordering or tree structure for cross-branch stacking.
zIndex - multiplies down the tree. A child at
opacityinside a root atopacity: 0.8renders at 0.4 effective. Don't stack opacities unintentionally.opacity: 0.5 - deforms non-uniform art; use
textureMode: 'stretch'(with'nine-slices') for panels/buttons that must scale without distorting borders, andtextureSlicesto draw the texture at native size centered in the element.'center' - Texture paths are relative to the scene root (e.g.
src), not to'images/panel.png'.src/ - No pointer coordinates in UI handlers. /
onMouseDown/onMouseUp/onMouseEnterareonMouseLeave— the reconciler discards the() => voidbefore calling your callback, so "where on this element did they click" is unavailable. Track movement instead of position:PBPointerEventsResultreports per-frame mouse travel and drives drag interactions fine. SeePrimaryPointerInfo.screenDelta.{baseDir}/references/ui-sliders.md - UI elements with a handler become pointer-blocking. Adding makes the element block clicks to the 3D world behind it; elements without one let clicks through. Override either way with
onMouseDown(defaultuiTransform.pointerFilter: 'block' | 'none').'none'
- 和
Input是非受控组件。Dropdown/onChange会携带当前值触发,但字段不会像 React 那样每帧读取你传入的onSubmit/value属性。要以编程方式清空selectedIndex,先将Input短暂设置为一个非空哨兵值(例如value)保持一帧,然后再设回' '。不要期望设置''就能在每帧强制更新显示文本。value - **仅在同一父级下生效。**它用于排列同一父元素中的兄弟节点,无法将某个元素提升到树中其他分支之上。对于跨分支堆叠,请使用数组返回顺序或调整树结构。
zIndex - **会沿树结构乘算。**根元素的
opacity包含一个opacity: 0.5的子元素时,子元素的最终有效透明度为 0.4。不要无意中叠加透明度。opacity: 0.8 - **会使非统一缩放的内容变形;**对于缩放时必须保持边框不失真的面板和按钮,应使用
textureMode: 'stretch'(配合'nine-slices');使用textureSlices可按原始尺寸居中绘制纹理。'center' - 纹理 路径相对于场景根目录(例如
src),而不是'images/panel.png'。src/ - UI 处理函数中没有指针坐标。/
onMouseDown/onMouseUp/onMouseEnter都是onMouseLeave——reconciler 会在调用回调前丢弃() => void,因此无法获知“用户点击了元素中的哪个位置”。应跟踪移动量而非位置:PBPointerEventsResult会报告每帧鼠标移动量,足以驱动拖拽交互。参见PrimaryPointerInfo.screenDelta。{baseDir}/references/ui-sliders.md - **带有处理函数的 UI 元素会阻止指针事件。**添加 后,该元素会阻止点击穿透到身后的 3D 世界;没有处理函数的元素则允许点击穿透。可使用
onMouseDown覆盖这一行为(默认值为uiTransform.pointerFilter: 'block' | 'none')。'none'
Common Widgets — Build From Scratch
常见 Widget——从头构建
Build every widget from React-ECS primitives (, , ). There is no pre-built widget library to install.
UiEntityLabelButton- Prompt / dialog / confirmation? → full-screen overlay + centered panel + s. See the Modal Dialog pattern in
Button.references/ui-components.md - Health bar, progress bar, score? → nested with the inner one sized
UiEntity${pct}%``. See the Health Bar patterns inwidth:andreferences/ui-components.md; a score is areferences/ui-patterns.mdbound to a module-level variable.Label - Flash announcement (timed, centered)? → a centered gated on a module-level flag, cleared with
Label. See Timed Announcement intimers.setTimeout.references/ui-patterns.md - Slider / drag handle / scrub bar? → drag sliders work. UI handlers get no pointer coordinates, so instead: on the track starts a drag, and a system accumulates
onMouseDown(divided by the UI scale factor) into the value. A full-screenPrimaryPointerInfo.screenDelta.xoverlay rendered only while dragging catches the release. Verified in-world on both the Unity and Bevy explorers. Desktop only —pointerFilter: 'block'is always 0 on mobile, so pair the track withscreenDelta/-stepper+s. Full implementation inButton.{baseDir}/references/ui-sliders.md - Custom panel, inventory, complex layout? → React-ECS directly (see ).
references/ui-patterns.md
每个 widget 都应使用 React-ECS 原语(、、)构建。没有可安装的预构建 widget 库。
UiEntityLabelButton- 提示/对话框/确认框? → 全屏叠加层 + 居中面板 + 。参见
Button中的 模态对话框模式。references/ui-components.md - 生命值条、进度条、分数? → 嵌套 ,内层元素设置为
UiEntity${pct}%``。参见width:和references/ui-components.md中的 生命值条模式;分数则使用绑定到模块级变量的references/ui-patterns.md。Label - 定时居中的闪烁公告? → 使用模块级标志控制居中的 ,并通过
Label清除。参见timers.setTimeout中的 定时公告。references/ui-patterns.md - 滑块/拖拽手柄/拖动进度条? → **拖拽滑块可用。**UI 处理函数不会获得指针坐标,因此应在轨道上使用 开始拖拽,再由一个 system 将
onMouseDown累加到数值中(除以 UI 缩放系数)。仅在拖拽期间渲染的、覆盖全屏且设置了PrimaryPointerInfo.screenDelta.x的叠加层可捕获释放事件。已在 Unity 和 Bevy explorer 的场景中完成实机验证。仅支持桌面端——移动端上的pointerFilter: 'block'始终为 0,因此应将轨道与screenDelta/-步进+配合使用。完整实现参见Button。{baseDir}/references/ui-sliders.md - 自定义面板、背包、复杂布局? → 直接使用 React-ECS(参见 )。
references/ui-patterns.md
Troubleshooting
故障排除
Work through the wiring causes in this table in order before speculating about layout-level causes (sizing, , off-screen positioning, color-on-color) — wiring problems are the cause by a wide margin.
display: 'none'| Problem | Cause | Solution |
|---|---|---|
| UI not rendering / invisible / nothing on screen (most common) | | Add the |
UI not rendering even though | | Add |
| UI blank on first frames, sometimes appears later | Root component returns | Render a placeholder or hidden root instead of returning |
| Multiple UIs fighting / UI missing | More than one | Only call |
| Absolute-positioned children laid out unexpectedly | Root | Add |
| UI elements overlapping | Missing | Set |
| Button clicks not registering | Missing | Add |
| JSX errors at compile time | File extension is | Rename the file to |
| Text not visible | Text color matches background | Set contrasting |
在推测布局层面的原因(尺寸、、屏幕外定位、颜色相同)之前,请先按顺序检查下表中的连接问题。连接问题出现的概率远高于布局问题。
display: 'none'| 问题 | 原因 | 解决方案 |
|---|---|---|
| UI 未渲染/不可见/屏幕上没有任何内容(最常见) | 未从 | 在 |
即使调用了 | | 添加 |
| 前几帧为空白,有时稍后才出现 | 根组件首次渲染时返回 | 渲染占位内容或隐藏的根元素,而不是返回 |
| 多个 UI 相互争用/UI 缺失 | 调用了多次 | 仅调用一次 |
| 绝对定位的子元素布局异常 | 根 | 为根元素添加 |
| UI 元素重叠 | 缺少 | 在父容器上设置 |
| 按钮点击没有响应 | 缺少 | 在 Button 或 UiEntity 上添加 |
| 编译时出现 JSX 错误 | 文件扩展名是 | 将文件重命名为 |
| 文本不可见 | 文本颜色与背景颜色相同 | 在 Label 或 |
Convention: root <UiEntity>
must set width: '100%', height: '100%'
<UiEntity>width: '100%', height: '100%'约定:根 <UiEntity>
必须设置 width: '100%', height: '100%'
<UiEntity>width: '100%', height: '100%'Set on the root returned to / whenever the UI uses absolute positioning. Do this by default.
uiTransform={{ width: '100%', height: '100%' }}<UiEntity>setUiRendereraddUiRendererNote: this is required specifically so absolute-positioned children get a full-screen positioning context. Some engine test scenes that lay everything out with flow/ (no absolute children) use a smaller root (e.g. or ) and render fine — but a full-canvas root is the safe default and never hurts.
margin90%50%Rationale (empirically verified — tested in-engine June 2026):
- Without a full-canvas root, absolute-positioned children using may fail to render entirely. In testing, a root with no explicit
position: { top, right }/widthcaused aheightpositioned child to disappear while atop-rightchild rendered correctly. Addingbottom-leftto the root fixed the issue.width: '100%', height: '100%' - A full-canvas root gives absolute-positioned children (with
positionType: 'absolute') a known, full-screen positioning context. This matches the implicit assumption most HUD code makes.position: { top, left, ... } - It avoids edge-case layout surprises with Yoga's default sizing for unspecified /
width.height
只要 UI 使用了绝对定位,就必须在传给 / 的根 上设置 。默认应始终这样做。
setUiRendereraddUiRenderer<UiEntity>uiTransform={{ width: '100%', height: '100%' }}注意:这是专门为了确保绝对定位的子元素获得全屏定位上下文而要求的。部分引擎测试场景使用流式布局/ 布局(没有绝对定位的子元素),其根元素尺寸较小(例如 或 ),也能正常渲染;但全画布根元素是安全的默认方案,不会带来任何负面影响。
margin90%50%理由(已通过实测验证——于 2026 年 6 月在引擎中测试):
- 没有全画布根元素时,使用 的绝对定位子元素可能完全无法渲染。测试中,未显式设置
position: { top, right }/width的根元素导致右上角定位的子元素消失,而左下角定位的子元素仍能正常渲染。添加height后问题得到解决。width: '100%', height: '100%' - 全画布根元素可为绝对定位的子元素(配合
positionType: 'absolute')提供明确的全屏定位上下文。这符合大多数 HUD 代码的隐含假设。position: { top, left, ... } - 它可以避免 Yoga 对未指定 /
width的默认尺寸计算所带来的边缘布局问题。height
Example scenes
示例场景
Engine-team test scenes exercised against the real renderer (ground truth for the APIs above):
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,6-ui-zindex-and-opacity — (incl. negative) and
zIndexonopacity, including root-level opacity cascade; buttons cycle values.uiTransform - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/70,-9-sdk7-ui-backgrounds — every texture mode (
uiBackground,stretch,nine-slices), color tinting over textures,center, andavatarTexture.textureSlices - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/80,-3-ui — /
Label/Input/Dropdownend to end,ButtononuiText,UiEntityCSS-shorthand strings,marginsizing,'auto'.UiCanvasInformation - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/81,-3-ui-2 — array-return of stacked panels, toggling, border props (
disabled/borderWidth/borderColor) on Input/Dropdown/Button, uncontrolled-input clear trick, texturedborderRadius(nine-slices) vs. clickableButton.UiEntity - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/76,-10-UiCanvasInformation — reading each frame into a module variable to size UI responsively.
UiCanvasInformation - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/8,7-portable-experience-hide-ui — hiding a portable experience's UI via in
featureToggles.portableExperiences: "hideUi"(scene-config, not React-ECS).scene.json
For full code examples and implementation patterns, see . For component prop details, see . For sliders and the limits of UI pointer input, see .
{baseDir}/references/ui-patterns.md{baseDir}/references/ui-components.md{baseDir}/references/ui-sliders.md引擎团队的测试场景已针对真实渲染器进行验证,是上述 API 的可靠依据:
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0,6-ui-zindex-and-opacity — 上的
uiTransform(包括负数)和zIndex,以及根元素透明度级联;按钮用于循环切换数值。opacity - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/70,-9-sdk7-ui-backgrounds — 所有 纹理模式(
uiBackground、stretch、nine-slices)、纹理颜色着色、center和avatarTexture。textureSlices - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/80,-3-ui — 完整演示 /
Label/Input/Dropdown、UiEntity 上的Button、uiTextCSS 简写字符串、margin尺寸和'auto'。UiCanvasInformation - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/81,-3-ui-2 — 堆叠面板的数组返回形式、切换、Input/Dropdown/Button 上的边框属性(
disabled/borderWidth/borderColor)、非受控输入清空技巧,以及带纹理的 Button(nine-slices)与可点击 UiEntity 的对比。borderRadius - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/76,-10-UiCanvasInformation — 每帧将 读取到模块级变量,以实现响应式 UI 尺寸。
UiCanvasInformation - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/8,7-portable-experience-hide-ui — 通过 隐藏便携体验的 UI(属于 scene-config,而非 React-ECS)。
featureToggles.portableExperiences: "hideUi"
有关完整代码示例和实现模式,请参见 。有关组件属性详情,请参见 。有关滑块和 UI 指针输入的限制,请参见 。
{baseDir}/references/ui-patterns.md{baseDir}/references/ui-components.md{baseDir}/references/ui-sliders.mdCross-references
交叉引用
- Platform detection: Use /
getPlatform()fromisMobile()to branch UI for mobile vs. desktop. See the advanced-input skill.@dcl/sdk/platform - Mobile UI limitations: is unsupported on mobile. Design for touch (larger tap targets, no hover states). See the mobile considerations in the advanced-input skill.
borderRadius
- 平台检测:使用 中的
@dcl/sdk/platform/getPlatform(),根据移动端或桌面端区分 UI。参见 advanced-input skill。isMobile() - 移动端 UI 限制:移动端不支持 。应面向触控进行设计(使用更大的点击目标,不提供悬停状态)。参见 advanced-input skill 中的移动端注意事项。
borderRadius