Project_WL/Assets/WL/UI/UI_UnitScale_SafeArea_Note.md

11 KiB
Raw Blame History

WL UI 기준 노트 — CanvasScaler 유닛 스케일 · Safe Area 공통 적용

WL-802b (#802 재단 2차) · 2026-09-08 · 브랜치 wl/ui/WL-802b-safearea-scale (base main @ 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.prefabPopupUI/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.prefab1080×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).

반드시 지킬 것

  1. 전용 루트 패널에만 붙인다. 캔버스 직속에 Graphic 없는 빈 패널(WL_SafeArea)을 두고 거기에만 부착한다. 안전 영역을 따라야 하는 UI 는 이 패널의 자식으로 넣는다.
  2. 이미 스트레치 + offset 을 쓰는 패널에는 붙이지 않는다. Apply() 가 offsetMin/Max 를 무조건 0 으로 덮어쓰므로, 여백(offset)으로 레이아웃을 잡아둔 패널에 붙이면 그 여백이 소실된다. 같은 이유로 LayoutGroup/ContentSizeFitter 가 자기 RectTransform 을 제어하는 오브젝트에도 붙이지 않는다.
  3. 프리팹당 1개. [DisallowMultipleComponent] 는 같은 오브젝트의 중복만 막는다. 중첩 부착(부모·자식 양쪽)은 막지 못하고 안전 영역이 이중으로 축소되니 금지.
  4. 성능: Update() 가 매 프레임 Screen.safeArea 를 폴링한다(SafeAreaFitter.cs:25-29). 값이 바뀔 때만 Apply() 하므로 비용은 비교 3회뿐이지만, 모바일 예산상 부착 수는 화면당 1개로 유지한다.
  5. 미적용 기기 회귀 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:107739 m_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

기존 SafeAreaFitterIngameUIs/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 1Assets/ResWork/UIPrefabs/Equip/EquipUI.prefab:52-64 (루트 EquipUI · BasePosisLeft: 1 · xMul: 1) MyCoroutine.Set_Repeat_for1sec1초마다 실행(:17). m_cUIRoot 를 받아만 두고 쓰지 않아 null 이어도 조기 return 이 없다. localPosition.xScreen.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 패널의 자식으로 넣는다 ③ NGUI UIRoot 를 쓰는 실사용 프리팹은 BoxOpenUI·GetItemUI·ItemDescUI 3종(나머지 23건은 Assets/ThirdParty/NGUI/Examples/**) — 재작성 범위 산정에 사용.

7. 미확인 · 리스크

  • 「미확인」 NewGameUIIngameUIs 가 런타임에 언제 활성화되는지 · 팝업/오버레이 레이어가 WL_HUD 밖에 따로 있는지(5.3 MB 프리팹 통째 Read 금지 · 부분 실측만 수행).
  • 「미확인」 Title.unity 안 PopupUI 인스턴스와 SortOrder_5.prefab 의 관계(씬 수정·Play 금지 범위).
  • 「미확인」 실기 노치 인셋 값. QA 5종 캡처(#802 §3)는 시뮬레이터 프리셋이며 실제 기기 Screen.safeArea「추정」.
  • 「미확인」 세로 전용 자동회전이 실기 빌드에서 적용되는지(빌드 미수행).