---
title: "해상도 대응은 씬이 아니라 에셋 한 장에 적힌다"
description: "UI Toolkit 의 Panel Settings 는 Canvas Scaler 와 하는 일이 거의 같은데 있는 자리가 다릅니다. 배율 방식 세 가지, Match 값, Sort Order 로 정하는 패널 순서, Render Texture, Safe Area 가 없다는 것까지 정리했습니다."
author: "유니얼"
pubDate: 2026-09-18
category: "game-dev/unity/ui"
tags: ["Unity", "UI", "UI Toolkit"]
canonical: https://blog.unialgames.com/posts/uitk-panel-settings/
---
[칸은 남는 자리를 나눠 갖는 방식으로 정해진다](/posts/uitk-flex-layout/)까지가 한 화면 안쪽 이야기였습니다. 이번은 그 화면이 통째로 어떤 크기로 뜨는지입니다.

uGUI 에서 이 일을 하던 것은 [Canvas Scaler](/posts/unity-ui-canvas-scaler-safe-area/) 였습니다. UI Toolkit 에서는 **Panel Settings** 입니다. 값 이름과 계산은 거의 그대로 옮겨왔는데, **있는 자리가 다릅니다.**

## 규칙이 씬이 아니라 에셋에 있습니다

Canvas Scaler 는 컴포넌트였습니다. 캔버스마다 하나씩 붙어 있고, 값은 그 씬이나 프리팹 안에 들어 있었습니다. Panel Settings 는 **프로젝트 창에 있는 에셋 한 장** 입니다.

<figure class="diagram">
<svg viewBox="0 0 680 272" role="img" aria-label="uGUI 는 캔버스마다 Canvas Scaler 컴포넌트가 붙고 UI Toolkit 은 UI Document 여럿이 에셋 하나를 가리키는 그림">
<text x="24" y="24" font-size="13" fill="var(--text)">해상도 규칙이 어디에 있는가</text>
<text x="24" y="50" font-size="11.5" fill="var(--accent2)">uGUI · 씬 안</text>
<rect x="24" y="62" width="276" height="52" rx="8" fill="var(--bg-subtle)" stroke="var(--border)"></rect>
<text x="40" y="93" font-size="11" fill="var(--text)">Canvas 1</text>
<rect x="150" y="75" width="134" height="26" rx="13" fill="var(--accent2)" fill-opacity="0.16" stroke="var(--accent2)"></rect>
<text x="217" y="92" font-size="10" fill="var(--text)" text-anchor="middle">Canvas Scaler</text>
<rect x="24" y="122" width="276" height="52" rx="8" fill="var(--bg-subtle)" stroke="var(--border)"></rect>
<text x="40" y="153" font-size="11" fill="var(--text)">Canvas 2</text>
<rect x="150" y="135" width="134" height="26" rx="13" fill="var(--accent2)" fill-opacity="0.16" stroke="var(--accent2)"></rect>
<text x="217" y="152" font-size="10" fill="var(--text)" text-anchor="middle">Canvas Scaler</text>
<rect x="24" y="182" width="276" height="52" rx="8" fill="var(--bg-subtle)" stroke="var(--border)"></rect>
<text x="40" y="213" font-size="11" fill="var(--text)">Canvas 3</text>
<rect x="150" y="195" width="134" height="26" rx="13" fill="var(--accent2)" fill-opacity="0.16" stroke="var(--accent2)"></rect>
<text x="217" y="212" font-size="10" fill="var(--text)" text-anchor="middle">Canvas Scaler</text>
<text x="162" y="254" font-size="10.5" fill="var(--text-muted)" text-anchor="middle">캔버스마다 하나씩 붙어 있습니다</text>
<text x="356" y="50" font-size="11.5" fill="var(--accent)">UI Toolkit</text>
<rect x="356" y="62" width="140" height="52" rx="8" fill="var(--bg-subtle)" stroke="var(--border)"></rect>
<text x="372" y="93" font-size="10.5" fill="var(--text)">UI Document 1</text>
<line x1="496" y1="88" x2="524" y2="148" stroke="var(--accent)" stroke-width="1" stroke-dasharray="3 3"></line>
<rect x="356" y="122" width="140" height="52" rx="8" fill="var(--bg-subtle)" stroke="var(--border)"></rect>
<text x="372" y="153" font-size="10.5" fill="var(--text)">UI Document 2</text>
<line x1="496" y1="148" x2="524" y2="148" stroke="var(--accent)" stroke-width="1" stroke-dasharray="3 3"></line>
<rect x="356" y="182" width="140" height="52" rx="8" fill="var(--bg-subtle)" stroke="var(--border)"></rect>
<text x="372" y="213" font-size="10.5" fill="var(--text)">UI Document 3</text>
<line x1="496" y1="208" x2="524" y2="148" stroke="var(--accent)" stroke-width="1" stroke-dasharray="3 3"></line>
<rect x="528" y="112" width="128" height="72" rx="8" fill="var(--accent)" fill-opacity="0.16" stroke="var(--accent)"></rect>
<text x="592" y="142" font-size="11" fill="var(--text)" text-anchor="middle">PanelSettings</text>
<text x="592" y="162" font-size="9.5" fill="var(--text-muted)" text-anchor="middle">프로젝트 창의 에셋</text>
<text x="506" y="254" font-size="10.5" fill="var(--text-muted)" text-anchor="middle">셋이 같은 파일 하나를 가리킵니다</text>
</svg>
<figcaption>값을 맞출 일이 없습니다. 하나뿐이라서 그렇습니다.</figcaption>
</figure>

`Assets > Create > UI Toolkit > Panel Settings` 로 만들고, 씬의 UI Document 컴포넌트들이 그 에셋을 가리킵니다. 문서 표현으로 "The Panel Settings asset defines a panel in the Scene where the UI is rendered. The UI Document component connects the UI to the panel." 입니다.

패널이 무엇인지도 같은 문서에 적혀 있습니다. "The panel serves as the root element where UI hierarchies attach, and renders the UI in the Scene at runtime." 화면을 올려놓는 판입니다.

여기서 따라오는 것이 둘입니다.

- 화면이 스무 개여도 해상도 규칙은 **파일 하나** 입니다. 기준 해상도를 바꾸면 스무 개가 같이 바뀝니다
- 그래서 예외를 만들려면 값을 다르게 주는 게 아니라 **Panel Settings 를 하나 더 만듭니다.** 전체 화면 UI 와 3D 안에 붙는 UI 는 애초에 같은 판을 쓸 이유가 없습니다

uGUI 쪽에서 캔버스마다 Canvas Scaler 값이 조금씩 달라져 있던 프로젝트를 본 적이 있다면, 이 구조가 왜 편한지 바로 보입니다. 값을 맞출 일이 없습니다. 하나뿐이니까요.

## 배율 정하는 방식이 세 가지입니다

`Scale Mode` 가 "how the panel's UI scales when the screen size changes" 를 정합니다. 셋 중 하나를 고르면 딸린 값이 달라집니다.

| Scale Mode | 딸린 값 | 하는 일 |
| --- | --- | --- |
| Constant Pixel Size | `Scale` | 화면이 커져도 픽셀 크기가 그대로입니다. `Scale` 로 통째로 곱합니다 |
| Constant Physical Size | `Reference DPI`, `Fallback DPI` | 화면 밀도를 재서 **손가락에 닿는 실제 크기** 를 맞춥니다 |
| Scale with Screen Size | `Reference Resolution`, `Screen Match Mode` | 시안 해상도를 기준으로 화면 크기에 따라 늘리고 줄입니다 |

게임에서 거의 항상 쓰는 것은 셋째입니다. 문서 표현으로 `Reference Resolution` 은 "the resolution that this panel's UI is designed for" 이고, "If the screen resolution is larger than the reference resolution, the UI scales up" 입니다.

`Constant Physical Size` 는 이름이 낯설지만 하는 일이 분명합니다. "Set the Reference DPI value to the screen density that your UI was designed for. When the system renders your UI, it tries to find the actual DPI value of the screen, and compares it to the Reference DPI." DPI 를 못 읽는 기기에서는 `Fallback DPI` 를 씁니다. 버튼을 실제로 손가락만 하게 유지해야 하는 화면에 맞습니다.

## 시안 비율과 실제 화면 비율이 다를 때

`Scale with Screen Size` 를 고르면 `Screen Match Mode` 가 따라옵니다. 셋입니다.

| Screen Match Mode | 문서 설명 |
| --- | --- |
| Match Width or Height | "Scale the canvas area with the width or the height as reference, or a linear interpolation between the width and the height" |
| Shrink | "Crop the canvas area either horizontally or vertically, so the size of the canvas is smaller than the reference" |
| Expand | "Expand the canvas area either horizontally or vertically, so the size of the canvas is larger than the reference" |

`Match Width or Height` 를 고르면 `Match` 슬라이더가 나옵니다. "if the value is `0`, it matches the width; if the value is `1`, it matches the height; if the value is `0.4`, it linearly interpolates between width and height by `40%`" 입니다.

**Canvas Scaler 와 계산이 같습니다.** 3편에서 쓴 그림을 그대로 다시 씁니다.

<x-fit>
<script type="application/json">
{"label":"기준 1920 × 1080 에서 패널이 어떻게 잡히나","screens":[["16:9",1920,1080],["20:9 폰",2400,1080],["4:3 태블릿",2048,1536]],"default":[1,0],"note":"점선이 시안, 굵은 실선이 실제 화면입니다. Panel Settings 의 Reference Resolution 과 Screen Match Mode 가 정확히 이 계산입니다."}
</script>
</x-fit>

결론도 그때와 같습니다. **가로와 세로를 동시에 지킬 수 없습니다.** Match 0 은 가로를 지키고 세로를 흘리고, Match 1 은 반대입니다. Expand 는 시안보다 넓게 잡아서 여백이 생기고, Shrink 는 좁게 잡아서 잘립니다.

세로로 긴 폰과 가로로 넓은 태블릿을 같이 받아야 하면, 값 하나로 해결하려 하지 말고 잘려도 되는 자리를 배치로 만들어 두는 쪽이 낫습니다. 3편에서 본 `flex-grow` 가 그 자리입니다. 여백을 가져갈 칸을 정해 두면 비율이 달라져도 그 칸만 늘고 줄어듭니다.

## 패널 순서는 숫자 하나입니다

<figure class="diagram">
<svg viewBox="0 0 680 266" role="img" aria-label="Sort Order 가 큰 패널이 위에 그려지고 클릭을 먼저 받는 그림">
<text x="24" y="24" font-size="13" fill="var(--text)">Sort Order 가 큰 판이 위입니다</text>
<rect x="100" y="172" width="320" height="64" rx="8" fill="var(--bg-subtle)" stroke="var(--border)"></rect>
<text x="116" y="200" font-size="11" fill="var(--text)">Sort Order 0</text>
<text x="116" y="218" font-size="9.5" fill="var(--text-muted)">HUD</text>
<rect x="150" y="118" width="320" height="64" rx="8" fill="var(--accent)" fill-opacity="0.16" stroke="var(--accent)"></rect>
<text x="166" y="146" font-size="11" fill="var(--text)">Sort Order 10</text>
<text x="166" y="164" font-size="9.5" fill="var(--text-muted)">팝업</text>
<rect x="200" y="64" width="320" height="64" rx="8" fill="var(--accent2)" fill-opacity="0.2" stroke="var(--accent2)"></rect>
<text x="216" y="92" font-size="11" fill="var(--text)">Sort Order 20</text>
<text x="216" y="110" font-size="9.5" fill="var(--text-muted)">로딩 화면</text>
<line x1="614" y1="46" x2="532" y2="88" stroke="var(--warn)" stroke-width="1.3"></line>
<polygon points="524,92 536,88 534,79" fill="var(--warn)"></polygon>
<text x="620" y="42" font-size="10.5" fill="var(--warn)">클릭</text>
<text x="656" y="158" font-size="10.5" fill="var(--text-muted)" text-anchor="end">맨 앞 판이 먼저 받습니다</text>
<text x="24" y="256" font-size="11.5" fill="var(--text-muted)">뒤 판의 버튼은 앞 판이 덮은 자리에서는 안 눌립니다</text>
</svg>
<figcaption>팝업을 띄웠는데 뒤 버튼이 눌리는 사고가 이 숫자에서 갈립니다.</figcaption>
</figure>

[하이어라키 맨 아래가 화면 맨 앞이다](/posts/unity-ui-canvas-draw-order/)에서 uGUI 의 Canvas Sort Order 를 봤습니다. Panel Settings 에도 `Sort Order` 가 있고 뜻이 같습니다. "Panels with higher Sort Order values are drawn on top of panels with lower values."

같이 붙어 있는 문장이 더 중요합니다. "the panel that's visually in the front intercepts clicks from the user before the panels that are visually in the back." **앞에 있는 판이 클릭을 먼저 가로챕니다.** 팝업을 띄웠는데 뒤 버튼이 눌리는 사고가 여기서 갈립니다.

패널을 몇 개 둘지는 정할 문제입니다. 하나에 다 넣으면 순서가 uxml 안의 쌓기 순서로 정해지고, 나누면 숫자로 정해집니다. 로딩 화면이나 전역 팝업처럼 **항상 맨 앞이어야 하는 것** 만 따로 패널을 주는 정도가 무난합니다.

`Target Display` 도 여기 있습니다. "Only set this if the target texture isn't set, as the target texture takes precedence over the target display."

## 3D 안에 붙이려면 텍스처로 뺍니다

`Target Texture` 는 "Set the render texture for the panel. For a 3D game, display UI on 3D geometry in the scene." 입니다.

패널을 화면에 직접 그리는 대신 Render Texture 에 그리고, 그 텍스처를 씬의 메시에 붙입니다. uGUI 의 World Space 캔버스가 하던 일을 한 단계 돌려서 합니다. 벽에 붙은 모니터, 손목의 단말기 같은 것이 이 방식입니다.

돌려서 하는 만큼 따라오는 것이 있습니다. 텍스처 해상도를 직접 정해야 하고, 그 텍스처 크기가 곧 그 UI 의 픽셀 수입니다. 가까이 다가가서 보는 화면이면 텍스처를 크게 잡아야 하고, 크게 잡으면 메모리를 그만큼 씁니다.

## Safe Area 항목은 없습니다

Canvas Scaler 편에서 노치를 피하는 Safe Area 를 같이 봤는데, Panel Settings 에는 그 항목이 없습니다.

직접 해야 합니다. `Screen.safeArea` 가 픽셀 단위 사각형을 주니까, 그 값을 루트 요소의 패딩으로 옮겨 넣는 식입니다.

```csharp
var root = GetComponent<UIDocument>().rootVisualElement;
var safe = Screen.safeArea;
root.style.paddingTop = Screen.height - safe.yMax;
root.style.paddingBottom = safe.y;
root.style.paddingLeft = safe.x;
root.style.paddingRight = Screen.width - safe.xMax;
```

주의할 것이 둘입니다. `Screen.safeArea` 는 픽셀이고 패널은 배율이 걸린 좌표라서, `Scale with Screen Size` 를 쓰면 배율로 나눠 줘야 값이 맞습니다. 그리고 화면을 돌리면 값이 바뀌므로 한 번 넣고 끝내면 안 됩니다.

이런 코드는 화면마다 적지 말고 루트에 한 번만 걸어 두는 쪽이 낫습니다. 패널이 하나면 붙일 자리도 하나입니다.

## 같은 에셋에 들어 있는 나머지

해상도와 상관없지만 같은 파일에 있는 것들입니다. 이름과 하는 일만 알아 두면 나중에 찾습니다.

| 항목 | 하는 일 |
| --- | --- |
| `Theme Style Sheet` | "Apply a default TSS file to every UI Document that the panel renders" |
| `Text Settings` | 글꼴 기본값. 안 넣으면 "UI Toolkit automatically creates one with the default settings" |
| `Clear Settings` | "Set how the panel clears before rendering" |
| `Dynamic Atlas Settings` | 작은 텍스처들을 런타임에 한 장으로 묶는 설정 |
| `Vertex Budget` | "Set the approximate number of vertices the panel uses. This helps initialize vertex buffers with the right size, which can reduce draw calls" |

`Theme Style Sheet` 는 [이름을 붙여야 스타일을 걸 수 있다](/posts/uitk-uss-selectors/)에서 본 `unity-button` 같은 기본 클래스들의 값이 들어 있는 파일입니다. 기본 테마를 통째로 갈아 끼우고 싶으면 여기를 바꿉니다.

`Dynamic Atlas Settings` 는 uGUI 의 Sprite Atlas 와 목적이 같습니다. 아이콘이 여러 장이면 묶어서 한 번에 그리는 쪽이 낫다는 이야기입니다.

## 정리

- Canvas Scaler 는 컴포넌트였고 **Panel Settings 는 프로젝트의 에셋** 입니다. 화면이 몇 개든 규칙은 한 파일입니다
- 예외가 필요하면 값을 다르게 주는 게 아니라 Panel Settings 를 하나 더 만듭니다
- `Scale Mode` 는 셋입니다. 게임에서 쓰는 것은 대부분 `Scale with Screen Size` 입니다
- `Screen Match Mode` 와 `Match` 값의 계산이 Canvas Scaler 와 같습니다. 가로와 세로를 동시에 지킬 수 없다는 결론도 같습니다
- `Sort Order` 가 큰 판이 위에 그려지고, **위에 있는 판이 클릭을 먼저 가로챕니다**
- 3D 안에 붙이려면 `Target Texture` 로 Render Texture 에 그린 다음 메시에 붙입니다
- Safe Area 항목이 없어서 `Screen.safeArea` 를 읽어 루트 패딩으로 직접 넣습니다. 배율로 나누는 것과 회전 처리를 빠뜨리기 쉽습니다
- `Theme Style Sheet` 에 기본 컨트롤 스타일이 들어 있습니다. 테마를 갈아 끼우는 자리가 여기입니다