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

593 lines
31 KiB
C#
Raw Normal View History

// ─────────────────────────────────────────────────────────────────────────────
// LootToast.cs — 전리품 획득 토스트 (WL-813g §1-3 · #813)
//
// 기준서 v1 §B 요소 4 「토스트 1.2 s · 동시 3줄 · 등급 색(보유 팔레트 그대로)」 · §C 요소 4 「토스트 없음 → 813g(U)」.
//
// ■ 입력 경로 (지금 / 나중)
// · 지금 = `CombatEvents.Killed`(811b 코어) 로 "뭔가 죽었다"를 알고, 그 뒤 `DropItemInfo.m_StageDropData`
// 획득 집계의 **델타**를 폴링해 실제로 먹은 아이템만 줄로 띄운다(원본 훅 0 · Gameplay 코드 0).
// 집계는 `DropItem.cs:125 Add_Item` 이 채우는 값이라 "떨어진 것"이 아니라 "**주운 것**"이다.
// · 나중 = 813f 가 드랍/획득 이벤트를 내면 `Push(itemId, count)` 로 바로 밀어 넣고 폴링을 걷어낸다(후속).
//
// ■ 등급 색 = `MyValue.Get_GradeColor`(원본 팔레트) 를 그대로 쓴다 — 기준서 §B 요소 4 「새 색 만들 이유 없음」.
// 설정 에셋의 lootToastGradeColors 를 채우면 그 값이 우선한다(SO 이관 대비).
//
// ■ Safe Area (813c 규칙) — 부모(`IngameUIs/WL_HUD`)가 이미 SafeAreaFitter 아래다. 여기서는 부모 기준 우측 정렬만 한다.
//
// ■ 롤백(C8) — 설정이 없거나 textEnabled/lootToastEnabled = false 면 구독·폴링 0 · 표시 0.
//
// 🔴 어셈블리 주의: Assets/WL/UI/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일).
// ─────────────────────────────────────────────────────────────────────────────
using System.Collections.Generic;
using System.Text;
using TMPro;
using UnityEngine;
[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
using UnityEngine.UI;
using WL.Combat.Core;
namespace WL.UI
{
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
public class LootToast : MonoBehaviour
{
[Header("구성 요소 (BuildIfNeeded 가 만든다)")]
[SerializeField] private RectTransform lineRoot;
[SerializeField] private TMP_FontAsset font;
private class Line
{
public RectTransform rt;
public TextMeshProUGUI label;
public CanvasGroup group;
[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
public Image panel; // WL-813y — 밝은 배경 대비용 반투명 패널
public int itemId, count;
public float life, lifeMax, elapsed;
public bool used;
}
private readonly List<Line> _lines = new List<Line>();
private readonly Dictionary<int, int> _seen = new Dictionary<int, int>(); // itemId → 마지막으로 본 누적 수량
private RectTransform _rt;
private bool _subscribed;
private float _nextPoll;
private bool _armed; // Killed 를 한 번이라도 본 뒤부터 폴링한다(로비에서 헛돌지 않게)
// ── WL-813z(D-7) Safe Area 클램프 실값(ApplyLayout 이 계산 · Update 슬라이드가 읽는다)
private float _widthPx, _rightPx, _padPx, _slideMaxPx;
/// <summary>WL-813z — 마지막 클램프 실측(프로브·Dump 용).</summary>
public string LastClamp { get; private set; } = "";
// ── 진단(프로브가 읽는다 · 실측만)
public int KilledSeen { get; private set; }
public int PushCount { get; private set; }
public int MergeCount { get; private set; }
public int DropCount { get; private set; } // 줄 상한 초과로 밀려난 수
public bool Subscribed { get { return _subscribed; } }
[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
/// <summary>마지막으로 적용한 아웃라인/그림자 실측 문자열(WL-813y).</summary>
public string LastEdge { get; private set; } = "";
public int VisibleLines
{
get { int n = 0; for (int i = 0; i < _lines.Count; i++) if (_lines[i].used) n++; return n; }
}
private void OnEnable() { Initialize(); }
private void OnDisable() { Subscribe(false); }
/// <summary>OnEnable 본체 분리(813c 교훈) — 에디트 모드도 같은 경로.</summary>
public string Initialize()
{
_rt = GetComponent<RectTransform>();
BuildIfNeeded();
string layout = ApplyLayout();
HideAll();
var s = WLCombatTextSettings.Instance;
bool on = WLCombatTextSettings.Enabled && s != null && s.lootToastEnabled;
Subscribe(on);
return (on ? "구독 ON · " : "설정 off — 구독 0 · ") + layout;
}
public void Subscribe(bool on)
{
if (on == _subscribed) return;
if (on) CombatEvents.Killed.Add(OnKilled);
else CombatEvents.Killed.Remove(OnKilled);
_subscribed = on;
}
/// <summary>설정의 줄 수만큼 라벨을 만든다(줄 수를 바꾸면 다시 만든다).</summary>
public bool BuildIfNeeded()
{
if (_rt == null) _rt = GetComponent<RectTransform>();
var s = WLCombatTextSettings.Instance;
int want = s != null ? Mathf.Max(1, s.lootToastLines) : 3;
bool made = false;
if (lineRoot == null) { lineRoot = WLTextFxUtil.NewChild(_rt, "Lines"); made = true; }
while (_lines.Count < want)
{
int idx = _lines.Count;
var lrt = WLTextFxUtil.NewChild(lineRoot, "Line" + idx);
[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
// 패널을 먼저 만든다 — uGUI 는 형제 순서가 곧 그리는 순서라 라벨이 위로 온다(813tj 교훈).
var prt = WLTextFxUtil.NewChild(lrt, "Panel");
var panel = prt.GetComponent<Image>();
if (panel == null) panel = prt.gameObject.AddComponent<Image>();
panel.raycastTarget = false;
var label = WLTextFxUtil.NewText(lrt, "Label", font, TextAlignmentOptions.Right);
var cg = lrt.GetComponent<CanvasGroup>();
if (cg == null) cg = lrt.gameObject.AddComponent<CanvasGroup>();
cg.interactable = false; cg.blocksRaycasts = false; cg.alpha = 0f;
[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
_lines.Add(new Line { rt = lrt, label = label, group = cg, panel = panel });
made = true;
}
for (int i = want; i < _lines.Count; i++)
if (_lines[i].rt != null) _lines[i].group.alpha = 0f; // 남는 줄은 숨기기만(파괴하지 않는다)
return made;
}
/// <summary>우측 정렬 · 줄 간격 · 글자 크기 (px → 유닛 환산은 캔버스에서 · 상수 0).</summary>
public string ApplyLayout()
{
var s = WLCombatTextSettings.Instance;
if (s == null) return "WLCombatTextSettings 없음 — 배치 건너뜀";
if (_rt == null) _rt = GetComponent<RectTransform>();
if (lineRoot == null) return "구성 요소 없음 — BuildIfNeeded 먼저";
float u = WLTextFxUtil.UnitsPerPx(this, s);
WLTextFxUtil.Stretch(_rt);
// ── WL-813z(D-7) — 폭·오른쪽 여백·슬라이드 시작 x 를 Safe Area 안으로 클램프.
// 부모(IngameUIs/WL_HUD)는 SafeAreaFitter 아래이므로 이 RectTransform 의 rect 가 곧 Safe Area 다.
// 중심 오프셋(lootToastCenterOffsetPx)은 발주서 지시대로 **그대로 둔다**.
_widthPx = s.lootToastWidthPx;
_rightPx = s.lootToastRightMarginPx;
_padPx = 0f;
_slideMaxPx = s.lootToastSlideInPx;
float parentPx = u > 0.0001f ? _rt.rect.width / u : 0f;
if (s.lootToastClampToSafeArea)
{
_padPx = Mathf.Max(0f, s.lootToastSafePadPx) + (s.lootToastPanelEnabled ? Mathf.Max(0f, s.lootToastPanelPadXPx) : 0f);
_rightPx = Mathf.Max(_padPx, _rightPx);
if (s.lootToastMaxWidthPx > 0f) _widthPx = Mathf.Min(_widthPx, s.lootToastMaxWidthPx);
if (parentPx > 1f) _widthPx = Mathf.Min(_widthPx, parentPx - _rightPx - _padPx);
_widthPx = Mathf.Max(1f, _widthPx);
_slideMaxPx = Mathf.Clamp(_rightPx - _padPx, 0f, s.lootToastSlideInPx); // 등장 중에도 오른쪽 밖으로 못 나간다
}
LastClamp = "부모=" + parentPx.ToString("F1") + "px · 폭 " + s.lootToastWidthPx.ToString("F0") + "→" + _widthPx.ToString("F1") +
"px(상한 " + s.lootToastMaxWidthPx.ToString("F0") + ") · 우여백 " + s.lootToastRightMarginPx.ToString("F0") + "→" + _rightPx.ToString("F1") +
"px · pad=" + _padPx.ToString("F1") + "px · 슬라이드 " + s.lootToastSlideInPx.ToString("F0") + "→" + _slideMaxPx.ToString("F1") +
"px · 우측 최대 침범 " + Mathf.Max(0f, _slideMaxPx + _padPx - _rightPx).ToString("F1") + "px · clamp=" + s.lootToastClampToSafeArea;
lineRoot.anchorMin = lineRoot.anchorMax = new Vector2(1f, 0.5f); // 우측 · 세로 중앙 기준
lineRoot.pivot = new Vector2(1f, 0.5f);
lineRoot.sizeDelta = new Vector2(_widthPx * u, s.lootToastLineHeightPx * s.lootToastLines * u);
lineRoot.anchoredPosition = new Vector2(-_rightPx * u, s.lootToastCenterOffsetPx * u);
var sb = new StringBuilder();
sb.Append("lines=").Append(_lines.Count).Append(" root pos=").Append(lineRoot.anchoredPosition)
.Append(" size=").Append(lineRoot.sizeDelta).Append(" unitsPerPx=").Append(u.ToString("F4"))
.Append(" · ").Append(LastClamp);
for (int i = 0; i < _lines.Count; i++)
{
var l = _lines[i];
l.rt.anchorMin = l.rt.anchorMax = new Vector2(1f, 1f);
l.rt.pivot = new Vector2(1f, 1f);
l.rt.sizeDelta = new Vector2(_widthPx * u, s.lootToastLineHeightPx * u);
l.rt.anchoredPosition = new Vector2(0f, -s.lootToastLineHeightPx * i * u);
WLTextFxUtil.Stretch(l.label.rectTransform);
l.label.fontSize = s.lootToastFontPx * u;
l.label.alignment = TextAlignmentOptions.MidlineRight;
[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
// WL-813z(D-7) — 긴 아이템 이름은 줄바꿈 대신 말줄임(…)으로 자른다(폭 밖으로 밀려나지 않게).
if (s.lootToastEllipsis)
{
if (l.label.textWrappingMode != TextWrappingModes.NoWrap) l.label.textWrappingMode = TextWrappingModes.NoWrap;
if (l.label.overflowMode != TextOverflowModes.Ellipsis) l.label.overflowMode = TextOverflowModes.Ellipsis;
}
[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
// WL-813y — 줄 뒤 패널(대비) + 글자 아웃라인. 값은 전부 SO.
if (l.panel != null)
{
bool on = s.lootToastPanelEnabled;
if (l.panel.enabled != on) l.panel.enabled = on;
l.panel.color = s.lootToastPanelColor;
var prt = l.panel.rectTransform;
prt.anchorMin = Vector2.zero; prt.anchorMax = Vector2.one; prt.pivot = new Vector2(0.5f, 0.5f);
prt.offsetMin = new Vector2(-s.lootToastPanelPadXPx * u, -s.lootToastPanelPadYPx * u);
prt.offsetMax = new Vector2(s.lootToastPanelPadXPx * u, s.lootToastPanelPadYPx * u);
}
LastEdge = WLTextFxUtil.ApplyEdge(l.label, s.Edge(s.lootToastEdge));
sb.Append(" · [").Append(i).Append("] pos=").Append(l.rt.anchoredPosition);
}
return sb.ToString();
}
// ───────────────────────────────────────────── 입력
private void OnKilled(in KilledEvent e)
{
KilledSeen++;
_armed = true; // 처치가 있어야 드랍이 있다 — 그 뒤부터만 획득 집계를 본다
}
/// <summary>813f(후속) · 프로브 공용 입력 — 아이템 1건 획득을 줄로 띄운다.</summary>
public string Push(int itemId, int count)
{
int r = PushCore(itemId, count);
switch (r)
{
case kOff: return "설정 off — 표시 0";
case kGold: return "골드 제외(설정) — 표시 0";
case kGrade: return "등급 하한 미만 — 표시 0";
case kMerged: return "merge item=" + itemId + " count=" +
(_lastPushLine >= 0 && _lastPushLine < _lines.Count ? _lines[_lastPushLine].count : count) +
" line=" + _lastPushLine;
case kQueued: return "queue item=" + itemId + " count=" + count + " 대기=" + _qCount + "(오래된 줄 페이드)";
case kQueueDropped: return "queue full item=" + itemId + " — 버림(대기 " + _qCount + ")";
default: return "push item=" + itemId + " count=" + count + " line=" + _lastPushLine;
}
}
// ── WL-813c2 (Q6 D-31) — 큐 · 병합 · 넘침 페이드 ────────────────────────
// 실측: 화면 동시 줄 수는 813g 때부터 `lootToastLines`(3)로 막혀 있었다. Q6 의 「창 안 6줄」은
// 프로브가 **표시 시간 창 안의 Picked 이벤트 수**를 센 값이다(`WL813_ScoreProbe.cs:437~443`).
// 실물 결함은 넘칠 때 오래된 줄이 **페이드 없이 즉시 다른 내용으로 교체**돼 깜빡이는 것이다.
// → ① 같은 아이템은 보이는 동안 계속 수량 병합("금화 ×8") ② 넘치면 오래된 줄을 즉시 페이드시키고
// 새 줄은 **고정 크기 링 큐**에서 자리를 기다린다(동시 줄 수 상한 유지 · 할당 0).
// C8 롤백 = `lootToastOverflowFadeSec = 0` → 813g 의 즉시 한 칸 당김 그대로.
private const int kOff = 0, kGold = 1, kGrade = 2, kMerged = 3, kShown = 4, kQueued = 5, kQueueDropped = 6;
private int _lastPushLine = -1;
private struct Pending { public int itemId, count; }
private Pending[] _queue = new Pending[0];
private int _qHead, _qCount;
/// <summary>지금 자리를 기다리는 건수(진단).</summary>
public int QueuedCount { get { return _qCount; } }
/// <summary>큐가 꽉 차 버린 건수(진단).</summary>
public int QueueDropCount { get; private set; }
/// <summary>넘침으로 즉시 페이드시킨 줄 수(진단).</summary>
public int OverflowFadeCount { get; private set; }
/// <summary>문자열을 만들지 않는 입력 본체(GC 0). 돌려주는 값은 위 k* 코드.</summary>
private int PushCore(int itemId, int count)
{
_lastPushLine = -1;
var s = WLCombatTextSettings.Instance;
if (s == null || !WLCombatTextSettings.Enabled || !s.lootToastEnabled) return kOff;
if (!s.lootToastIncludeGold && itemId == GoldItemId) return kGold;
int grade; string name;
ResolveItem(itemId, out grade, out name);
if (grade < s.lootToastMinGrade) return kGrade;
BuildIfNeeded();
int n = LineCap(s);
// ① 같은 아이템 병합 — 보이는 동안이면 창과 무관하게(설정) 수량만 올린다.
for (int i = 0; i < n; i++)
{
var l = _lines[i];
if (!l.used || l.itemId != itemId) continue;
if (!s.lootToastMergeWhileVisible && l.elapsed > s.lootToastMergeSeconds) continue;
l.count += count;
l.life = l.lifeMax; l.elapsed = 0f; l.group.alpha = 1f;
Render(l, name, grade, s);
MergeCount++; _lastPushLine = i;
return kMerged;
}
// ② 빈 줄이 있으면 바로 띄운다(지연 0).
int slot = FreeSlot(s);
if (slot >= 0) { Occupy(slot, itemId, count, name, grade, s); return kShown; }
// ③ 넘침 — 오래된 줄을 즉시 페이드시키고 새 줄은 큐에서 기다린다.
if (s.lootToastOverflowFadeSec > 0f)
{
FadeOldest(s);
return Enqueue(s, itemId, count) ? kQueued : kQueueDropped;
}
// ④ C8 롤백 경로(페이드 0) — 813g 그대로 한 칸씩 당기고 마지막 자리를 쓴다.
slot = ShiftUp(s);
Occupy(slot, itemId, count, name, grade, s);
return kShown;
}
/// <summary>표가 아직 안 올라온 시점(에디트 모드·로딩 중)에도 죽지 않게 감싼다 — 없으면 ID·등급 1.</summary>
private static void ResolveItem(int itemId, out int grade, out string name)
{
grade = 1; name = null;
try
{
var data = table_itemlist.Ins != null ? table_itemlist.Ins.Get_Data(itemId) : null;
if (data != null)
{
grade = Mathf.Max(1, data.n_ItemGrade);
var n = data.Get_Name();
if (!string.IsNullOrEmpty(n)) name = n;
}
}
catch { /* 표 미로드 · ID 없음 — 기본값으로 표시 */ }
if (name == null) name = itemId.ToString();
}
/// <summary>지금 쓸 수 있는 줄 수 = min(만들어 둔 줄, 설정 상한).</summary>
private int LineCap(WLCombatTextSettings s)
{
return Mathf.Min(_lines.Count, Mathf.Max(1, s.lootToastLines));
}
private int FreeSlot(WLCombatTextSettings s)
{
int n = LineCap(s);
for (int i = 0; i < n; i++) if (!_lines[i].used) return i;
return -1;
}
private void Occupy(int slot, int itemId, int count, string name, int grade, WLCombatTextSettings s)
{
var line = _lines[slot];
line.used = true; line.itemId = itemId; line.count = count;
line.lifeMax = line.life = Mathf.Max(0.01f, s.lootToastSeconds);
line.elapsed = 0f;
line.group.alpha = 1f;
Render(line, name, grade, s);
PushCount++; _lastPushLine = slot;
}
/// <summary>남은 수명이 가장 짧은 줄(= 가장 오래된 줄)을 페이드 시간으로 줄인다 — 「넘치면 오래된 줄 즉시 페이드」.</summary>
private void FadeOldest(WLCombatTextSettings s)
{
int n = LineCap(s), pick = -1; float least = float.MaxValue;
for (int i = 0; i < n; i++)
{
var l = _lines[i];
if (!l.used) continue;
if (l.life < least) { least = l.life; pick = i; }
}
if (pick < 0) return;
float f = Mathf.Max(0.01f, s.lootToastOverflowFadeSec);
if (_lines[pick].life <= f) return; // 이미 그만큼만 남았으면 건드리지 않는다
_lines[pick].life = f;
_lines[pick].lifeMax = f; // Passed = 1 life/lifeMax → 곧바로 페이드 구간
OverflowFadeCount++; DropCount++;
}
private void EnsureQueue(WLCombatTextSettings s)
{
int want = Mathf.Max(0, s.lootToastQueueMax);
if (_queue.Length == want) return;
_queue = new Pending[want]; // 설정을 바꿀 때만 1회 — 폴링·프레임 경로에서는 할당 0
_qHead = 0; _qCount = 0;
}
private bool Enqueue(WLCombatTextSettings s, int itemId, int count)
{
EnsureQueue(s);
if (_queue.Length == 0) return false;
for (int k = 0; k < _qCount; k++) // 큐 안에서도 같은 아이템은 수량만 더한다
{
int idx = (_qHead + k) % _queue.Length;
if (_queue[idx].itemId != itemId) continue;
_queue[idx].count += count; MergeCount++;
return true;
}
if (_qCount >= _queue.Length) // 꽉 차면 가장 오래 기다린 항목을 버린다
{
_qHead = (_qHead + 1) % _queue.Length; _qCount--;
QueueDropCount++;
}
int tail = (_qHead + _qCount) % _queue.Length;
_queue[tail].itemId = itemId; _queue[tail].count = count;
_qCount++;
return true;
}
/// <summary>자리가 나면 큐에서 꺼내 띄운다(매 프레임 · 문자열 0).</summary>
private void FlushQueue(WLCombatTextSettings s)
{
if (_qCount <= 0 || _queue.Length == 0) return;
int guard = _queue.Length;
while (_qCount > 0 && guard-- > 0)
{
int slot = FreeSlot(s);
if (slot < 0) return;
var p = _queue[_qHead];
_qHead = (_qHead + 1) % _queue.Length; _qCount--;
int grade; string name;
ResolveItem(p.itemId, out grade, out name);
Occupy(slot, p.itemId, p.count, name, grade, s);
}
}
/// <summary>프로브 전용 — 자리가 난 만큼 큐를 밀어 넣고 띄운 건수를 돌려준다(런타임은 Update 가 매 프레임 부른다).</summary>
public int FlushQueueNow()
{
var s = WLCombatTextSettings.Instance;
if (s == null) return 0;
int before = _qCount;
FlushQueue(s);
return before - _qCount;
}
/// <summary>C8 롤백 경로 — 0번을 버리고 한 칸씩 올린다(내용만 옮기고 RectTransform 은 그대로).</summary>
private int ShiftUp(WLCombatTextSettings s)
{
int n = LineCap(s);
DropCount++;
for (int i = 0; i < n - 1; i++)
{
var a = _lines[i]; var b = _lines[i + 1];
a.itemId = b.itemId; a.count = b.count; a.life = b.life; a.lifeMax = b.lifeMax; a.elapsed = b.elapsed;
a.used = b.used; a.label.text = b.label.text; a.label.color = b.label.color; a.group.alpha = b.group.alpha;
}
_lines[n - 1].used = false;
return n - 1;
}
private void Render(Line l, string name, int grade, WLCombatTextSettings s)
{
[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
string colored = s.GradeColorTagBright(grade) + name; // WL-813y — 등급 1 회색이 모래 배경에서 사라진다(Q3 D-6 ②)
l.label.text = l.count > 1
? DSUtil.Format(s.lootToastFormat, colored, l.count)
: DSUtil.Format(s.lootToastFormatSingle, colored);
l.label.color = Color.white; // 등급색은 리치텍스트 태그가 담당한다(원본 팔레트 그대로)
}
public void HideAll()
{
for (int i = 0; i < _lines.Count; i++)
{
_lines[i].used = false;
_lines[i].life = 0f;
if (_lines[i].group != null) _lines[i].group.alpha = 0f;
if (_lines[i].label != null) _lines[i].label.text = "";
}
_qHead = 0; _qCount = 0; // WL-813c2 — 대기 큐도 비운다
}
// ───────────────────────────────────────────── 폴링 · 수명
/// <summary>골드 아이템 ID — 원본 DropItemInfo 의 코인 분기 키(`DropItemInfo.cs:12` dic_addrPath[2]).</summary>
public const int GoldItemId = 2;
private void Update()
{
var s = WLCombatTextSettings.Instance;
if (s == null) return;
// ── 획득 집계 폴링(813f 이벤트가 오면 걷어낸다)
// 🔴 WL-813c2 — 게임 경로는 **문자열을 만들지 않는 갈래**로 부른다(기존에는 폴링마다
// `new StringBuilder()` + 진단 문자열이 초당 10회 났다 = GC). 진단용 문자열 갈래는 프로브 전용.
if (_armed && Application.isPlaying && s.lootToastPollHz > 0f && Time.unscaledTime >= _nextPoll)
{
_nextPoll = Time.unscaledTime + 1f / Mathf.Max(0.1f, s.lootToastPollHz);
PollStageDropCore(null);
}
// ── WL-813c2(D-31) — 넘쳐서 기다리던 줄을 자리가 나는 대로 띄운다
FlushQueue(s);
// ── 줄 수명
float dt = Time.unscaledDeltaTime;
float u = WLTextFxUtil.UnitsPerPx(this, s);
for (int i = 0; i < _lines.Count; i++)
{
var l = _lines[i];
if (!l.used) continue;
l.life -= dt; l.elapsed += dt;
float passed = WLTextFxUtil.Passed(l.life, l.lifeMax);
l.group.alpha = WLTextFxUtil.FadeAlpha(passed, s.lootToastFadeStartRatio);
if (s.lootToastSlideInPx > 0f && s.lootToastSlideSeconds > 0f)
{
float k = Mathf.Clamp01(l.elapsed / s.lootToastSlideSeconds);
// WL-813z(D-7) — 등장 x 상한 = Safe Area 를 넘지 않는 값(ApplyLayout 계산 · 클램프 off 면 원래 값).
float startPx = s.lootToastClampToSafeArea ? _slideMaxPx : s.lootToastSlideInPx;
float x = Mathf.Lerp(startPx, 0f, k) * u;
l.rt.anchoredPosition = new Vector2(x, -s.lootToastLineHeightPx * i * u);
}
if (l.life <= 0f) { l.used = false; l.group.alpha = 0f; l.label.text = ""; }
}
}
/// <summary>StageDropData 의 아이템 누적 수량 델타 = 이번에 주운 것(진단 문자열 갈래 · 프로브 전용).</summary>
public string PollStageDrop() { return PollStageDropCore(new StringBuilder()); }
/// <summary>폴링 본체. <paramref name="sb"/> 가 null 이면 문자열을 하나도 만들지 않는다(게임 경로 · GC 0).</summary>
private string PollStageDropCore(StringBuilder sb)
{
if (!DropItemInfo.isIns || DropItemInfo.Ins == null) return "DropItemInfo 없음";
var data = DropItemInfo.Ins.m_StageDropData;
if (data == null) return "StageDropData 없음";
var dic = data.Get_Item();
if (dic == null) return "집계 없음";
foreach (var kv in dic) // Dictionary 열거자는 구조체 = 할당 0
{
int id = kv.Key;
int now = kv.Value;
int last;
if (!_seen.TryGetValue(id, out last)) last = 0;
if (now > last)
{
DropCountedItems++;
if (sb != null) sb.Append(Push(id, now - last)).Append(" | ");
else PushCore(id, now - last);
}
_seen[id] = now;
}
return sb != null && sb.Length > 0 ? sb.ToString() : "델타 0";
}
/// <summary>진단 — 폴링으로 잡아낸 획득 건수.</summary>
public int DropCountedItems { get; private set; }
/// <summary>스테이지가 바뀌면 집계 기준을 초기화한다(StageDropData.Init 과 짝).</summary>
public void ResetPollBaseline()
{
_seen.Clear(); _armed = false; DropCountedItems = 0;
_qHead = 0; _qCount = 0; QueueDropCount = 0; OverflowFadeCount = 0; // WL-813c2
}
// ───────────────────────────────────────────── 프로브(에디트 모드 · Play 0)
/// <summary>합성 처치 이벤트 — 811b 코어의 실제 Dispatch 경로(구독 배선까지 검증).</summary>
public static string RaiseFakeKilled()
{
var e = new KilledEvent
{
victim = null, killer = null, subRole = eSubRol.None, id = 0,
position = Vector3.zero, byDirectHit = false, time = Time.unscaledTime, frame = Time.frameCount
};
CombatEvents.Killed.Dispatch(in e);
return "dispatch Killed · 구독자=" + CombatEvents.Killed.Count;
}
public string Dump()
{
var sb = new StringBuilder();
sb.AppendLine("LootToast subscribed=" + _subscribed + " killedSeen=" + KilledSeen +
" push=" + PushCount + " merge=" + MergeCount + " drop=" + DropCount +
" visible=" + VisibleLines);
var sq = WLCombatTextSettings.Instance;
sb.AppendLine(" [813c2] 상한=" + (sq != null ? sq.lootToastLines : 0) +
" 대기=" + _qCount + "/" + _queue.Length + " 큐버림=" + QueueDropCount +
" 넘침페이드=" + OverflowFadeCount + "회 · 병합창=" +
(sq != null ? sq.lootToastMergeSeconds.ToString("F2") : "-") + "s(보이는동안=" +
(sq != null ? sq.lootToastMergeWhileVisible.ToString() : "-") + ") · 페이드=" +
(sq != null ? sq.lootToastOverflowFadeSec.ToString("F2") : "-") + "s");
for (int i = 0; i < _lines.Count; i++)
{
var l = _lines[i];
sb.AppendLine(" [" + i + "] used=" + l.used + " item=" + l.itemId + " x" + l.count +
" alpha=" + l.group.alpha.ToString("F2") + " pos=" + l.rt.anchoredPosition +
" size=" + l.rt.sizeDelta + " font=" + l.label.fontSize.ToString("F1") +
[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
" panel=" + (l.panel != null ? (l.panel.enabled ? "#" + ColorUtility.ToHtmlStringRGBA(l.panel.color) +
" off" + l.panel.rectTransform.offsetMin + l.panel.rectTransform.offsetMax : "off") : "없음") +
" text=\"" + l.label.text + "\"");
}
[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
var st = WLCombatTextSettings.Instance;
if (st != null)
sb.AppendLine(" 설정 seconds=" + st.lootToastSeconds + "s fontPx=" + st.lootToastFontPx +
" widthPx=" + st.lootToastWidthPx + " lineHeightPx=" + st.lootToastLineHeightPx +
" rightMarginPx=" + st.lootToastRightMarginPx + " centerOffsetPx=" + st.lootToastCenterOffsetPx +
" panel=" + st.lootToastPanelEnabled + " 밝기하한=" + st.lootToastMinBrightness +
" · 등급1 태그 " + st.GradeColorTag(1) + " → " + st.GradeColorTagBright(1));
sb.AppendLine(" clamp = " + LastClamp);
[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
sb.AppendLine(" edge = " + LastEdge);
return sb.ToString();
}
public void SetFont(TMP_FontAsset f)
{
font = f;
for (int i = 0; i < _lines.Count; i++)
if (_lines[i].label != null && f != null) _lines[i].label.font = f;
}
}
}