> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/creator/content-creator-zh/chang-jing-sdk7/2d-ui/onscreen-ui.md).

# 屏幕 UI

了解如何为场景中的玩家创建 UI。例如，这可用于显示与游戏相关的信息。

你可以为你的场景构建一个 UI，将其显示在屏幕固定的 2D 空间中，而不是显示在 3D 世界空间中。

只有当玩家站在场景的 LAND 地块内时，UI 元素才可见，因为相邻场景可能有它们自己的 UI 要显示。UI 的某些部分也可以在世界空间中发生特定事件时被触发打开，例如玩家点击了某个特定位置。

通过定义嵌套的 `UiEntity` 对象结构来构建 UI，使用 JSX。用于 UI 的语法与 [React](https://reactjs.org/) （一个非常流行、基于 JavaScript 的 Web UI 构建库）非常相似。

{% hint style="warning" %}
**📔 注意**：你只能在具有 `.tsx` 扩展名的文件中定义 UI 语法。 `.tsx` 文件支持 `.ts` 文件支持的全部内容，外加 UI 语法。我们建议创建一个 `ui.tsx` 文件，并在那里定义你的 UI。记得从 `index.ts` 中调用你的 UI 渲染方法，并使用 `ReactEcsRenderer.setUiRenderer(yourUiMethodName)`，请参见下面的示例。
{% endhint %}

一个带有静态元素的简单 UI 看起来很像 HTML，但当你加入会响应状态变化的动态元素时，你就可以做出强大得多的效果。

默认的 Decentraland 浏览器 UI 包括聊天小组件、地图和其他元素。这些 UI 元素始终显示在最上层，位于任何场景专属 UI 之上。因此，如果你的场景中有占据相同屏幕空间的 UI 元素，它们会被遮住。

参见 [UX 指南](/creator/content-creator-zh/chang-jing-sdk7/ti-yan-she-ji/ux-ui-guide.md) ，了解关于如何设计 UI 外观和体验的提示。

{% hint style="info" %}
**📱 面向移动端的设计**：设备会为刘海、状态栏、主屏幕指示条以及圆角预留屏幕空间，而且客户端会在画布的一部分上绘制自己的控制项。你不需要自己测量——请使用 [`screenInset`](#screen-inset-area) 下方选取 UI 所在的区域。它默认使用设备安全区域，因此无需你额外处理硬件边距。在发布前，请查看 [移动端安全区域](/creator/content-creator-zh/mian-xiang-yi-dong-duan-gou-jian/kai-fa/safe-area.md) 和 [移动端 UI 最佳实践](/creator/content-creator-zh/mian-xiang-yi-dong-duan-gou-jian/kai-fa/ui-best-practices.md).
{% endhint %}

当玩家点击屏幕右下角的 *关闭 UI* 按钮时，所有 UI 元素都会被隐藏。

## 渲染 UI

要在你的场景中显示 UI，请使用 `ReactEcsRenderer.setUiRenderer()` 函数，并传入一个有效的实体结构，该结构定义在 `.tsx` 文件。

每个实体都被定义为一个类似 HTML 的节点，并为其各个组件提供属性。

***ui.tsx 文件：***

```ts
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 文件：***

```ts
import { ReactEcsRenderer } from '@dcl/sdk/react-ecs'
import { uiMenu } from './ui'

export function main() {
    ReactEcsRenderer.setUiRenderer(uiMenu, { virtualWidth: 1920, virtualHeight: 1080 })
}
```

你也可以在同一条命令中定义一个实体结构并将其渲染出来，使用 `.tsx` 文件。

***ui.tsx 文件：***

```tsx
import ReactEcs, { ReactEcsRenderer, UiEntity } from '@dcl/sdk/react-ecs'
import { Color4 } from '@dcl/sdk/math'

export function setupUI() {
  ReactEcsRenderer.setUiRenderer(() => (
    <UiEntity
      uiTransform={{
        width: 700,
        height: 400,
        margin: { top: '35px', left: '500px' },
      }}
      uiBackground={{ color: Color4.Red() }}
    />
  ), { virtualWidth: 1920, virtualHeight: 1080 })
}
```

***index.ts 文件：***

```ts
import { setupUI } from './ui'

export function main() {
    setupUI()
}
```

{% hint style="warning" %}
**📔 注意**：你的所有 UI 元素都需要嵌套在同一个结构中，并且在结构根部只有一个单一父级。你只能在场景中调用 `ReactEcsRenderer.setUiRenderer()` 一次。
{% endhint %}

## UI 实体

UI 中的每个元素都必须定义为一个单独的 `UiEntity`，无论它是图片、文本、背景、不可见的对齐框等。就像在场景的 3D 空间中一样，每个 `UiEntity` 都有自己的组件来赋予它位置、颜色等属性。

类似 React 的语法让你可以将每个组件指定为 `UiEntity`中的一个属性，这让代码更短也更易读。

在 `UiEntity` 中使用的组件与普通实体所使用的组件不同。你不能把 UI 组件应用到普通实体上，也不能把普通组件应用到 UI 实体上。

以下组件可在 `UiEntity`:

* `uiTransform`
* `uiBackground`
* `uiText`
* `onMouseDown`
* `onMouseUp`
* `onMouseEnter`
* `onMouseLeave`
* [`uiInputBinding`](/creator/content-creator-zh/chang-jing-sdk7/2d-ui/ui_input_binding.md) 中使用

—— 按住元素时持续输入动作，用于构建自定义控制

{% hint style="danger" %}
**警告：** 让全屏布局包装器不要带指针处理器。给一个 `onMouse` 处理器，或者 `pointerFilter: 'block'`，添加到一个 `UiEntity` 尺寸的 `100%` 由 `100%` 元素上，会让它捕获屏幕上的每一次点击，导致你其他所有 UI 和整个 3D 世界都无法点击。请把处理器放到真正需要它们的面板或按钮上。参见 [指针阻挡](/creator/content-creator-zh/chang-jing-sdk7/2d-ui/ui_button_events.md#pointer-blocking).
{% endhint %}

***ui.tsx 文件：***

```tsx
import ReactEcs, { ReactEcsRenderer, UiEntity } from '@dcl/sdk/react-ecs'
import { Color4 } from '@dcl/sdk/math'

export const uiMenu = () => (
  // 父实体
  <UiEntity
    uiTransform={{
      width: 200,
      height: 200,
      margin: { top: '250px', left: '500px' },
    }}
    uiBackground={{ color: Color4.Blue() }}
  >
    {/* 自闭合子实体 */}
    <UiEntity
      uiTransform={{
        width: 400,
        height: 400,
        margin: { top: '35px', left: '500px' },
      }}
      uiText={{ value: `Hello world!`, fontSize: 40 }}
    />
    {/* 父实体的结束语句 */}
  </UiEntity>
)
```

***index.ts 文件：***

```ts
import { ReactEcsRenderer } from '@dcl/sdk/react-ecs'
import { uiMenu } from './ui'

export function main() {
    ReactEcsRenderer.setUiRenderer(uiMenu, { virtualWidth: 1920, virtualHeight: 1080 })
}
```

一个 UI 模块的定义只能有一个顶级父实体。你可以定义任意多个其他实体，但它们都必须包含在顶部只有一个父级的结构中。

## 屏幕虚拟缩放

为 UI 设置一个虚拟宽高。这样可以确保无论实际像素尺寸是多少，你的 UI 在不同屏幕尺寸上看起来都一样。

```ts
export function setupUi() {
    ReactEcsRenderer.setUiRenderer(uiComponent, { virtualWidth: 1920, virtualHeight: 1080 })
}
```

如果你将虚拟宽度设为 1920、虚拟高度设为 1080，UI 将会被缩放以适配屏幕尺寸。如果屏幕是 1920x1080，UI 会以与虚拟尺寸相同的大小显示。如果屏幕更大或更小，任何像素值都会按比例缩放以适配虚拟尺寸。例如，如果屏幕是 3840x2160，那么一个宽度定义为 100 像素的项目会显示为 200 个实际像素。

用于与像素值相乘的 UI 缩放因子的实际计算公式是 [`Math.min(realWidth / virtualWidth, realHeight / virtualHeight)`](https://github.com/decentraland/js-sdk-toolchain/blob/main/packages/%40dcl/react-ecs/src/system.ts).

### 默认虚拟尺寸

虚拟尺寸是可选的，但它的两个维度必须成对出现：要么同时传入，要么都不传。如果你只传其中一个，平台会应用默认值：

| 平台       | 默认虚拟尺寸      |
| -------- | ----------- |
| 移动端      | `1600x720`  |
| 桌面端和 Web | `1920x1080` |

这意味着，即使你完全不传任何选项，UI 中的像素值也始终会相对于一个参考分辨率进行缩放。即使场景只使用 `addUiRenderer()` 并且从不调用 `setUiRenderer()`.

有三种特殊情况：

* **取消缩放**：传入无效尺寸——任何 `0` 或更小的值——即可完全禁用虚拟屏幕。此时像素值将被当作原始画布像素使用，不进行任何缩放。这是关闭虚拟屏幕的文档化方式，因此不会输出日志。

  ```ts
  ReactEcsRenderer.setUiRenderer(uiComponent, { virtualWidth: 0, virtualHeight: 0 })
  ```
* **不完整尺寸**：只给出两个维度中的一个也是无效的，因此它会像上面的情况一样禁用虚拟屏幕——平台默认会 **不是** 介入。与上面的取消缩放不同，这种情况会被报告：只给一半尺寸是个错误，而不是有意为之，所以 SDK 会记录 `不完整的虚拟屏幕尺寸（…）：两个维度都是必需的，因此虚拟屏幕已被禁用，并且没有应用 UI 缩放。` 每种尺寸只记录一次。请同时传入两个维度，或者都不传。

  ```ts
  // 不要这样做——完全不会应用缩放
  ReactEcsRenderer.setUiRenderer(uiComponent, { virtualWidth: 1920 })
  ```

  在主渲染器上尤其值得再次检查，因为那里的不完整尺寸也是一个全局场景开关：一个 `setUiRenderer()` 调用中提到 *任一* 维度都会优先于任何 `addUiRenderer()`，所以上面的示例会禁用整个场景的虚拟屏幕，并丢弃传给额外渲染器的一个有效尺寸。
* **移动端上的 16:9 尺寸**：手机屏幕比 16:9 宽得多，因此一个 16:9 的虚拟画布会给 UI 加上黑边。如果你传入一个 16:9 的尺寸（`1920x1080`, `1280x720`，……）并且场景运行在移动端，它会被覆盖为 `1600x720`，并且控制台会记录一次消息。移动端上的非 16:9 尺寸，以及桌面端或 Web 上的所有有效尺寸，都会按原样保留。

{% hint style="info" %}
**📔 注意**：该 `vw` 和 `vh` 单位独立于虚拟屏幕： `1vw` 是画布宽度的 1%，而 `1vh` 是画布高度的 1%，与 CSS 完全一致。它们也会忽略 [`screenInset`](#screen-inset-area)，因此在默认 `'device'` 中插入一个 `width: '100vw'` 比一个 `width: '100%'` 更宽——前者覆盖整个屏幕，后者填满内嵌区域。
{% endhint %}

{% hint style="info" %}
**📱 注意**：平台检测是异步解析的。在前几帧中，SDK 还不知道它是否运行在移动端，因此虚拟屏幕一开始会是 `1920x1080` ，并在检测完成后切换到 `1600x720` —— 你可能会在加载时短暂看到 UI 重新缩放，而 16:9 覆盖消息会在场景启动后一会儿出现在控制台中。如果你希望从第一帧开始就使用一个稳定的参考分辨率，请传入一个明确的非 16:9 虚拟尺寸。
{% endhint %}

{% hint style="warning" %}
**📔 对于在此 SDK 版本之前编写的场景，哪些地方发生了变化**

有三项变更会影响现有场景。它们都不是可选启用的，所以一个完全未修改的场景，在更新 SDK 版本后外观也会不同：

1. **现在默认会应用虚拟屏幕。** 过去，一个没有传入任何选项的场景会将像素值按原始画布像素进行布局。现在它会按 `1920x1080` (`1600x720` （在移动端）。要恢复之前的行为，请显式禁用虚拟屏幕，使用 `setUiRenderer(ui, { virtualWidth: 0, virtualHeight: 0 })`.
2. **`screenInset` 默认为 `'device'`.** 现在你的 UI 会放置在设备安全区域内，因此在移动端它会向内移动，而且根级别的 `100%` 不再是全屏——全屏背景、遮罩和覆盖层会停在刘海和主屏幕指示条之前。要恢复之前的行为，请传入 `setUiRenderer(ui, { screenInset: 'none' })`。参见 [屏幕内边距区域](#screen-inset-area).
3. **`devicePixelRatio` 不参与 UI 布局。** 在高密度（retina 和移动端）屏幕上，按像素大小设置的 UI 现在比以前大 2 到 3 倍。 **这项没有取消选项** —— 请重新检查任何手工调过的尺寸，并移除你的场景自己计算出的任何缩放因子。
   {% endhint %}

## 屏幕内边距区域

屏幕并不能完全自由使用：手机会为刘海、状态栏、主屏幕指示条和圆角预留空间，而每个浏览器都会在画布的一部分上绘制自己的 HUD（小地图、聊天等）。渲染器选项中的可选 `screenInset` 属性用于选择你的 UI 被放置在哪个屏幕区域中：

| 值                 | UI 所放置的区域                                                           |
| ----------------- | ------------------------------------------------------------------- |
| `'device'` *（默认）* | 设备安全区域，不包括刘海、状态栏和圆角。从 `UiCanvasInformation.screenInsetArea`.        |
| `'interactable'`  | 读取。 `浏览器报告为其自身 HUD（小地图、聊天等）之外的区域。从`读取。它覆盖哪些内容由各浏览器自行决定——请在你目标平台上验证。 |
| `'none'`          | 整个屏幕， `0,0` 位于其左上角。                                                 |

```ts
// UI 会避开刘海、状态栏和圆角——这是默认行为
ReactEcsRenderer.setUiRenderer(uiComponent, { virtualWidth: 1920, virtualHeight: 1080 })

// UI 会避开浏览器自身的 HUD
ReactEcsRenderer.setUiRenderer(uiComponent, { screenInset: 'interactable' })

// UI 覆盖整个屏幕，边距由你自己处理
ReactEcsRenderer.setUiRenderer(uiComponent, { screenInset: 'none' })
```

在桌面端设备边距为零，因此 `'device'` 在这里会把 UI 放在整个屏幕上——与 `'none'`的结果相同。该区域会在每个 tick 重新读取，因此 UI 会随着边距变化而跟随，例如在旋转时或系统栏出现和隐藏时。

{% hint style="warning" %}
**📔 不要把 UI 锚定在左上角。** 那个角落，以及整个左边缘，正是浏览器绘制小地图、聊天，以及在移动端绘制虚拟摇杆的位置。放在那里的场景 UI 会被渲染出来，但客户端自己的 UI 会画在它上面，因此会被遮住，按钮通常也无法点击。请把你的 UI 锚定到右侧或居中，或者使用 `screenInset: 'interactable'` ，这样浏览器会把它放到远离自己 HUD 的位置。参见 [永远不要把你的 UI 锚定到左上角](/creator/content-creator-zh/chang-jing-sdk7/ti-yan-she-ji/ux-ui-guide.md#never-anchor-your-ui-to-the-top-left).
{% endhint %}

{% hint style="warning" %}
**📔 `'interactable'` 在桌面端并不是无操作。** 与设备边距不同，可交互区域在 *不是* 桌面客户端中为零：它为自己的 UI 预留了大约屏幕左侧 25% 的区域，因此 `screenInset: 'interactable'` 会把你的 UI 放到剩余的 75% 区域。这就是该选项的目的，但这也意味着它会改变你的桌面布局——如果你只想在手机上使用，请使用 [`isMobile()`](/creator/content-creator-zh/mian-xiang-yi-dong-duan-gou-jian/kai-fa/detect-platform.md) 进行分支处理。

**客户端支持**: `'interactable'` 需要一个会报告该区域的浏览器。桌面端支持此功能，移动端从客户端版本 `1.12.1` 开始支持——在更旧的移动客户端上，该值会被报告为零，UI 会回退到覆盖整个屏幕。相同的 `1.12.1` 版本还会规范化 `'device'` 在 Android 和 iOS 之间的区域，因此如果布局依赖任一边距，请将其视为下限。 `'none'` 在任何地方的行为都一样。
{% endhint %}

{% hint style="warning" %}
**📔 注意**：不要把你的 UI 包在 [`ScreenInsetArea` 或 `InteractableArea`](/creator/content-creator-zh/mian-xiang-yi-dong-duan-gou-jian/kai-fa/safe-area.md#wrap-part-of-your-ui-instead) 组件里，同时又在渲染器上保留相应的 `screenInset` 值——这样内边距会被应用两次，把 UI 向内推两倍的边距。要么依赖 `screenInset`，要么把它设为 `'none'` 并且自己放置包装器。
{% endhint %}

### 真实设备上的三个区域

下面的截图是同一台手机上同一个场景，仅改变了 `screenInset` 值并渲染了三次。品红色矩形是 UI 本身；轮廓线是浏览器报告的区域——青色是完整画布，琥珀色是 `screenInsetArea`，绿色是 `interactableArea`.

<figure><img src="https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-78aa0fa3423a3ed51b5889f0150484a354b2dd30%2Fscreeninset-none.png?alt=media" alt="Scene UI covering the whole phone screen, including the notch strip and the areas under the client controls"><figcaption><p><code>screenInset: 'none'</code> —— UI 覆盖整个画布。它的角落会处于刘海下方以及客户端自身控制项后面。</p></figcaption></figure>

<figure><img src="https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-94c4b28274282d30cda22c389b375fa30c78dc64%2Fscreeninset-device.png?alt=media" alt="Scene UI inset to the device safe area, clear of the notch"><figcaption><p><code>screenInset: 'device'</code> （默认）—— UI 被收进设备安全区域，避开刘海和圆角。它仍然会与客户端的控制项重叠，因为这些不属于该区域。</p></figcaption></figure>

<figure><img src="https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-c0c75a80bed756d6b2b1483a7cca0cadef26c77e%2Fscreeninset-interactable.png?alt=media" alt="Scene UI inset to the area the client reports as free of its own HUD"><figcaption><p><code>screenInset: 'interactable'</code> —— UI 被放置在客户端为场景 UI 指定的矩形内。在这里捕获的移动端客户端 <code>1.12.1</code>上，这会排除左侧一列（头像、聊天、摇杆）；右下角的操作按钮是有意画在该区域之上的。</p></figcaption></figure>

{% hint style="info" %}
**💡 提示**: `'interactable'` 会为你提供每个浏览器为场景 UI 指定的区域。这故意并不等同于“所有客户端控制项都在该区域之外”——正如第三张截图所示，移动端操作按钮就是故意画在该区域上的。你放在它们下面的任何内容仍然可以被你触达，但会争夺相同的点击，因此请记住 [拥挤的角落](/creator/content-creator-zh/mian-xiang-yi-dong-duan-gou-jian/kai-fa/safe-area.md#where-the-client-controls-live) ，并检查你的目标平台：该区域覆盖什么由每个浏览器自行决定，并且可能会在不同版本之间变化。
{% endhint %}

每个渲染器都会遵守自己的 `screenInset`，因此主 UI 和通过 `addUiRenderer()` 添加的任何 UI 可以同时使用不同的区域。与虚拟尺寸不同，这个值从不在渲染器之间共享。

## 多个 UI 模块

如果你的场景包含多个系统或模块，并且它们各自定义了自己的 UI，你可以使用 `ReactEcsRenderer.addUiRenderer()`分别渲染每个 UI 模块。这在处理包含多个 UI 组件的复杂场景时特别有用，或者在为一个 [智能物品](https://github.com/decentraland/docs/tree/main/creator/sdk7/smart-items/smart-items.md)定义 UI 时尤其有用，因为它应该能够独立于场景其余代码中的内容正常使用。

该 `ReactEcsRenderer.addUiRenderer()` 函数要求你提供一个实体作为 UI 的所有者。这个实体可以是任何实体，甚至可以是仅仅为了作为 UI 所有者而创建的一个虚拟实体。

```ts
export function setupUi() {

    // 创建一个虚拟实体作为 UI 的所有者
    const dummyEntity = engine.addEntity()

    // 将 UI 模块定义为一个返回 UI 模块数组的函数
    const uiComponent = () => [
      // 返回一个 UI 模块的函数，
      // 返回一个 UI 模块的函数
      // ...
    ]

    // 使用该虚拟实体作为所有者来渲染 UI 模块
    ReactEcsRenderer.addUiRenderer(dummyEntity, uiComponent)
}
```

这段代码可以独立于场景中的任何其他 UI 代码存在。场景的其余部分可能包含一个 `ReactEcsRenderer.setUiRenderer()`，也可能完全没有，而 UI 仍然会被渲染。

一个 `addUiRenderer()` 调用也可以像 `setUiRenderer()`一样包含虚拟宽度和高度。然而，如果场景中有一个 `setUiRenderer()` 调用也定义了虚拟宽度和高度，那么 `addUiRenderer()` 调用中的虚拟宽度和高度将被忽略。

```tsx
ReactEcsRenderer.addUiRenderer(dummyEntity, uiComponent, { virtualWidth: 1920, virtualHeight: 1080 })
```

虚拟尺寸是一个整个场景共用的单一值，其解析规则如下： `setUiRenderer()` 上的尺寸优先；否则，第一个提供了尺寸的 `addUiRenderer()` 调用获胜；如果没有任何渲染器提供尺寸，则应用 [平台默认值](#default-virtual-size) 。仅携带一个 `screenInset` 的选项不算作已提供尺寸。

该 [`screenInset`](#screen-inset-area) 的工作方式正相反——它是按渲染器分别设置的，因此每个 UI 模块都可以位于不同的屏幕区域：

```tsx
// 无论主 UI 使用什么，这个小部件都保持在浏览器 HUD 之外
ReactEcsRenderer.addUiRenderer(dummyEntity, uiComponent, { screenInset: 'interactable' })
```

该 UI 可以使用 `ReactEcsRenderer.removeUiRenderer(dummyEntity)` 移除，此外，如果拥有该 UI 的实体被销毁，UI 也会一并被移除。若对同一个实体再次调用 `ReactEcsRenderer.addUiRenderer()` ，但传入不同的 UiRenderer，则会清理之前的那个，并由新的替换，同时保持其在堆叠顺序中的位置。再次调用它也是修改已经渲染的某个 UI 模块选项的方式，例如它的 `zIndex`.

### UI 模块之间的堆叠顺序

默认情况下，UI 模块会按照它们首次出现的顺序堆叠，后出现的会位于前面。首次出现在同一帧中的模块——通常是场景第一帧之前注册的所有内容——会与来自 `setUiRenderer()` 的主 UI 一起渲染，位于最底层，然后其余模块按添加顺序排列在上面。要显式控制这一点，请在渲染器选项中传入一个 `zIndex` ，具有更高 `zIndex` 的模块会渲染在更低值模块的前面，而不管它们出现的顺序。保留为 `0` 的模块会维持默认顺序。该选项在 `setUiRenderer()`上的工作方式相同，因此主 UI 也可以放在其他模块前面。

```tsx
// 虽然最先注册，但始终位于其他 UI 模块前面
ReactEcsRenderer.addUiRenderer(dummyEntity, uiComponent, { zIndex: 10 })

// 主 UI 位于所有未设置更低值的模块之后
ReactEcsRenderer.setUiRenderer(mainUi, { zIndex: -10 })
```

这与 `zIndex` 中的 `UiEntity`是不同的设置，后者只会对 [内的兄弟节点排序](/creator/content-creator-zh/chang-jing-sdk7/2d-ui/ui-positioning.md#z-index)。渲染器选项是在整个模块之间进行排序，而每个模块内部的元素则保留各自的 `zIndex`.

### 共享一个 setUiRenderer 语句

不要为每个 UI 模块都调用 `ReactEcsRenderer.addUiRenderer()` ，你可以只调用 `ReactEcsRenderer.setUiRenderer()` 一次，传入一个 UI 模块数组，这些模块可以位于不同文件中。

```ts
const uiComponent = () => [
  // 返回一个 UI 模块的函数，
  // 返回一个 UI 模块的函数
  // ...
]

ReactEcsRenderer.setUiRenderer(uiComponent, { virtualWidth: 1920, virtualHeight: 1080 })
```

下面是一个更完整的示例：

***ui.tsx 文件：***

```ts
export function UIModule1() {
  return (
    <UiEntity
      uiTransform={{
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'space-between',
        positionType: 'absolute',
        position: { right: '3%', bottom: '3%' },
      }}
    >
      <Label value="Hello World!" fontSize={18} textAlign="middle-center" />
    </UiEntity>
  )
}

export function UIModule2() {
  return (
    <UiEntity
      uiTransform={{
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'space-between',
        positionType: 'absolute',
        position: { right: '3%', top: '3%' },
      }}
    >
      <Label
        value="这里还有一些 UI！"
        fontSize={18}
        textAlign="middle-center"
      />
    </UiEntity>
  )
}
```

***index.ts 文件：***

```ts
import { ReactEcsRenderer } from '@dcl/sdk/react-ecs'
import { UIModule1, UIModule2 } from './ui'

export function main() {
    ReactEcsRenderer.setUiRenderer(() => [
      UIModule1(),
      UIModule2(),
      // ...
      // 下方这一行是为了使用 DCL UI Toolkit 库
      // https://github.com/decentraland-scenes/dcl-ui-toolkit
      ui.render(),
    ], { virtualWidth: 1920, virtualHeight: 1080 })
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.decentraland.org/creator/content-creator-zh/chang-jing-sdk7/2d-ui/onscreen-ui.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
