// ───────────────────────────────────────────────────────────────────────────── // WLIngameUiOverride.cs — 인게임 진입 시 힘민지 UI 위에 WL 규칙을 덧씌우는 런타임 훅 // // PD 지시 #775 (2026-09-06): // "힘민지 프로젝트 우측 하단 공격 버튼 및 스킬 아이콘 버튼은 추후 변경해서 사용할 예정이므로 // 지금은 보이지 않게 숨기고, 포트레이트 뷰에 맞게 WL 프로젝트 UI를 배치해줘." // // 설계: // - 프리팹을 편집하지 않는다. NewGameUI.SetUI_byGameMode 가 화면을 구성한 "직후"에 // SetActive 만 바꾼다 → 설정 스위치를 끄면 다음 진입에서 즉시 원복(C8 롤백 경로). // - 무엇을 끄고 켤지는 코드 상수가 아니라 WLGameplaySettings 에셋에 둔다(C45). // - 인게임(Stage·Dungeon)에서만 끄고, 로비로 돌아오면 반드시 되돌린다 — 로비 화면 무손상. // // 실측(2026-09-06 · NewGameUI.prefab): // - 인게임 전투 패드 = NewGameUI/IngameUIs/BattleUI (BattleUI.prefab 인스턴스) // 자식 = Attack(235x235) · Avoid(90x90) · Equip · btn_auto(61x61) · SkillCard x6 // - 같은 이름 BattleUI 인스턴스가 Center/SkillUI/SkillEquipUI 와 SkillLayout 아래에도 있다 // → 이름 검색이 아니라 "루트 기준 경로"로만 찾는다. // // 🔴 어셈블리 주의: 힘민지 Assets/Script/ 에는 .asmdef 가 없다(전부 Assembly-CSharp). // 이 파일도 Assembly-CSharp 에 속한다 — Assets/WL/UI/ 에 .asmdef 를 만들지 말 것. // ───────────────────────────────────────────────────────────────────────────── using System.Collections.Generic; using System.Text; using UnityEngine; using WL.Settings; namespace WL.UI { public static class WLIngameUiOverride { // 마지막으로 적용한 상태 — 런타임 토글(검증·디버그)에서 같은 조건으로 다시 적용하기 위해 캐시한다. private static NewGameUI s_ui; private static eGameMode s_mode; private static bool s_hasApplied; /// 인게임 진입/모드 전환 직후 1회 호출. NewGameUI.SetUI_byGameMode 끝에서 부른다. public static void Apply(NewGameUI ui, eGameMode gameMode) { if (ui == null) return; s_ui = ui; s_mode = gameMode; s_hasApplied = true; bool ingame = gameMode == eGameMode.Stage || gameMode == eGameMode.Dungeon; ApplyBattlePad(ui, ingame); ApplyCommonHide(ui, ingame); ApplyCanvasGroupHide(ui, ingame); } // ── 채팅창 등 "끄면 안 되는" UI 가리기 (#796) ────────────────────────── // SetActive 대신 CanvasGroup 만 만진다 — 대상 스크립트의 Awake/Start/Update 가 계속 돌아야 하기 때문. // (ChatUI 는 Awake 에서 밴 목록을 로드한다. 꺼 두면 채팅 수신 시 NullReferenceException.) private static void ApplyCanvasGroupHide(NewGameUI ui, bool ingame) { var s = WLGameplaySettings.Instance; string[] paths = (s != null && s.ingameHideCanvasGroupPaths != null) ? s.ingameHideCanvasGroupPaths : null; if (paths == null || paths.Length == 0) return; bool hide = ingame && WLGameplaySettings.HideIngameChat; for (int i = 0; i < paths.Length; i++) { var t = FindByPath(ui.transform, paths[i]); if (t == null) continue; var cg = t.GetComponent(); if (cg == null) { if (!hide) continue; // 가릴 필요가 없으면 컴포넌트를 만들지 않는다 cg = t.gameObject.AddComponent(); } float alpha = hide ? 0f : 1f; if (!Mathf.Approximately(cg.alpha, alpha)) cg.alpha = alpha; if (cg.blocksRaycasts == hide) cg.blocksRaycasts = !hide; if (cg.interactable == hide) cg.interactable = !hide; } } // ── 우측 하단 전투 패드 ─────────────────────────────────────────────── private static void ApplyBattlePad(NewGameUI ui, bool ingame) { var s = WLGameplaySettings.Instance; string[] paths = (s != null && s.battlePadPaths != null) ? s.battlePadPaths : null; if (paths == null || paths.Length == 0) return; // 인게임이 아니면 패드는 애초에 IngameUIs 째로 꺼져 있다 — // 그래도 스위치를 끈 뒤 원복되도록 "인게임에서만" 상태를 만진다. if (!ingame) return; bool hide = WLGameplaySettings.HideBattlePad; for (int i = 0; i < paths.Length; i++) { var t = FindByPath(ui.transform, paths[i]); if (t == null) continue; if (t.gameObject.activeSelf == !hide) continue; t.gameObject.SetActive(!hide); } } // ── 힘민지 공용 UI(Common) 중 WL HUD 와 기능이 겹치는 것 ─────────────── private static void ApplyCommonHide(NewGameUI ui, bool ingame) { var s = WLGameplaySettings.Instance; string[] names = (s != null && s.ingameHideCommonObjectNames != null) ? s.ingameHideCommonObjectNames : null; if (names == null || names.Length == 0) return; // gos[0] = 공용 UI 묶음(Common). 인덱스는 NewGameUI 인스펙터 주석 그대로다. var common = (ui.gos != null && ui.gos.Length > 0) ? ui.gos[0] : null; if (common == null) return; for (int i = 0; i < names.Length; i++) { if (string.IsNullOrEmpty(names[i])) continue; var t = common.transform.Find(names[i]); if (t == null) continue; bool want = !ingame; // 인게임이면 끄고, 로비/농장이면 켠다 if (t.gameObject.activeSelf == want) continue; t.gameObject.SetActive(want); } } // ── 유틸 ────────────────────────────────────────────────────────────── /// /// 비활성 자식까지 따라가는 경로 탐색("A/B/C"). Transform.Find 와 달리 구분자 공백을 허용한다. /// 813tj 가 NewGameUI 캔버스 직속 5레이어를 `WL_SafeArea_*` 패널 아래로 옮겼으므로, /// 한 단계를 못 찾으면 Safe Area 패널을 한 겹 건너뛰고 다시 본다 /// (설정 에셋의 옛 경로 문자열 "MessageInfo/Chat" 이 그대로 살아 있어야 한다 = 회귀 0). /// private static Transform FindByPath(Transform root, string path) { return WLVignetteUtil.FindUiPath(root, path); } // ── 검증·디버그 전용 ────────────────────────────────────────────────── /// 마지막 적용 조건으로 다시 적용한다(설정을 런타임에 바꾼 뒤 호출). public static string ReapplyAll() { if (!s_hasApplied || s_ui == null) return "(아직 SetUI_byGameMode 가 불리지 않았다)"; Apply(s_ui, s_mode); return "reapplied mode=" + s_mode + " hideBattlePad=" + WLGameplaySettings.HideBattlePad; } /// /// 검증용 런타임 토글. **로드된 설정 인스턴스만** 바꾸므로 .asset 파일은 건드리지 않는다 /// (Play 를 끝내면 원래 값으로 돌아온다). /// public static string SetHideBattlePad(bool hide) { var s = WLGameplaySettings.Instance; if (s == null) return "WLGameplaySettings 에셋 없음"; s.hideBattlePad = hide; return ReapplyAll(); } /// 현재 상태 덤프(경로 · activeSelf · activeInHierarchy). public static string Dump() { var sb = new StringBuilder(); var s = WLGameplaySettings.Instance; sb.AppendLine("hideBattlePad=" + (s != null ? s.hideBattlePad.ToString() : "(에셋없음)") + " lastMode=" + (s_hasApplied ? s_mode.ToString() : "-")); var ui = s_ui != null ? s_ui : NewGameUI.Ins; if (ui == null) { sb.AppendLine("NewGameUI 없음"); return sb.ToString(); } var paths = (s != null && s.battlePadPaths != null) ? s.battlePadPaths : new string[0]; foreach (var p in paths) { var t = FindByPath(ui.transform, p); if (t == null) { sb.AppendLine(" [pad] " + p + " : 없음"); continue; } sb.AppendLine(" [pad] " + p + " activeSelf=" + t.gameObject.activeSelf + " activeInHierarchy=" + t.gameObject.activeInHierarchy + " children=" + t.childCount); foreach (Transform c in t) sb.AppendLine(" - " + c.name + " activeInHierarchy=" + c.gameObject.activeInHierarchy); } var common = (ui.gos != null && ui.gos.Length > 0) ? ui.gos[0] : null; var names = (s != null && s.ingameHideCommonObjectNames != null) ? s.ingameHideCommonObjectNames : new string[0]; if (common != null) { foreach (var n in names) { var t = common.transform.Find(n); sb.AppendLine(" [common] " + n + (t == null ? " : 없음" : " activeSelf=" + t.gameObject.activeSelf + " activeInHierarchy=" + t.gameObject.activeInHierarchy)); } sb.AppendLine(" [common] 유지 대상: " + KeepList(common.transform, names)); } return sb.ToString(); } private static string KeepList(Transform common, string[] hidden) { var hide = new HashSet(hidden ?? new string[0]); var sb = new StringBuilder(); foreach (Transform c in common) { if (hide.Contains(c.name)) continue; if (sb.Length > 0) sb.Append(", "); sb.Append(c.name).Append('(').Append(c.gameObject.activeInHierarchy ? "on" : "off").Append(')'); } return sb.ToString(); } } }