Project_WL/Assets/WL/UI/Scripts/WLIngameUiOverride.cs

217 lines
11 KiB
C#

// ─────────────────────────────────────────────────────────────────────────────
// 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;
/// <summary>인게임 진입/모드 전환 직후 1회 호출. NewGameUI.SetUI_byGameMode 끝에서 부른다.</summary>
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<CanvasGroup>();
if (cg == null)
{
if (!hide) continue; // 가릴 필요가 없으면 컴포넌트를 만들지 않는다
cg = t.gameObject.AddComponent<CanvasGroup>();
}
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);
}
}
// ── 유틸 ──────────────────────────────────────────────────────────────
/// <summary>비활성 자식까지 따라가는 경로 탐색("A/B/C"). Transform.Find 와 달리 구분자 공백을 허용한다.</summary>
private static Transform FindByPath(Transform root, string path)
{
if (root == null || string.IsNullOrEmpty(path)) return null;
var cur = root;
var parts = path.Split('/');
for (int i = 0; i < parts.Length; i++)
{
var name = parts[i].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;
}
// ── 검증·디버그 전용 ──────────────────────────────────────────────────
/// <summary>마지막 적용 조건으로 다시 적용한다(설정을 런타임에 바꾼 뒤 호출).</summary>
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;
}
/// <summary>
/// 검증용 런타임 토글. **로드된 설정 인스턴스만** 바꾸므로 .asset 파일은 건드리지 않는다
/// (Play 를 끝내면 원래 값으로 돌아온다).
/// </summary>
public static string SetHideBattlePad(bool hide)
{
var s = WLGameplaySettings.Instance;
if (s == null) return "WLGameplaySettings 에셋 없음";
s.hideBattlePad = hide;
return ReapplyAll();
}
/// <summary>현재 상태 덤프(경로 · activeSelf · activeInHierarchy).</summary>
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<string>(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();
}
}
}