// WL802b_Probe.cs — #802 재단 2차 검증 덤프: CanvasScaler 유닛 스케일 + SafeAreaFitter 부착/앵커 실측 (에디트 모드 · Play 불필요) // unity command run_script --file AgentScripts/WL802b_Probe.cs --entry WL802b_Probe.Dump // 산출: ⓑ 파일당 SafeAreaFitter 개수·경로 · ⓒ 부착 RectTransform 앵커/offset + safeArea=전체화면 시뮬레이션 · ⓓ #806 팝업 값 · ⓔ 환산표. using System.Text; using UnityEditor; using UnityEngine; using UnityEngine.UI; public static class WL802b_Probe { static readonly string[] Prefabs = { "Assets/ResWork/UIPrefabs/Title/TitleInfo.prefab", "Assets/ResWork/UIPrefabs/Title/SortOrder_5.prefab", "Assets/Res_Addr/MainUI/NewGameUI.prefab", }; // Lead 확정 QA 캡처 해상도 5종 (§3) static readonly Vector2Int[] QaRes = { new Vector2Int(1080, 1920), new Vector2Int(1080, 2340), new Vector2Int(1080, 2400), new Vector2Int(720, 1600), new Vector2Int(1440, 3200), }; // CanvasScaler.ScreenMatchMode.Expand 의 실제 계산식 (UnityEngine.UI 소스와 동일) static float ExpandScale(Vector2 refRes, Vector2Int screen) { return Mathf.Min(screen.x / refRes.x, screen.y / refRes.y); } static string Path(Transform t, Transform stop) { var s = t.name; for (var p = t.parent; p != null && p != stop; p = p.parent) s = p.name + "/" + s; return s; } static string Rt(RectTransform rt) { return "aMin=" + rt.anchorMin + " aMax=" + rt.anchorMax + " offMin=" + rt.offsetMin + " offMax=" + rt.offsetMax + " size=" + rt.sizeDelta + " pos=" + rt.anchoredPosition + " pivot=" + rt.pivot; } // SafeAreaFitter.Apply() 와 동일한 산식 — Play 없이 결과를 재현한다. static string SimulateFitter(Rect safe, Vector2Int screen) { var min = new Vector2(safe.xMin / screen.x, safe.yMin / screen.y); var max = new Vector2(safe.xMax / screen.x, safe.yMax / screen.y); return "screen=" + screen.x + "x" + screen.y + " safeArea=" + safe + " → anchorMin=" + min + " anchorMax=" + max + " offsetMin=(0,0) offsetMax=(0,0)"; } static RectTransform FindByName(GameObject root, string name) { foreach (var rt in root.GetComponentsInChildren(true)) if (rt.name == name) return rt; return null; } public static object Dump() { var sb = new StringBuilder(); sb.AppendLine("== WL-802b Probe =="); foreach (var path in Prefabs) { var root = PrefabUtility.LoadPrefabContents(path); try { sb.AppendLine(); sb.AppendLine("### " + path + " (root=" + root.name + ")"); // --- CanvasScaler --- foreach (var cs in root.GetComponentsInChildren(true)) { sb.AppendLine(" CanvasScaler @" + Path(cs.transform, root.transform.parent) + " mode=" + cs.uiScaleMode + " ref=" + cs.referenceResolution + " match=" + cs.screenMatchMode + " matchWH=" + cs.matchWidthOrHeight + " refPPU=" + cs.referencePixelsPerUnit); foreach (var r in QaRes) sb.AppendLine(" scaleFactor " + r.x + "x" + r.y + " = " + ExpandScale(cs.referenceResolution, r).ToString("0.#####") + " (1 px = " + (1f / ExpandScale(cs.referenceResolution, r)).ToString("0.####") + " unit)"); } // --- SafeAreaFitter --- var fitters = root.GetComponentsInChildren(true); sb.AppendLine(" SafeAreaFitter count = " + fitters.Length); foreach (var f in fitters) { var so = new SerializedObject(f); var rt = (RectTransform)f.transform; bool activeChain = true; for (var t = f.transform; t != null; t = t.parent) if (!t.gameObject.activeSelf) { activeChain = false; break; } sb.AppendLine(" @" + Path(f.transform, root.transform.parent) + " parent=" + (f.transform.parent != null ? f.transform.parent.name : "(none)") + " canvasDirectChild=" + (f.transform.parent == root.transform) + " activeInPrefab=" + activeChain + " applyH=" + so.FindProperty("applyHorizontal").boolValue + " applyV=" + so.FindProperty("applyVertical").boolValue); sb.AppendLine(" RectTransform: " + Rt(rt)); sb.AppendLine(" graphic=" + (rt.GetComponent() != null) + " childCount=" + rt.childCount); } // --- #806 팝업 값 (레이아웃 회귀 확인) --- if (path.EndsWith("SortOrder_5.prefab")) { foreach (var n in new[] { "PopupUI", "LoadingUI" }) { var p = FindByName(root, n); if (p == null) { sb.AppendLine(" [" + n + "] 없음"); continue; } sb.AppendLine(" [" + n + "] " + Rt(p)); foreach (var c in p.GetComponentsInChildren(true)) if (c != p && (c.name == "bg" || c.name == "msg" || c.name == "btn_ok")) sb.AppendLine(" " + c.name + " " + Rt(c)); } } } finally { PrefabUtility.UnloadPrefabContents(root); } } // --- ⓒ safeArea = 전체 화면일 때 회귀 0 --- sb.AppendLine(); sb.AppendLine("### SafeAreaFitter.Apply 시뮬레이션 (Play 없이 산식 재현)"); foreach (var r in QaRes) sb.AppendLine(" 미적용기기: " + SimulateFitter(new Rect(0, 0, r.x, r.y), r)); sb.AppendLine(" 노치예시(추정): " + SimulateFitter(new Rect(0, 0, 1080, 2400 - 91), new Vector2Int(1080, 2400))); // --- ⓔ 환산표 --- sb.AppendLine(); sb.AppendLine("### 유닛 ↔ px 환산 (세로 1080 폭 기준 · Expand)"); sb.AppendLine(" ref 1080x1920: scale 1.0 → 1 unit = 1 px"); sb.AppendLine(" ref 1920x1080: scale 0.5625 → 1 px = 1.7778 unit"); foreach (var px in new[] { 44f, 88f, 130f, 340f }) sb.AppendLine(" " + px + " px = " + px + " unit(1080기준) = " + (px / 0.5625f).ToString("0.##") + " unit(1920기준)"); sb.AppendLine(); sb.AppendLine("Screen(batch) = " + Screen.width + "x" + Screen.height + " safeArea=" + Screen.safeArea + " (배치모드 값 — 실기 아님)"); return sb.ToString(); } }