> 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-ko/sdk7/2d-ui/onscreen-ui.md).

# 화면 UI

씬의 플레이어를 위한 UI를 만드는 방법을 알아보세요. 예를 들어 게임 관련 정보를 표시하는 데 유용합니다.

장면을 위한 UI를 만들 수 있으며, 이는 3D 월드 공간이 아니라 화면의 고정된 2D 공간에 표시됩니다.

UI 요소는 플레이어가 장면의 LAND 구획 안에 있을 때만 보입니다. 인접한 장면에는 표시할 자체 UI가 있을 수도 있기 때문입니다. UI의 일부는 월드 공간에서 특정 이벤트가 발생할 때 열리도록 트리거할 수도 있습니다. 예를 들어 플레이어가 특정 장소를 클릭하는 경우입니다.

중첩된 `UiEntity` 객체 구조를 JSX로 정의하여 UI를 만들 수 있습니다. UI에 사용되는 문법은 [React](https://reactjs.org/) 의 문법과 매우 비슷합니다. (웹 UI를 만들기 위한 아주 널리 쓰이는 자바스크립트 기반 라이브러리입니다.)

{% hint style="warning" %}
**📔 참고**: UI 문법은 `.tsx` 확장자를 가진 파일에서만 정의할 수 있습니다. `.tsx` 파일은 `.ts` 파일이 지원하는 모든 기능에 더해 UI 문법까지 지원합니다. 우리는 `ui.tsx` 파일을 만들고 그곳에 UI를 정의하는 것을 권장합니다. UI 렌더 메서드는 `index.ts` 에서 `ReactEcsRenderer.setUiRenderer(yourUiMethodName)`를 호출하는 것을 잊지 마세요. 아래 예시를 보세요.
{% endhint %}

정적 요소로 구성된 간단한 UI는 HTML과 꽤 비슷해 보일 수 있지만, 상태 변화에 반응하는 동적 요소를 추가하면 훨씬 더 강력한 작업을 할 수 있습니다.

기본 Decentraland 탐색기 UI에는 채팅 위젯, 지도 및 다른 요소들이 포함됩니다. 이러한 UI 요소는 항상 최상위 레이어에 표시되며, 어떤 장면 전용 UI보다 위에 있습니다. 따라서 장면의 UI 요소가 이들과 같은 화면 공간을 차지하면 가려지게 됩니다.

참고 [UX 가이드라인](/creator/content-creator-ko/sdk7/designing-the-experience/ux-ui-guide.md) UI의 모양과 느낌을 설계하는 팁을 보려면

{% hint style="info" %}
**📱 모바일을 위한 디자인**: 기기들은 노치, 상태 표시줄, 홈 인디케이터 및 둥근 모서리를 위해 화면 공간을 예약하며, 클라이언트는 캔버스의 일부 위에 자체 컨트롤을 그립니다. 어느 쪽도 직접 측정할 필요는 없습니다. 아래의 [`screenInset`](#screen-inset-area) 로 UI가 배치될 영역을 선택하세요. 기본적으로 기기의 안전 영역이 사용되므로, 별다른 작업 없이 하드웨어 여백이 피합니다. 게시하기 전에 [모바일 안전 영역](/creator/content-creator-ko/build-for-mobile/develop/safe-area.md) 및 [모바일 UI 모범 사례](/creator/content-creator-ko/build-for-mobile/develop/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-ko/sdk7/2d-ui/ui_input_binding.md) — 요소가 눌린 동안 입력 액션을 유지하여 커스텀 컨트롤을 만들 수 있습니다

HTML 태그와 마찬가지로 컴포넌트를 self-closing으로 정의하거나 하나를 다른 것 안에 중첩할 수 있습니다.

{% hint style="danger" %}
**경고:** 전체 화면 레이아웃 래퍼에는 포인터 핸들러를 두지 마세요. 다음의 어느 `onMouse` 핸들러나 `pointerFilter: 'block'`를 `UiEntity` 크기의 `100%` 크기가 `100%` 요소에 추가하면 화면의 모든 클릭을 가로채게 되어 다른 모든 UI와 전체 3D 월드가 클릭 불가능해집니다. 대신 핸들러가 필요한 패널이나 버튼에 넣으세요. 다음을 보세요: [포인터 차단](/creator/content-creator-ko/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() }}
  >
    {/* self-closing 자식 엔티티 */}
    <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`  |
| 데스크톱 및 웹 | `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가 아닌 크기와 데스크톱 또는 웹에서의 모든 유효한 크기는 그대로 존중됩니다.

{% hint style="info" %}
**📔 참고**:  `vw` 및 `vh` 단위는 가상 화면과 무관합니다: `1vw` 는 캔버스 너비의 1%이고 `1vh` 는 캔버스 높이의 1%입니다. CSS와 정확히 같습니다. 또한 [`screenInset`](#screen-inset-area)를 무시하므로, 기본 `'device'` 안에 `width: '100vw'` 는 `width: '100%'` 보다 더 넓습니다. 첫 번째는 전체 화면에 걸치고, 두 번째는 inset 영역을 채웁니다.
{% 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 레이아웃에 관여하지 않습니다.** 픽셀 크기의 UI는 이제 고밀도(레티나 및 모바일) 화면에서 이전보다 최대 2\~3배 더 크게 보입니다. **이것에는 제외 설정이 없습니다** — 손으로 조정했던 크기는 다시 확인하고, 장면이 자체적으로 계산하던 스케일 팩터는 제거하세요.
   {% endhint %}

## 화면 인셋 영역

화면은 완전히 사용 가능하지 않습니다. 휴대폰은 노치, 상태 표시줄, 홈 인디케이터 및 둥근 모서리를 위해 공간을 예약하고, 모든 탐색기는 캔버스 일부 위에 자체 HUD(미니맵, 채팅, …)를 그립니다. 렌더러 옵션의 선택적 `screenInset` 속성은 UI가 배치될 화면 영역을 선택합니다:

| 값                  | UI가 배치되는 영역                                                                                                                               |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `'device'` *(기본값)* | 노치, 상태 표시줄 및 둥근 모서리를 제외한 기기의 안전 영역입니다. 다음에서 읽습니다. `UiCanvasInformation.screenInsetArea`.                                                  |
| `'interactable'`   | 탐색기가 자체 HUD(미니맵, 채팅, …)가 없는 것으로 보고하는 영역입니다. 다음에서 읽습니다. `UiCanvasInformation.interactableArea`. 무엇을 포함하는지는 각 탐색기에 달려 있습니다. 대상 플랫폼에서 확인하세요. |
| `'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' })
```

데스크톱에서는 기기 inset이 0이므로 `'device'` 는 그곳에서 UI를 전체 화면에 배치합니다. 이는 `'none'`와 동일한 결과입니다. 영역은 매 틱마다 다시 읽히므로, 회전하거나 시스템 바가 나타나거나 숨을 때 UI가 inset을 따라갑니다.

{% hint style="warning" %}
**📔 `'interactable'` 는 데스크톱에서 무효 동작이 아닙니다.** 기기 inset과 달리 interactable 영역은 *아닙니다* 데스크톱 클라이언트에서 0입니다. 자체 UI를 위해 화면의 왼쪽 약 25%를 예약하므로 `screenInset: 'interactable'` 가 그곳의 남은 75%에 UI를 배치합니다. 이것이 이 옵션의 목적이지만, 데스크톱 레이아웃도 바뀐다는 뜻이므로 [`isMobile()`](/creator/content-creator-ko/build-for-mobile/develop/detect-platform.md) 를 사용해 휴대폰에서만 적용되도록 분기하세요.

**클라이언트 지원**: `'interactable'` 영역을 보고하는 탐색기가 필요합니다. 데스크톱에서는 지원되며, 모바일에서는 클라이언트 버전 `1.12.1` 부터 지원됩니다. 더 पुराने 모바일 클라이언트에서는 값이 0으로 보고되고, UI는 전체 화면을 덮는 것으로 되돌아갑니다. 동일한 `1.12.1` 릴리스는 또한 `'device'` 영역을 Android와 iOS 간에 표준화하므로, 어느 inset에 의존하는 레이아웃이든 그 값을 하한으로 취급하세요. `'none'` 는 모든 곳에서 동일하게 동작합니다.
{% endhint %}

{% hint style="warning" %}
**📔 참고**: UI를 [`ScreenInsetArea` 또는 `InteractableArea`](/creator/content-creator-ko/build-for-mobile/develop/safe-area.md#wrap-part-of-your-ui-instead) 컴포넌트로 감싸면서 동시에 일치하는 `screenInset` 값을 렌더러에 남겨 두지 마세요. inset이 두 번 적용되어 UI가 여백의 두 배만큼 안쪽으로 밀리게 됩니다. 대신 `screenInset`에 의존하거나, 그것을 `'none'` 로 설정하고 래퍼를 직접 배치하세요.
{% endhint %}

### 실제 기기의 세 영역

아래 캡처는 같은 휴대폰에서 같은 장면을, `screenInset` 값만 바꿔 세 번 렌더링한 것입니다. 자홍색 사각형은 UI 자체이고, 윤곽선은 탐색기가 보고하는 영역입니다. 청록색은 전체 캔버스, 호박색은 `screenInsetArea`, 초록색은 `interactableArea`.

<figure><img src="https://3980763956-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://3980763956-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://3980763956-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-ko/build-for-mobile/develop/safe-area.md#where-the-client-controls-live) 를 염두에 두고 대상 플랫폼을 확인하세요. 그 영역이 무엇을 포함하는지는 각 탐색기의 판단이며 버전에 따라 바뀔 수 있습니다.
{% endhint %}

각 렌더러는 자체 `screenInset`을 존중하므로, 메인 UI와 `addUiRenderer()` 로 추가한 모든 UI는 서로 다른 영역을 동시에 사용할 수 있습니다. 가상 크기와 달리 이 값은 렌더러 간에 절대 공유되지 않습니다.

## 여러 UI 모듈

장면에 각각 자체 UI를 정의하는 여러 시스템이나 모듈이 있다면, 각 UI 모듈을 `ReactEcsRenderer.addUiRenderer()`로 렌더링할 수 있습니다. 이는 여러 UI 컴포넌트가 있는 복잡한 장면을 작업할 때 특히 유용하며, [스마트 아이템](https://github.com/decentraland/docs/tree/main/creator/sdk7/smart-items/smart-items.md)용 UI를 정의할 때도 유용합니다. 이 경우 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로 다시 호출되면 이전 것은 정리되고 새 것이 이를 대체합니다.

### 하나의 setUiRenderer 문 공유하기

각 UI 모듈마다 `ReactEcsRenderer.addUiRenderer()` 를 호출하는 대신, 서로 다른 파일에 있을 수 있는 UI 모듈 배열에 대해 `ReactEcsRenderer.setUiRenderer()` 를 한 번만 호출할 수 있습니다.

```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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
