// WL802b_Apply.cs — #802 재단 2차: 세로 Safe Area 를 타이틀·로딩/팝업 프리팹까지 공통 적용 (에디트 모드 · 프리팹 컨텐츠 · Play 불필요) // unity command run_script --file AgentScripts/WL802b_Apply.cs --entry WL802b_Apply.Resave (무변경 재저장 — 재직렬화 diff 측정용) // unity command run_script --file AgentScripts/WL802b_Apply.cs --entry WL802b_Apply.Apply // unity command run_script --file AgentScripts/WL802b_Apply.cs --entry WL802b_Apply.Revert (신규 패널 제거 = 2026-09-07 원상) // 방식: 각 프리팹 캔버스 루트 직속에 **전용 루트 패널**(Graphic 없음 · 스트레치 · offset 0)을 1개 추가하고 WL.UI.SafeAreaFitter 를 붙인다. // 기존 자식은 재부모화하지 않는다(#806 팝업 값 포함 레이아웃 회귀 0). 패널은 후속 UI 가 들어갈 안전 영역 레이어다. // NewGameUI.prefab 은 WL_HUD 에 이미 SafeAreaFitter 가 있어 대상에서 제외한다(파일당 1개 · 중복 0). // 롤백: 이 스크립트의 Revert() 또는 git checkout -- <프리팹 2종> using System.Collections.Generic; using System.Text; using UnityEditor; using UnityEngine; public static class WL802b_Apply { // 이번 웨이브 UI 단독 소유 핫스팟 2종 (NewGameUI 는 기존 부착 유지 → 대상 아님) static readonly string[] Targets = { "Assets/ResWork/UIPrefabs/Title/TitleInfo.prefab", "Assets/ResWork/UIPrefabs/Title/SortOrder_5.prefab", }; public const string PanelName = "WL_SafeArea"; const int UILayer = 5; static RectTransform FindPanel(GameObject root) { var t = root.transform.Find(PanelName); return t as RectTransform; } static string Describe(RectTransform rt) { return "aMin=" + rt.anchorMin + " aMax=" + rt.anchorMax + " offMin=" + rt.offsetMin + " offMax=" + rt.offsetMax + " size=" + rt.sizeDelta + " pos=" + rt.anchoredPosition + " pivot=" + rt.pivot + " scale=" + rt.localScale; } public static object Apply() { var sb = new StringBuilder(); foreach (var path in Targets) { var root = PrefabUtility.LoadPrefabContents(path); try { if (FindPanel(root) != null) { sb.AppendLine(path + " : 이미 " + PanelName + " 있음 — 건너뜀"); continue; } var go = new GameObject(PanelName, typeof(RectTransform)); go.layer = UILayer; var rt = (RectTransform)go.transform; rt.SetParent(root.transform, false); // 마지막 자식 = 최상위 시블링(빈 패널이라 렌더 영향 0) rt.localScale = Vector3.one; rt.localRotation = Quaternion.identity; rt.pivot = new Vector2(0.5f, 0.5f); rt.anchorMin = Vector2.zero; // 스트레치 rt.anchorMax = Vector2.one; rt.offsetMin = Vector2.zero; // offset 0 (= sizeDelta 0 · anchoredPosition 0) rt.offsetMax = Vector2.zero; go.AddComponent(); // applyHorizontal/applyVertical 기본 true PrefabUtility.SaveAsPrefabAsset(root, path); sb.AppendLine(path + " : " + PanelName + " 추가 · SafeAreaFitter 부착 · " + Describe(rt)); } finally { PrefabUtility.UnloadPrefabContents(root); } } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); return sb.ToString(); } public static object Revert() { var sb = new StringBuilder(); foreach (var path in Targets) { var root = PrefabUtility.LoadPrefabContents(path); try { var panel = FindPanel(root); if (panel == null) { sb.AppendLine(path + " : " + PanelName + " 없음 — 건너뜀"); continue; } Object.DestroyImmediate(panel.gameObject); PrefabUtility.SaveAsPrefabAsset(root, path); sb.AppendLine(path + " : " + PanelName + " 제거"); } finally { PrefabUtility.UnloadPrefabContents(root); } } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); return sb.ToString(); } /// 내용 변경 없이 LoadPrefabContents→SaveAsPrefabAsset 만 수행 — 재직렬화가 diff 를 만드는지 측정한다(완료 기준 ⓓ 사전 확인). public static object Resave() { var sb = new StringBuilder(); var only = new List(Targets); foreach (var path in only) { var root = PrefabUtility.LoadPrefabContents(path); try { PrefabUtility.SaveAsPrefabAsset(root, path); sb.AppendLine(path + " : 무변경 재저장"); } finally { PrefabUtility.UnloadPrefabContents(root); } } AssetDatabase.Refresh(); return sb.ToString(); } }