// WL796_Apply.cs — PD 지시 #796 적용 (에디트 모드 전용 · 프리팹/설정 에셋 수정) // unity command run_script --file AgentScripts/WL796_Apply.cs --entry WL796_Apply.ApplyAll // unity command run_script --file AgentScripts/WL796_Apply.cs --entry WL796_Apply.Report // // 1) NewGameUI.prefab / IngameUIs/WL_HUD/HUD_BottomMenu // - 바 폭 = 캔버스 폭(1920 단위) / 스케일 1.55 = 1238.71 → 좌우를 가득 채운다 // - 바 바닥 y = 10 단위(≈5.6px) — 가운데 전투 버튼의 ProgressText 가 -5.9 단위까지 내려가 잘리지 않게 하는 최소 여유 // - 버튼 5개: 셀 중심 앵커(0.1/0.3/0.5/0.7/0.9 — 원본 그대로) · 셀 폭 = 바폭/5 - 간격 2 // - 일반 버튼 높이 140 → 200 (아이콘이 rect 위로 42 단위 튀어나와 터치가 안 되던 것을 rect 안으로 넣는다) // - 자식은 "부모 피벗(하단 중앙) 기준 localPosition" 을 기록/복원해 시각적 위치를 그대로 유지한다 // - BottomBarFitter 부착(해상도·Safe Area 변화 시 런타임 재계산) // 2) WLGameplaySettings.asset : hideIngameChat = true · ingameHideCanvasGroupPaths = [MessageInfo/Chat] // // 🔴 Play 중 실행 금지(에셋을 디스크에 쓴다). 백업 = 공유/개발팀_백업/WL/Hud2_20260907_0143/ using System.Collections.Generic; using System.Text; using UnityEditor; using UnityEngine; // WL.Settings / WL.UI 를 using 하지 않는다 — run_script 어셈블리가 Assembly-CSharp 최신 스냅샷을 못 볼 수 있어 // 새로 추가한 타입·필드는 전부 SerializedObject/리플렉션으로 접근한다. public static class WL796_Apply { const string PrefabPath = "Assets/Res_Addr/MainUI/NewGameUI.prefab"; const string MenuPath = "IngameUIs/WL_HUD/HUD_BottomMenu"; // 캔버스 폭(세로 화면에서 항상 1920 단위) / HUD_BottomMenu localScale.x(1.55) const float CanvasWidthUnits = 1920f; const float BarBottomY = 10f; // Safe Area 바닥에서 띄우는 최소 여유(단위) const float BarHeight = 280f; // 244 px @1080x1920 — 사양 230~260 px 창 안 const float CellGap = 2f; // ≈1.1 px — 인접 사각형 겹침 0 보장 const float NormalButtonHeight = 200f; /// WL.UI.BottomBarFitter 타입을 로드된 어셈블리에서 찾는다(컴파일 타임 참조 회피). static System.Type FitterType() { var t = System.Type.GetType("WL.UI.BottomBarFitter, Assembly-CSharp"); if (t != null) return t; foreach (var asm in System.AppDomain.CurrentDomain.GetAssemblies()) { t = asm.GetType("WL.UI.BottomBarFitter"); if (t != null) return t; } return null; } static Transform FindByPath(Transform root, string path) { var cur = root; foreach (var raw in path.Split('/')) { var name = raw.Trim(); if (name.Length == 0) continue; Transform next = null; for (int c = 0; c < cur.childCount; c++) if (cur.GetChild(c).name == name) { next = cur.GetChild(c); break; } if (next == null) return null; cur = next; } return cur; } public static object ApplyAll() { if (EditorApplication.isPlaying) return "ABORT: Play 중에는 실행하지 않는다(에셋 디스크 쓰기)."; var sb = new StringBuilder(); sb.AppendLine(ApplyPrefabInternal()); sb.AppendLine(ApplySettingsInternal()); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); return sb.ToString(); } static string ApplyPrefabInternal() { var sb = new StringBuilder(); var root = PrefabUtility.LoadPrefabContents(PrefabPath); if (root == null) return "prefab load 실패: " + PrefabPath; try { var menuT = FindByPath(root.transform, MenuPath); if (menuT == null) { return "HUD_BottomMenu 없음: " + MenuPath; } var menu = menuT as RectTransform; float scale = menu.localScale.x; float barWidth = CanvasWidthUnits / scale; int n = menu.childCount; if (n <= 0) return "버튼 없음"; int center = n / 2; float cellW = barWidth / n; sb.AppendLine("[before] bar sizeDelta=" + menu.sizeDelta + " anchoredPos=" + menu.anchoredPosition + " scale=" + scale); // ── 1) 바 ──────────────────────────────────────────────────────── menu.sizeDelta = new Vector2(barWidth, BarHeight); menu.anchoredPosition = new Vector2(0f, BarBottomY); // ── 2) 버튼 5개 ────────────────────────────────────────────────── for (int i = 0; i < n; i++) { var b = menu.GetChild(i) as RectTransform; if (b == null) continue; // 자식들의 "버튼 피벗(하단 중앙) 기준" 위치를 기록 → rect 를 키운 뒤 그대로 복원한다. var keep = new List>(); for (int c = 0; c < b.childCount; c++) keep.Add(new KeyValuePair(b.GetChild(c), b.GetChild(c).localPosition)); var before = b.sizeDelta; float mid = (i + 0.5f) / n; var aMin = b.anchorMin; var aMax = b.anchorMax; aMin.x = mid; aMax.x = mid; b.anchorMin = aMin; b.anchorMax = aMax; var ap = b.anchoredPosition; ap.x = 0f; b.anchoredPosition = ap; float h = (i == center) ? b.sizeDelta.y : NormalButtonHeight; b.sizeDelta = new Vector2(cellW - CellGap, h); foreach (var kv in keep) kv.Key.localPosition = kv.Value; // 시각 위치 그대로 sb.AppendLine(" " + b.name + " : size " + before + " -> " + b.sizeDelta + " anchorX=" + mid.ToString("F2") + " children=" + keep.Count); } // ── 3) 런타임 보정 컴포넌트 ────────────────────────────────────── // run_script 어셈블리가 Assembly-CSharp 의 최신 스냅샷을 못 볼 수 있어 타입 참조 대신 리플렉션으로 붙인다. var fitterType = FitterType(); if (fitterType == null) sb.AppendLine(" ⚠ BottomBarFitter 타입 없음 — 프리팹 값만 적용(런타임 보정 미부착)"); else { var fitter = menu.GetComponent(fitterType); if (fitter == null) { fitter = menu.gameObject.AddComponent(fitterType); sb.AppendLine(" BottomBarFitter 추가"); } else sb.AppendLine(" BottomBarFitter 이미 있음"); var so = new SerializedObject(fitter); so.FindProperty("fitWidthToParent").boolValue = true; so.FindProperty("centerCellMultiplier").floatValue = 1f; // 옵션 A(균등 5등분) 기본 so.FindProperty("cellGap").floatValue = CellGap; so.FindProperty("uniformButtonHeight").floatValue = 0f; // 프리팹 높이 유지 so.ApplyModifiedPropertiesWithoutUndo(); } sb.AppendLine("[after] bar sizeDelta=" + menu.sizeDelta + " anchoredPos=" + menu.anchoredPosition + " cellW=" + cellW.ToString("F2") + " (px@1080=" + (cellW * scale * 0.5625f).ToString("F1") + ")"); PrefabUtility.SaveAsPrefabAsset(root, PrefabPath); sb.AppendLine("saved prefab"); } finally { PrefabUtility.UnloadPrefabContents(root); } return sb.ToString(); } const string SettingsPath = "Assets/WL/Settings/Resources/WL/WLGameplaySettings.asset"; static string ApplySettingsInternal() { var s = AssetDatabase.LoadAssetAtPath(SettingsPath); if (s == null) return "WLGameplaySettings.asset 없음"; var so = new SerializedObject(s); var p = so.FindProperty("hideIngameChat"); var arr = so.FindProperty("ingameHideCanvasGroupPaths"); if (p == null || arr == null) return "⚠ 설정 필드 미인식(WLGameplaySettings.cs 컴파일 반영 대기) — hideIngameChat/ingameHideCanvasGroupPaths 미기록"; p.boolValue = true; arr.arraySize = 1; arr.GetArrayElementAtIndex(0).stringValue = "MessageInfo/Chat"; so.ApplyModifiedPropertiesWithoutUndo(); EditorUtility.SetDirty(s); return "settings: hideIngameChat=true paths=[MessageInfo/Chat]"; } /// 적용 결과 확인(프리팹 값 · 설정 에셋 값). public static object Report() { var sb = new StringBuilder(); var go = AssetDatabase.LoadAssetAtPath(PrefabPath); var menu = go != null ? FindByPath(go.transform, MenuPath) as RectTransform : null; if (menu == null) return "HUD_BottomMenu 없음"; float scale = menu.localScale.x, k = scale * 0.5625f; // 1080x1920 기준 px 환산 sb.AppendLine("bar sizeDelta=" + menu.sizeDelta + " anchoredPos=" + menu.anchoredPosition + " scale=" + scale + " => px w=" + (menu.sizeDelta.x * k).ToString("F0") + " h=" + (menu.sizeDelta.y * k).ToString("F0") + " bottom=" + (menu.anchoredPosition.y * 0.5625f).ToString("F0")); var ft = FitterType(); sb.AppendLine("fitter=" + (ft != null && menu.GetComponent(ft) != null)); for (int i = 0; i < menu.childCount; i++) { var b = menu.GetChild(i) as RectTransform; sb.AppendLine(" " + b.name + " anchorX=" + b.anchorMin.x.ToString("F2") + " size=" + b.sizeDelta + " => px w=" + (b.sizeDelta.x * k).ToString("F0") + " h=" + (b.sizeDelta.y * k).ToString("F0")); } var s = AssetDatabase.LoadAssetAtPath(SettingsPath); if (s != null) { var so2 = new SerializedObject(s); var p = so2.FindProperty("hideIngameChat"); var arr = so2.FindProperty("ingameHideCanvasGroupPaths"); var pad = so2.FindProperty("hideBattlePad"); var paths = new List(); if (arr != null) for (int i = 0; i < arr.arraySize; i++) paths.Add(arr.GetArrayElementAtIndex(i).stringValue); sb.AppendLine("settings hideIngameChat=" + (p != null ? p.boolValue.ToString() : "(필드없음)") + " paths=[" + string.Join(",", paths.ToArray()) + "]" + " hideBattlePad=" + (pad != null ? pad.boolValue.ToString() : "?")); } return sb.ToString(); } /// 옵션 B(가운데 셀 1.3배)로 전환. 프리팹의 앵커·폭을 다시 계산한다. public static object SetCenterMultiplier(float mul) { if (EditorApplication.isPlaying) return "ABORT: Play 중"; var root = PrefabUtility.LoadPrefabContents(PrefabPath); if (root == null) return "prefab load 실패"; var sb = new StringBuilder(); try { var menu = FindByPath(root.transform, MenuPath) as RectTransform; if (menu == null) return "HUD_BottomMenu 없음"; int n = menu.childCount, center = n / 2; float barWidth = menu.sizeDelta.x; float total = 0f; for (int i = 0; i < n; i++) total += (i == center ? mul : 1f); float cursor = 0f; for (int i = 0; i < n; i++) { var b = menu.GetChild(i) as RectTransform; float w = (i == center ? mul : 1f) / total; float mid = cursor + w * 0.5f; cursor += w; var keep = new List>(); for (int c = 0; c < b.childCount; c++) keep.Add(new KeyValuePair(b.GetChild(c), b.GetChild(c).localPosition)); var aMin = b.anchorMin; var aMax = b.anchorMax; aMin.x = mid; aMax.x = mid; b.anchorMin = aMin; b.anchorMax = aMax; var ap = b.anchoredPosition; ap.x = 0f; b.anchoredPosition = ap; b.sizeDelta = new Vector2(barWidth * w - CellGap, b.sizeDelta.y); foreach (var kv in keep) kv.Key.localPosition = kv.Value; sb.AppendLine(" " + b.name + " anchorX=" + mid.ToString("F3") + " w=" + b.sizeDelta.x.ToString("F1")); } var ft = FitterType(); var fitter = ft != null ? menu.GetComponent(ft) : null; if (fitter != null) { var so = new SerializedObject(fitter); so.FindProperty("centerCellMultiplier").floatValue = mul; so.ApplyModifiedPropertiesWithoutUndo(); } PrefabUtility.SaveAsPrefabAsset(root, PrefabPath); } finally { PrefabUtility.UnloadPrefabContents(root); } AssetDatabase.SaveAssets(); return "centerCellMultiplier=" + mul + "\n" + sb; } }