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

205 lines
10 KiB
C#
Raw Permalink Normal View History

[WL-813y] UI 핫픽스 묶음 (#813) Q3 `qa/W2-3_바퀴2.md` 결함 4·6·7·8 + 813tj 연결점 6줄 + 813k LevelUp 페이로드 + 813m 도달 747 px 대응. 재부팅으로 끊긴 이전 워커의 미커밋 작업을 인수해 이어서 완성했다. 1. 813i 연결 6줄 — WLSurvivalUiBridge(신규): PotionButton Provider 4 + ReviveDialog 2. Gameplay 소유 813i API 를 UI 쪽에서 호출만 한다(Assets/WL/Combat/** 수정 0). 2. 스탯 팝업 — StatPopup 이 813k GrowthEvents.LevelUp 을 구독(811b 코어 축과 택일 = 이중 팝업 0). 스탯 변화 0 이면 "+5 포인트(보유 N)" 줄로 떨어진다(813k 보고 ①). 3. 토스트 가시성 — 지속 1.2 → 2.0 s · 글자 40 → 52 px · 줄 뒤 반투명 패널 · 등급 1 회색(#a6a6a7) 밝기 보정(#F2F5FA) · 중심 오프셋 +120 → -120 px(스탯 +180 · 연쇄 +520 대역 회피). 4. 텍스트 아웃라인 — uGUI Outline/Shadow 는 TMP 에 안 먹으므로 TMP 머티리얼 아웃라인 + 언더레이 그림자를 쓴다. (원본 머티리얼 + 값) 조합마다 1장 캐시 공유 = 라벨별 인스턴스 0. 5. 보스 HP 바·배너 늦은 구독 보정 — 켜질 때 BossArena.Boss → 없으면 MobActor 1회 스캔. 6. 전투 중 하단 메뉴 숨김 + 패드 원점 복귀 — HudCombatVisibility(신규 · 노드는 런타임 생성, 프리팹 diff 0) · fanOriginPx (0,260) → (0,0) · attackCenterPx (100,360) → (100,100). 엄지 도달 실측 746.8 → **490.0 px**(발주서 ≤ 490 PASS). 수정 중 실측으로 잡은 버그 2건: UnityEngine.Object 가짜 null 을 `??` 가 못 걸러 HUD_BottomMenu 에 CanvasGroup 이 붙지 않던 MissingComponentException · C8 롤백 (combatHideBottomMenu=false)이 모서리 원점을 돌려줘 메뉴와 다시 겹치던 문제. 값은 전부 SO(C8/C45) · 프리팹 0 · Gameplay/Systems 경로 0 · 핫스팟 0 · Play 0.
2026-09-08 22:58:46 +00:00
// ─────────────────────────────────────────────────────────────────────────────
// WLSurvivalUiBridge.cs — 813tj 가 열어 둔 6줄을 813i API 에 연결한다 (WL-813y §1-1 · #813)
//
// 813tj 완료보고 §4 ②:
// "813i 병합 시 PotionButton.CountProvider/CooldownRemainProvider/CooldownTotalProvider/UseHandler 4줄 +
// ReviveDialog.NotifyDeath()/ReviveRequested 2줄만 연결하면 끝(UI 수정 0)"
//
// ■ 왜 Gameplay 가 아니라 여기인가
// 813i(`Assets/WL/Combat/Survival/**`)는 **Gameplay 소유**라 이 세션이 손대지 못한다(발주서 §2 금지).
// UI 쪽에서 813i 의 **public API 만 호출**하는 얇은 브리지를 두면 소유 경계를 넘지 않고 6줄이 붙는다.
//
// ■ 연결 6줄 (813i 완료보고 §4 · 전부 `WL.Combat.Survival`)
// ① PotionButton.CountProvider ← PotionUse.Remaining
// ② PotionButton.CooldownRemainProvider ← PotionUse.CooldownRemaining
// ③ PotionButton.CooldownTotalProvider ← PotionUse.CooldownTotal
// ④ PotionButton.UseHandler ← PotionUse.TryUse() == PotionResult.Used
// ⑤ ReviveDialog.NotifyDeath() ← DeathFlow.Died / DeathFlow.ReviveRequested 구독
// ⑥ ReviveDialog.ReviveRequested ← DeathFlow.Revive()
//
// ■ 스위치 = 813i 소유 `WLSurvivalSettings.Enabled`(에셋 `enabled` · **Lead 가 병합 시 켠다**)
// 꺼져 있으면 물약 표시는 813tj 의 표시용 기본값으로 떨어지고, 사망/부활 이벤트는 애초에 안 온다
// → 병합 전후 어느 쪽이든 화면이 깨지지 않는다(C8).
//
// 🔴 어셈블리 주의: Assets/WL/UI/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일).
// ─────────────────────────────────────────────────────────────────────────────
using System.Text;
using UnityEngine;
using WL.Combat.Survival;
namespace WL.UI
{
public static class WLSurvivalUiBridge
{
public static bool Installed { get; private set; }
// 진단(프로브가 읽는다 · 실측만)
public static int DiedSeen, ReviveRequestedSeen, RevivedSeen, PotionChangedSeen;
public static int ReviveCalls, PotionUseCalls, PotionUseOk;
public static string LastEvent = "";
public static float LastEventTime;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Boot() { Install(); }
/// <summary>6줄을 연결한다(중복 호출 안전). 프로브도 이 경로를 쓴다.</summary>
public static string Install()
{
if (Installed) return "이미 연결됨";
// ── 물약 4줄 ──────────────────────────────────────────────────────
// 스위치가 꺼져 있으면 813tj 의 표시용 기본값으로 떨어진다(연결 전 화면과 동일).
PotionButton.CountProvider = ReadCount;
PotionButton.CooldownRemainProvider = ReadCooldownRemain;
PotionButton.CooldownTotalProvider = ReadCooldownTotal;
PotionButton.UseHandler = Use;
// ── 부활 2줄 ──────────────────────────────────────────────────────
ReviveDialog.ReviveRequested = RequestRevive;
DeathFlow.Died.Add(OnDied);
DeathFlow.ReviveRequested.Add(OnReviveRequested);
DeathFlow.Revived.Add(OnRevived);
PotionUse.Changed.Add(OnPotionChanged);
Installed = true;
return "연결 6줄 완료 · survivalEnabled=" + WLSurvivalSettings.Enabled;
}
/// <summary>연결을 끊는다(프로브 종료 · 813tj 원래 상태로 복귀).</summary>
public static string Uninstall()
{
if (!Installed) return "연결 안 돼 있음";
PotionButton.ClearProvider();
ReviveDialog.ReviveRequested = null;
DeathFlow.Died.Remove(OnDied);
DeathFlow.ReviveRequested.Remove(OnReviveRequested);
DeathFlow.Revived.Remove(OnRevived);
PotionUse.Changed.Remove(OnPotionChanged);
Installed = false;
return "연결 해제(813tj 미연결 상태로 복귀)";
}
// ── 물약 (813i PotionUse) ─────────────────────────────────────────────
private static int ReadCount()
{
if (!WLSurvivalSettings.Enabled) return FallbackCount();
return PotionUse.Remaining;
}
private static float ReadCooldownRemain()
{
return WLSurvivalSettings.Enabled ? PotionUse.CooldownRemaining : 0f;
}
private static float ReadCooldownTotal()
{
if (!WLSurvivalSettings.Enabled) return FallbackCooldown();
float t = PotionUse.CooldownTotal;
return t > 0f ? t : FallbackCooldown();
}
private static bool Use()
{
PotionUseCalls++;
if (!WLSurvivalSettings.Enabled) { Note("potion:disabled"); return false; }
var r = PotionUse.TryUse();
bool ok = r == PotionResult.Used;
if (ok) PotionUseOk++;
Note("potion:" + r);
return ok;
}
private static int FallbackCount()
{
var s = WLSurvivalUiSettings.Instance;
return s != null ? s.potionFallbackCount : 0;
}
private static float FallbackCooldown()
{
var s = WLSurvivalUiSettings.Instance;
return s != null ? s.potionFallbackCooldownSeconds : 0f;
}
private static void OnPotionChanged(in PotionEvent e)
{
PotionChangedSeen++;
Note("changed:" + e.change + "(" + e.remaining + "/" + e.max + ")");
}
// ── 사망 · 부활 (813i DeathFlow) ──────────────────────────────────────
/// <summary>사망 = 팝업 예약(813tj 는 자체 지연으로 사망 연출 시간을 기다린다).</summary>
private static void OnDied(in DeathEvent e)
{
DiedSeen++;
Note("died#" + e.deathCount + " display=" + e.displaySeconds.ToString("F2") + "s · " + ReviveDialog.NotifyDeath());
}
/// <summary>
/// 813i 가 사망 연출을 끝내고 부활 대기에 들어간 시점 — 지연 없이 지금 연다.
/// **이 구독자가 있어야** 813i 의 `autoReviveWhenNoListener`(구독 0 이면 자동 부활)가 꺼진다.
/// </summary>
private static void OnReviveRequested(in ReviveRequestEvent e)
{
ReviveRequestedSeen++;
Note("reviveRequested#" + e.deathCount + " worldHeld=" + e.worldHeld + " · " + ReviveDialog.NotifyReviveRequested());
}
private static void OnRevived(in RevivedEvent e)
{
RevivedSeen++;
Note("revived#" + e.deathCount + " external=" + e.external + " auto=" + e.auto + " · " + ReviveDialog.NotifyRevived());
}
/// <summary>팝업 "존 시작점에서 부활" 버튼 → 813i.</summary>
private static void RequestRevive()
{
ReviveCalls++;
bool ok = false;
if (WLSurvivalSettings.Enabled) ok = DeathFlow.Revive();
Note("revive() → " + ok);
}
private static void Note(string s)
{
LastEvent = s;
LastEventTime = Time.unscaledTime;
}
// ── 진단 ──────────────────────────────────────────────────────────────
public static void ResetDiagnostics()
{
DiedSeen = ReviveRequestedSeen = RevivedSeen = PotionChangedSeen = 0;
ReviveCalls = PotionUseCalls = PotionUseOk = 0;
LastEvent = ""; LastEventTime = 0f;
}
public static string Dump()
{
var sb = new StringBuilder();
sb.AppendLine("[WLSurvivalUiBridge] installed=" + Installed +
" survivalEnabled=" + WLSurvivalSettings.Enabled +
" · died=" + DiedSeen + " reviveReq=" + ReviveRequestedSeen + " revived=" + RevivedSeen +
" potionChanged=" + PotionChangedSeen +
" reviveCalls=" + ReviveCalls + " potionUse=" + PotionUseOk + "/" + PotionUseCalls +
" last=\"" + LastEvent + "\"");
sb.AppendLine(" 연결 6줄 = count:" + (PotionButton.CountProvider != null) +
" cdRemain:" + (PotionButton.CooldownRemainProvider != null) +
" cdTotal:" + (PotionButton.CooldownTotalProvider != null) +
" use:" + (PotionButton.UseHandler != null) +
" reviveReq:" + (ReviveDialog.ReviveRequested != null) +
" deathFlow구독(Died/ReviveRequested/Revived)=" +
DeathFlow.Died.Count + "/" + DeathFlow.ReviveRequested.Count + "/" + DeathFlow.Revived.Count);
sb.AppendLine(" 813i 현재값 = remaining " + PotionUse.Remaining + "/" + PotionUse.Max +
" cd " + PotionUse.CooldownRemaining.ToString("F2") + "/" + PotionUse.CooldownTotal.ToString("F2") + "s" +
" ready=" + PotionUse.IsReady +
" · DeathFlow state=" + DeathFlow.State + " deaths=" + DeathFlow.DeathCount +
" revives=" + DeathFlow.ReviveCount + " auto=" + DeathFlow.AutoReviveCount);
sb.AppendLine(" UI 가 읽는 값 = count " + ReadCount() + " cdRemain " + ReadCooldownRemain().ToString("F2") +
"s cdTotal " + ReadCooldownTotal().ToString("F2") + "s");
return sb.ToString();
}
}
}