11 KiB
WL UI 기준 노트 — CanvasScaler 유닛 스케일 · Safe Area 공통 적용
WL-802b (#802 재단 2차) · 2026-09-08 · 브랜치
wl/ui/WL-802b-safearea-scale(basemain@abcd03865) 대상 = 세로 1080×1920 모바일(#782 모바일 전제 · #803 세로 전용). 후속 UI 작업은 이 문서의 계수·규칙을 기준으로 한다. 줄 번호는 이 브랜치(WL-802b 적용 후) 파일 기준. 실측 재현 =unity command run_script --file AgentScripts/WL802b_Probe.cs --entry WL802b_Probe.Dump
1. 세 프리팹의 CanvasScaler (실측)
| 프리팹 | 참조 해상도 | UI Scale Mode | Screen Match Mode | matchWidthOrHeight | 근거 |
|---|---|---|---|---|---|
Assets/ResWork/UIPrefabs/Title/TitleInfo.prefab |
1080 × 1920 | ScaleWithScreenSize(m_UiScaleMode: 1) |
Expand(m_ScreenMatchMode: 1) |
0 (Expand 에서 미사용) | TitleInfo.prefab:983-988 |
Assets/ResWork/UIPrefabs/Title/SortOrder_5.prefab |
1920 × 1080 | ScaleWithScreenSize | Expand | 0 | SortOrder_5.prefab:9173-9178 |
Assets/Res_Addr/MainUI/NewGameUI.prefab |
1920 × 1080 | ScaleWithScreenSize | Expand | 0 | NewGameUI.prefab:131617-131622 |
- 세 프리팹 모두 캔버스가 프리팹 루트다(
m_Father: {fileID: 0}) ·m_ReferencePixelsPerUnit: 100동일. - ⚠️ matchWidthOrHeight 는 Expand 모드에서 아무 효과가 없다.
m_MatchWidthOrHeight: 0은 직렬화된 잔여값일 뿐, MatchWidthOrHeight 모드로 바꾸기 전에는 무의미하다. 「match 0 이라서 폭 기준」이라는 해석은 틀렸다 — 폭 기준이 되는 이유는 아래 Expand 산식 때문이다.
2. Expand 산식과 계수
scaleFactor = min(screenW / refW, screenH / refH) // ScreenMatchMode.Expand
실화면 px = 프리팹 유닛 × scaleFactor
세로(폭이 좁은) 화면에서는 두 참조 해상도 모두 폭 항이 최솟값이 되므로 scaleFactor = screenW / refW 이고,
따라서 두 계열의 비는 화면 해상도와 무관하게 항상 1920 / 1080 = 1.7778 배로 고정된다.
| 화면(QA 5종 · #802 §3) | ref 1080×1920 (TitleInfo) |
ref 1920×1080 (SortOrder_5·NewGameUI) |
비 |
|---|---|---|---|
| 1080 × 1920 | 1.0 | 0.5625 | 1.7778 |
| 1080 × 2340 | 1.0 | 0.5625 | 1.7778 |
| 1080 × 2400 | 1.0 | 0.5625 | 1.7778 |
| 720 × 1600 | 0.66667 | 0.375 | 1.7778 |
| 1440 × 3200 | 1.33333 | 0.75 | 1.7778 |
- 세로 화면에서 높이는 남는다(Expand = 잘림 없음). 1080×2400 이면 1920 기준 캔버스의 높이 유닛은 2400/0.5625 = 4266.7 유닛까지 늘어난다 → 화면 상·하단에 여백이 생기므로 세로 전체를 채우는 배경은 stretch 앵커로 잡아야 한다(고정 sizeDelta 금지).
3. 유닛 ↔ px 환산표 (세로 · 화면 폭 1080 기준)
| 실화면 px | ref 1080 프리팹의 유닛 | ref 1920 프리팹의 유닛 (= px ÷ 0.5625 = px × 1.7778) |
|---|---|---|
| 1 px | 1 | 1.7778 |
| 44 px (최소 터치 하한 참고) | 44 | 78.2 |
| 88 px | 88 | 156.4 |
| 130 px | 130 | 231.1 |
| 340 px | 340 | 604.4 |
- 검증 예 (#806 후속 B ·
SortOrder_5.prefab의PopupUI/child실측):
| 오브젝트 | 유닛(1920 기준, 직렬화 값) | ÷ 1.7778 | 실화면 px (1080 폭) |
|---|---|---|---|
bg |
1529 × 924 | 860 × 520 | |
msg |
1387 × 498 @ (0, 124) | 780 × 280 @ (0, 70) | |
btn_ok |
604 × 231 @ (0, −267) | 340 × 130 @ (0, −150) | |
| 폰트 | 48 | 27 |
340 ÷ 0.5625 = 604.4, 130 ÷ 0.5625 = 231.1 → 반올림 일치. 산출 근거는 AgentScripts/WL806_Popup.cs:18-20.
- 작성 규칙:
TitleInfo(1080 기준)에 넣는 값은 세로 1080 화면에서 px 와 1:1 이다.SortOrder_5·NewGameUI(1920 기준)에 같은 실크기를 내려면 반드시 ×1.7778 한다. 두 계열의 수치를 그대로 복사하면 1.78배 어긋난다.
4. 인계서 §1 표기 불일치 (문서 오기)
인계서 §1 UI 행은 NewGameUI.prefab 을 1080×1920 으로 적고 있으나, 실제 직렬화 값은 1920×1080 이다(NewGameUI.prefab:131620 m_ReferenceResolution: {x: 1920, y: 1080}).
→ 인계서 쪽이 오기로 「추정」. 이 노트의 실측값을 SOT 로 쓴다. 인계서 수정은 Lead 판단(조직 레포는 워커가 커밋하지 않는다).
5. Safe Area 적용 규칙
컴포넌트
WL.UI.SafeAreaFitter (Assets/WL/UI/Scripts/SafeAreaFitter.cs · guid 4f4cdcc41055ee140ae3a9916e8563ce)
Screen.safeArea 를 화면 크기로 나눠 anchorMin/anchorMax 에 넣고 offsetMin/offsetMax 를 0 으로 덮어쓴다(SafeAreaFitter.cs:40-48).
반드시 지킬 것
- 전용 루트 패널에만 붙인다. 캔버스 직속에 Graphic 없는 빈 패널(
WL_SafeArea)을 두고 거기에만 부착한다. 안전 영역을 따라야 하는 UI 는 이 패널의 자식으로 넣는다. - 이미 스트레치 + offset 을 쓰는 패널에는 붙이지 않는다.
Apply()가 offsetMin/Max 를 무조건 0 으로 덮어쓰므로, 여백(offset)으로 레이아웃을 잡아둔 패널에 붙이면 그 여백이 소실된다. 같은 이유로LayoutGroup/ContentSizeFitter가 자기 RectTransform 을 제어하는 오브젝트에도 붙이지 않는다. - 프리팹당 1개.
[DisallowMultipleComponent]는 같은 오브젝트의 중복만 막는다. 중첩 부착(부모·자식 양쪽)은 막지 못하고 안전 영역이 이중으로 축소되니 금지. - 성능:
Update()가 매 프레임Screen.safeArea를 폴링한다(SafeAreaFitter.cs:25-29). 값이 바뀔 때만Apply()하므로 비용은 비교 3회뿐이지만, 모바일 예산상 부착 수는 화면당 1개로 유지한다. - 미적용 기기 회귀 0:
Screen.safeArea가 전체 화면이면 min=(0,0)·max=(1,1)·offset 0 → 프리팹 저장값과 동일해 아무 변화가 없다(WL-802b ⓒ).
현재 부착 현황 (WL-802b 적용 후)
| 프리팹 | 경로 | 캔버스 직속 | 비고 |
|---|---|---|---|
TitleInfo.prefab |
TitleInfo/WL_SafeArea |
O | WL-802b 신규 · 빈 패널(자식 0 · Graphic 없음) · TitleInfo.prefab:833-882 |
SortOrder_5.prefab |
SortOrder_5/WL_SafeArea |
O | WL-802b 신규 · 빈 패널(자식 0 · Graphic 없음) · SortOrder_5.prefab:12908-12957 |
NewGameUI.prefab |
NewGameUI/IngameUIs/WL_HUD |
X | 기존(#800 계열) · 이번 태스크 무수정 · Fitter 블록 NewGameUI.prefab:107746-107759 |
WL802b_Probe.Dump 실측(2026-09-08): 세 파일 모두 SafeAreaFitter count = 1 · applyH=True applyV=True · 부착 RectTransform aMin=(0,0) aMax=(1,1) offMin=(0,0) offMax=(0,0) pivot=(0.5,0.5).
- 신규 패널은 비어 있다. 기존 자식을 재부모화하면 레이아웃 회귀(WL-802b ⓓ)가 나므로 이동시키지 않았다. 이 패널은 후속 UI 가 들어갈 안전 영역 레이어이고, 기존 자식을 옮기는 작업은 별도 발주(레이아웃 변경 승인 필요)로 분리한다.
NewGameUI의 기존 부착은 캔버스 직속이 아니라IngameUIs아래에 있다(NewGameUI.prefab:107739m_Father: {fileID: 2647977513349308266}=IngameUIs).IngameUIs는 프리팹 저장 상태에서 비활성(m_IsActive: 0)이므로, 런타임에 활성화되기 전에는 Fitter 도 돌지 않는다 → 「미확인」(런타임 활성화 시점 미검증). 파일당 1개(중복 0) 기준을 지키려 이번에는 옮기거나 추가하지 않았다.
⚠️ 남은 공백 — NewGameUI 최상위 레이어 (후속 발주 후보)
NewGameUI 캔버스 직속 자식은 12개다(실측 · 저장 상태 활성/비활성 표기):
New Image(비활성) · Camera · CamRot · Common · LobbyUIs · IngameUIs(비활성) · FarmUIs(비활성) · MessageInfo · Center · PopupMgr · TestMapUIMgr
기존 SafeAreaFitter 는 IngameUIs/WL_HUD 한 갈래만 덮는다. 즉 PopupMgr·MessageInfo·Common·LobbyUIs·Center 는 안전 영역 밖에 그대로 노출된다.
WL-802b 는 완료 기준 ⓑ(파일당 1 · 중복 0)와 ⓓ(기존 오브젝트 무변경) 때문에 이 프리팹을 수정하지 않았다.
→ 후속 발주 제안: 이 5개 레이어를 덮는 방식은 (가) 각 레이어를 새 WL_SafeArea 패널의 자식으로 재부모화(레이아웃 승인 필요) 또는 (나) 레이어별 부착(부착 수 증가 = 매 프레임 폴링 증가) 중 PD·Lead 결정 사항이다. 이 노트만으로 임의 채택하지 않는다.
6. NGUI 잔재 3종 — 실측과 대체 계획 (W2 Systems 후보 · 이번 태스크 무수정)
Assets/Script/Util/** 는 Systems 소유이자 WL-802b 금지 경로다. 아래는 읽기 전용 실측이다.
| 파일 | 부착 수(프리팹·씬 grep) | 세로 전용(#803)에서의 실동작 | 대체 |
|---|---|---|---|
Assets/Script/Util/SafeArea.cs |
0 | switch(Screen.orientation) 이 LandscapeLeft/LandscapeRight 만 처리(:20-37) → 세로에서는 어떤 분기도 타지 않음. 그런데 while(true) { … yield return null; }(:18-38) 은 계속 돈다 = 효과 0 · 매 프레임 코루틴 비용만 남음 |
삭제 (부착 0 → 무위험) |
Assets/Script/Util/UISafeAreaTopController.cs |
0 | NGUI UIRoot.manualHeight 의존. GetComponentInParent<UIRoot>() 가 null 이면 조기 return(:30-31) → uGUI 캔버스 아래에서는 no-op |
삭제 |
Assets/Script/Util/UISafeAreaRightController.cs |
1 — Assets/ResWork/UIPrefabs/Equip/EquipUI.prefab:52-64 (루트 EquipUI · BasePosisLeft: 1 · xMul: 1) |
MyCoroutine.Set_Repeat_for1sec 로 1초마다 실행(:17). m_cUIRoot 를 받아만 두고 쓰지 않아 null 이어도 조기 return 이 없다. localPosition.x 를 Screen.safeArea.y(= 세로 상단 인셋)만큼 민다(:28-30) — 가로 화면 전제의 축 오용. EquipUI.prefab 루트는 RectTransform(!u!224)이 아니라 Transform(!u!4) = NGUI 시절 프리팹 |
uGUI 재작성 후 SafeAreaFitter 로 교체 |
- 세로 전용 전환 후 이 3종은 **동작하지 않거나(0·0건) 축이 틀린 상태(1건)**다. 세로에서 노치 대응은 전부
WL.UI.SafeAreaFitter로 일원화한다. - W2 Systems 제안: ①
SafeArea.cs·UISafeAreaTopController.cs삭제(부착 0 실측) ②EquipUI.prefab을 uGUI 로 재작성할 때UISafeAreaRightController를 제거하고 상위WL_SafeArea패널의 자식으로 넣는다 ③ NGUIUIRoot를 쓰는 실사용 프리팹은BoxOpenUI·GetItemUI·ItemDescUI3종(나머지 23건은Assets/ThirdParty/NGUI/Examples/**) — 재작성 범위 산정에 사용.
7. 미확인 · 리스크
- 「미확인」
NewGameUI의IngameUIs가 런타임에 언제 활성화되는지 · 팝업/오버레이 레이어가WL_HUD밖에 따로 있는지(5.3 MB 프리팹 통째 Read 금지 · 부분 실측만 수행). - 「미확인」
Title.unity안 PopupUI 인스턴스와SortOrder_5.prefab의 관계(씬 수정·Play 금지 범위). - 「미확인」 실기 노치 인셋 값. QA 5종 캡처(#802 §3)는 시뮬레이터 프리셋이며 실제 기기
Screen.safeArea는 「추정」. - 「미확인」 세로 전용 자동회전이 실기 빌드에서 적용되는지(빌드 미수행).