Project_WL/Assets/WL/UI/Settings/WLCombatTextSettings.cs

443 lines
23 KiB
C#
Raw Normal View History

// ─────────────────────────────────────────────────────────────────────────────
// WLCombatTextSettings.cs — 전투 텍스트 연출 값의 단일 출처 (WL-813g · #813)
//
// 기준서 v1 §B 요소 2 「데미지 숫자 = 일반 0.6 s · 크리 1.0 s · 1.6배 · 합산 윈도우 0.25 s」
// 요소 2 「연쇄 처치 3 s 윈도우 2/3/5/8킬」(윈도우·티어 자체는 811e = WLReactionSettings)
// 요소 4 「토스트 1.2 s · 동시 3줄 · 등급 색」
// 요소 5 「레벨업 = 스탯 팝업 3개」
//
// ■ 왜 이 에셋인가 (C45 · 발주서 §2 ⓓ)
// 지속 시간·배율·상한·오프셋을 코드 상수로 두지 않는다. 이 파일에는 "필드와 기본값"만 있고,
// 게임이 실제로 읽는 값은 Resources/WL/WLCombatTextSettings.asset 이다.
//
// ■ 롤백(C8)
// 에셋이 없거나 enabled = false 면 네 연출(데미지 리듬 · 연쇄 문구 · 전리품 토스트 · 스탯 팝업)이
// 전부 무동작으로 떨어지고 HUDDMGUI 는 **원본 값(0.9 s · 스케일 1배 · 상한 없음)** 으로 돈다.
//
// ■ 좌표계 (px)
// px 값은 세로 1080×1920 기준 화면 픽셀이다(813c WLHudLayoutSettings 와 같은 규약).
// px → 캔버스 유닛 환산은 런타임에 캔버스 폭에서 구한다(802b 노트 · 세로에서 1 px = 1.7778 유닛).
//
// 🔴 어셈블리 주의: Assets/WL/UI/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일).
// ─────────────────────────────────────────────────────────────────────────────
using UnityEngine;
using UnityEngine.UI;
namespace WL.UI
{
[CreateAssetMenu(fileName = "WLCombatTextSettings", menuName = "WL/Combat Text Settings", order = 31)]
public sealed class WLCombatTextSettings : ScriptableObject
{
/// <summary>Resources 경로 — 런타임 로드 키.</summary>
public const string ResourcesPath = "WL/WLCombatTextSettings";
private static WLCombatTextSettings s_instance;
private static bool s_tried;
/// <summary>에셋이 없으면 null(호출부는 null 이면 원본 동작 유지 = C8 롤백).</summary>
public static WLCombatTextSettings Instance
{
get
{
if (s_instance == null && !s_tried)
{
s_tried = true;
s_instance = Resources.Load<WLCombatTextSettings>(ResourcesPath);
}
return s_instance;
}
}
/// <summary>에디터에서 에셋을 새로 만들거나 값을 바꾼 뒤 캐시를 버린다.</summary>
public static void ClearCache() { s_instance = null; s_tried = false; }
/// <summary>진단·A/B 전용 런타임 스위치(에셋을 건드리지 않고 끈다).</summary>
public static bool RuntimeDisabled;
/// <summary>이 웨이브의 텍스트 연출이 살아 있는가 = 에셋 존재 + enabled + 런타임 스위치.</summary>
public static bool Enabled { get { var i = Instance; return i != null && i.textEnabled && !RuntimeDisabled; } }
// ── 전체 스위치 ────────────────────────────────────────────────────────
[Header("전체 스위치")]
[Tooltip("끄면 데미지 리듬 · 연쇄 문구 · 전리품 토스트 · 스탯 팝업이 전부 무동작(원본 HUDDMGUI 동작으로 복귀). 롤백 1순위.")]
public bool textEnabled = true;
[Tooltip("표시 판정을 Console 에 남긴다(검증용 · GC 발생).")]
public bool verboseLog = false;
[Header("기준 해상도 (px · 813c 와 동일 규약)")]
[Tooltip("모든 px 값의 기준이 되는 화면 폭")]
public float designWidthPx = 1080f;
[Tooltip("모든 px 값의 기준이 되는 화면 높이")]
public float designHeightPx = 1920f;
// ── ① 데미지 숫자 리듬 (기준서 §B 요소 2) ──────────────────────────────
[Header("① 데미지 숫자 — 리듬 (HUDDMGUI · 원본 0.9 s · ±25 px 랜덤)")]
[Tooltip("리듬화 사용 여부. off 면 HUDDMGUI 가 원본 상수(0.9 s · 배율 1)로 돈다.")]
public bool damageRhythmEnabled = true;
[Tooltip("일반 타격 숫자 표시 시간(초). 기준서 §B = 0.6 s.")]
public float damageNormalSeconds = 0.6f;
[Tooltip("크리티컬 숫자 표시 시간(초). 기준서 §B = 1.0 s.")]
public float damageCritSeconds = 1.0f;
[Tooltip("크리티컬 글자 크기 배율. 기준서 §B = 1.6배(일반 = 1.0).")]
public float damageCritScale = 1.6f;
[Tooltip("일반 타격 글자 크기 배율(기준 1.0 · 숫자죽 방지로 낮출 수 있다).")]
public float damageNormalScale = 1.0f;
[Tooltip("화면에 동시에 떠 있을 수 있는 데미지 숫자 상한. 초과하면 가장 오래된 것부터 즉시 회수한다. 0 이하 = 상한 없음.")]
public int damageMaxConcurrent = 12;
[Tooltip("상한 초과로 회수할 때 크리티컬은 남기고 일반부터 회수한다.")]
public bool damageEvictNormalFirst = true;
[Tooltip("위로 떠오르는 속도(원본 1.0 · 캔버스 유닛/초).")]
public float damageRiseSpeed = 1.0f;
[Tooltip("크리티컬은 이 배율로 더 빨리 떠오른다(1 = 일반과 같음).")]
public float damageCritRiseMultiplier = 1.35f;
[Tooltip("표시 시간 대비 이 비율이 지난 뒤부터 알파가 빠지기 시작한다(0~1 · 원본은 처음부터 서서히).")]
public float damageFadeStartRatio = 0.55f;
[Tooltip("랜덤 산포 반경(px · 원본 ±25). 0 이면 정확히 대상 위. 겹침을 줄이려면 키운다.")]
public float damageScatterPx = 25f;
[Tooltip("크리티컬은 산포를 이 배율로 줄여 중앙에 가깝게 띄운다(눈에 잘 띄게).")]
public float damageCritScatterMultiplier = 0.4f;
[Tooltip("811e DamageAggregator.Flushed(합산 0.25 s)를 구독해 '합계 · N타' 를 함께 보여 준다. 데이터는 811e 소유 · 여기는 표시만.")]
public bool damageUseMergeWindow = true;
[Tooltip("합산 표시 형식 — {0} = 합계 · {1} = 타수. 빈 문자열이면 합계만 표시.")]
public string damageMergeFormat = "{0}";
[Tooltip("합산 타수가 이 값 이상일 때만 타수 꼬리표를 붙인다(damageMergeHitsSuffix).")]
public int damageMergeHitsMin = 2;
[Tooltip("타수 꼬리표 형식 — {0} = 타수. 예: \" x{0}\". 빈 문자열이면 붙이지 않는다.")]
public string damageMergeHitsSuffix = " <size=60%>x{0}</size>";
// ── ② 연쇄 처치 문구 (기준서 §B 요소 2 · 데이터는 811e KillChain) ──────
[Header("② 연쇄 처치 문구 (KillChain.TierReached 구독 · 화면 중앙 상단)")]
[Tooltip("연쇄 처치 문구 표시 여부.")]
public bool killChainTextEnabled = true;
[Tooltip("문구 표시 시간(초 · unscaled). 발주서 §1-1 = 0.8 s.")]
public float killChainSeconds = 0.8f;
[Tooltip("스케일 펀치 — 등장 순간의 배율(1.0 이면 펀치 없음).")]
public float killChainPunchScale = 1.45f;
[Tooltip("펀치가 1.0 으로 돌아오는 시간(초). 표시 시간보다 짧아야 한다.")]
public float killChainPunchSeconds = 0.18f;
[Tooltip("표시 시간 대비 이 비율이 지난 뒤부터 알파가 빠진다(0~1).")]
public float killChainFadeStartRatio = 0.6f;
[Tooltip("화면 중앙 기준 위쪽 오프셋(px · 세로 1080×1920 기준). Safe Area 안에서 잰다.")]
public float killChainTopOffsetPx = 520f;
[Tooltip("글자 크기(px · 세로 1080×1920 기준).")]
public float killChainFontPx = 96f;
[Tooltip("티어 인덱스별 글자 색(tiers 순서 = DOUBLE/TRIPLE/RAMPAGE/MASSACRE). 모자라면 마지막 색을 쓴다.")]
public Color[] killChainTierColors = new Color[]
{
new Color(1f, 0.94f, 0.72f, 1f),
new Color(1f, 0.80f, 0.35f, 1f),
new Color(1f, 0.52f, 0.22f, 1f),
new Color(1f, 0.27f, 0.28f, 1f),
};
[Tooltip("티어 인덱스별 글자 크기 배율(위 색과 같은 순서). 모자라면 마지막 값.")]
public float[] killChainTierScales = new float[] { 1f, 1.12f, 1.28f, 1.45f };
[Tooltip("문구 형식 — {0} = 티어 이름(811e 에셋의 tiers[i].name) · {1} = . PD Q5 ( §1-1).")]
public string killChainFormat = "{0}!";
[Tooltip("연쇄 킬 수를 아래 줄에 덧붙인다(빈 문자열이면 표시 안 함) — {0} = 킬 수.")]
public string killChainSubFormat = "<size=45%>{0} KILLS</size>";
// ── ③ 전리품 토스트 (기준서 §B 요소 4) ────────────────────────────────
[Header("③ 전리품 토스트 (획득 아이템 · 우측 · 등급 색)")]
[Tooltip("토스트 표시 여부.")]
public bool lootToastEnabled = true;
[Tooltip("한 줄이 남아 있는 시간(초). 기준서 §B 요소 4 = 1.2 s.")]
public float lootToastSeconds = 1.2f;
[Tooltip("동시에 보이는 줄 수. 기준서 §B 요소 4 = 3줄. 넘치면 가장 오래된 줄부터 밀려난다.")]
public int lootToastLines = 3;
[Tooltip("같은 아이템이 이 시간(초) 안에 또 들어오면 새 줄 대신 기존 줄의 수량을 올린다(줄 폭주 방지).")]
public float lootToastMergeSeconds = 1.0f;
[Tooltip("우측 여백(px · Safe Area 우측 기준).")]
public float lootToastRightMarginPx = 40f;
[Tooltip("첫 줄의 화면 세로 중앙 기준 오프셋(px · 양수 = 위).")]
public float lootToastCenterOffsetPx = 120f;
[Tooltip("줄 간격(px).")]
public float lootToastLineHeightPx = 54f;
[Tooltip("줄 글자 크기(px).")]
public float lootToastFontPx = 40f;
[Tooltip("줄 폭(px) — 오른쪽 정렬 기준의 상자 폭.")]
public float lootToastWidthPx = 520f;
[Tooltip("표시 시간 대비 이 비율이 지난 뒤부터 알파가 빠진다(0~1).")]
public float lootToastFadeStartRatio = 0.65f;
[Tooltip("등장할 때 오른쪽에서 밀려 들어오는 거리(px · 0 이면 슬라이드 없음).")]
public float lootToastSlideInPx = 60f;
[Tooltip("슬라이드가 끝나는 시간(초).")]
public float lootToastSlideSeconds = 0.14f;
[Tooltip("줄 형식 — {0} = 등급 색 태그 + 이름 · {1} = 개수. 개수 1 이면 lootToastFormatSingle 을 쓴다.")]
public string lootToastFormat = "{0}[-] x{1}";
[Tooltip("개수 1 일 때의 줄 형식 — {0} = 등급 색 태그 + 이름.")]
public string lootToastFormatSingle = "{0}[-]";
[Tooltip("이 등급 이상만 토스트로 띄운다(기준서 §B 요소 4 = 연출은 4 이상 · 토스트는 전부 보고 싶으면 1).")]
public int lootToastMinGrade = 1;
[Tooltip("골드(아이템 ID 2)도 토스트로 띄운다.")]
public bool lootToastIncludeGold = false;
[Tooltip("획득 집계를 초당 몇 번 확인할지(폴링 · DropItemInfo.m_StageDropData 델타). 813f 이벤트가 오면 이 폴링은 걷어낸다.")]
public float lootToastPollHz = 10f;
[Tooltip("등급 색 팔레트(index 0 = 등급 1). 비우면 MyValue.Get_GradeColor(코드 팔레트)를 그대로 쓴다.")]
public Color[] lootToastGradeColors = new Color[0];
// ── ④ 레벨업 스탯 팝업 (기준서 §B 요소 5) ─────────────────────────────
[Header("④ 레벨업 스탯 팝업 (CombatEvents.LevelUp 구독 · 스탯 3줄)")]
[Tooltip("스탯 팝업 표시 여부.")]
public bool statPopupEnabled = true;
[Tooltip("팝업이 남아 있는 시간(초 · unscaled).")]
public float statPopupSeconds = 2.0f;
[Tooltip("등장 스케일 펀치(1.0 이면 없음).")]
public float statPopupPunchScale = 1.3f;
[Tooltip("펀치가 1.0 으로 돌아오는 시간(초).")]
public float statPopupPunchSeconds = 0.22f;
[Tooltip("표시 시간 대비 이 비율이 지난 뒤부터 알파가 빠진다(0~1).")]
public float statPopupFadeStartRatio = 0.7f;
[Tooltip("화면 중앙 기준 위쪽 오프셋(px).")]
public float statPopupCenterOffsetPx = 180f;
[Tooltip("제목 글자 크기(px).")]
public float statPopupTitleFontPx = 72f;
[Tooltip("스탯 줄 글자 크기(px).")]
public float statPopupRowFontPx = 46f;
[Tooltip("스탯 줄 간격(px).")]
public float statPopupRowHeightPx = 56f;
[Tooltip("보여 줄 스탯 줄 수 상한. 기준서 §B 요소 5 = 3개.")]
public int statPopupRows = 3;
[Tooltip("보여 줄 스탯 종류(순서대로 · statPopupRows 개까지). 813k 가 이전/새 값을 주면 그 값을, 아니면 이 스탯의 스냅샷 차이를 쓴다.")]
public eStat[] statPopupStats = new eStat[] { eStat.STR, eStat.DEX, eStat.INT };
[Tooltip("증가분이 0 인 스탯 줄도 보여 준다.")]
public bool statPopupShowZero = false;
[Tooltip("제목 형식 — {0} = 새 레벨.")]
public string statPopupTitleFormat = "LEVEL {0}";
[Tooltip("스탯 줄 형식 — {0} = 스탯 이름 · {1} = 증가분 · {2} = 새 값.")]
public string statPopupRowFormat = "{0} +{1}";
[Tooltip("제목 색.")]
public Color statPopupTitleColor = new Color(1f, 0.90f, 0.45f, 1f);
[Tooltip("스탯 줄 색.")]
public Color statPopupRowColor = new Color(0.55f, 0.95f, 0.62f, 1f);
[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
[Tooltip("813k GrowthEvents.LevelUp(스탯 3종 + 잔여 포인트)을 구독한다. 끄면 811b 코어 LevelUp(레벨만)만 본다.")]
public bool statPopupUseGrowthEvents = true;
[Tooltip("스탯 변화가 0 일 때(원본은 레벨업으로 스탯을 안 올린다 · 813k ①) 대신 보여 줄 줄 — {0} = 잔여 스탯 포인트. 빈 문자열이면 표시 안 함.")]
public string statPopupPointFormat = "+5 포인트 <size=70%>(보유 {0})</size>";
[Tooltip("한 번의 Add_Exp 에서 여러 레벨이 오를 때(levelsGained ≥ 2) 마지막 레벨만 팝업한다.")]
public bool statPopupMergeMultiLevel = true;
[Header("⑤ 텍스트 아웃라인 · 그림자 (Q3 결함 D-6 · 밝은 배경 대비 · 포스트 0)")]
[Tooltip("끄면 아래 4종이 전부 원본 머티리얼로 돌아간다(C8 롤백).")]
public bool textEdgeEnabled = true;
[Tooltip("연쇄 처치 문구(KillChainText) 테두리.")]
public WLTextEdge killChainEdge = new WLTextEdge
{
enabled = true,
outlineWidth = 0.30f,
outlineColor = new Color(0.05f, 0.03f, 0.02f, 1f),
shadowOffset = new Vector2(0.55f, -0.55f),
shadowColor = new Color(0f, 0f, 0f, 0.8f),
shadowSoftness = 0.25f,
faceDilate = 0.1f,
};
[Tooltip("레벨업 스탯 팝업(StatPopup) 테두리.")]
public WLTextEdge statPopupEdge = new WLTextEdge
{
enabled = true,
outlineWidth = 0.26f,
outlineColor = new Color(0.05f, 0.03f, 0.02f, 1f),
shadowOffset = new Vector2(0.45f, -0.45f),
shadowColor = new Color(0f, 0f, 0f, 0.75f),
shadowSoftness = 0.2f,
faceDilate = 0.08f,
};
[Tooltip("데미지 숫자(HUDDMGUI) 테두리. 숫자가 작아 두께는 낮게.")]
public WLTextEdge damageEdge = new WLTextEdge
{
enabled = true,
outlineWidth = 0.22f,
outlineColor = new Color(0.03f, 0.02f, 0.02f, 1f),
shadowOffset = new Vector2(0.35f, -0.35f),
shadowColor = new Color(0f, 0f, 0f, 0.7f),
shadowSoftness = 0.15f,
faceDilate = 0.05f,
};
[Tooltip("전리품 토스트(LootToast) 테두리.")]
public WLTextEdge lootToastEdge = new WLTextEdge
{
enabled = true,
outlineWidth = 0.24f,
outlineColor = new Color(0.03f, 0.02f, 0.02f, 1f),
shadowOffset = new Vector2(0.4f, -0.4f),
shadowColor = new Color(0f, 0f, 0f, 0.7f),
shadowSoftness = 0.18f,
faceDilate = 0.06f,
};
[Header("⑥ 전리품 토스트 가시성 (Q3 결함 D-6 ② · 15회 발생 · 육안 실패)")]
[Tooltip("줄 뒤에 반투명 패널을 깐다(밝은 배경 대비 확보 · Image 1장/줄).")]
public bool lootToastPanelEnabled = true;
[Tooltip("패널 색(알파 포함).")]
public Color lootToastPanelColor = new Color(0.03f, 0.03f, 0.05f, 0.68f);
[Tooltip("패널이 글자 좌우로 넘치는 여백(px).")]
public float lootToastPanelPadXPx = 18f;
[Tooltip("패널이 글자 위아래로 넘치는 여백(px).")]
public float lootToastPanelPadYPx = 6f;
[Tooltip("등급 1(회색) 처럼 어두운 등급 색을 이 색으로 대체한다(알파 0 이면 원래 등급 색 유지).")]
public Color lootToastMinBrightColor = new Color(0.95f, 0.96f, 0.98f, 1f);
[Tooltip("등급 색의 밝기(V)가 이 값보다 어두우면 위 색으로 바꾼다(0~1 · 0 이면 대체 안 함).")]
[Range(0f, 1f)] public float lootToastMinBrightness = 0.72f;
// ── 파생값 헬퍼 (코드 상수 0) ──────────────────────────────────────────
/// <summary>
/// px → 캔버스 유닛 계수. 813c(WLHudLayoutSettings.UnitsPerPx)와 **같은 산식**을 쓴다 —
/// CanvasScaler 설정을 재현해 구하고(에디트 모드처럼 캔버스가 레이아웃 전이어도 값이 나온다),
/// 스케일러가 없으면 캔버스 rect 폭으로 떨어진다. 세로 1080×1920 · 참조 1920×1080 Expand = 1.7778(802b 실측).
/// </summary>
public float UnitsPerPx(Canvas canvas)
{
if (designWidthPx <= 0f || designHeightPx <= 0f) return 1f;
var scaler = canvas != null ? canvas.GetComponent<CanvasScaler>() : null;
if (scaler != null && scaler.uiScaleMode == CanvasScaler.ScaleMode.ScaleWithScreenSize)
{
Vector2 r = scaler.referenceResolution;
if (r.x > 0f && r.y > 0f)
{
float w = designWidthPx, h = designHeightPx, scale;
switch (scaler.screenMatchMode)
{
case CanvasScaler.ScreenMatchMode.Expand: scale = Mathf.Min(w / r.x, h / r.y); break;
case CanvasScaler.ScreenMatchMode.Shrink: scale = Mathf.Max(w / r.x, h / r.y); break;
default: scale = Mathf.Pow(w / r.x, 1f - scaler.matchWidthOrHeight) *
Mathf.Pow(h / r.y, scaler.matchWidthOrHeight); break;
}
if (scale > 0f) return (w / scale) / designWidthPx;
}
}
var canvasRect = canvas != null ? canvas.transform as RectTransform : null;
if (canvasRect != null && canvasRect.rect.width > 0f) return canvasRect.rect.width / designWidthPx;
return 1f;
}
/// <summary>티어 인덱스의 색(배열이 짧으면 마지막 색 · 비어 있으면 흰색).</summary>
public Color TierColor(int tier)
{
if (killChainTierColors == null || killChainTierColors.Length == 0) return Color.white;
return killChainTierColors[Mathf.Clamp(tier, 0, killChainTierColors.Length - 1)];
}
/// <summary>티어 인덱스의 크기 배율(배열이 짧으면 마지막 값 · 비어 있으면 1).</summary>
public float TierScale(int tier)
{
if (killChainTierScales == null || killChainTierScales.Length == 0) return 1f;
return killChainTierScales[Mathf.Clamp(tier, 0, killChainTierScales.Length - 1)];
}
/// <summary>등급(1~9) 색. 팔레트가 비어 있으면 MyValue 코드 팔레트를 파싱해 쓴다.</summary>
public Color GradeColor(int grade)
{
if (lootToastGradeColors != null && lootToastGradeColors.Length > 0)
return lootToastGradeColors[Mathf.Clamp(grade - 1, 0, lootToastGradeColors.Length - 1)];
Color c;
var tag = MyValue.Get_GradeColor(grade); // "<color=#a6a6a7>"
int s = tag.IndexOf('#');
int e = tag.IndexOf('>');
if (s >= 0 && e > s && ColorUtility.TryParseHtmlString(tag.Substring(s, e - s), out c)) return c;
return Color.white;
}
/// <summary>등급의 리치텍스트 색 태그(원본 팔레트 그대로 · 기준서 §B 요소 4 「보유 팔레트 그대로」).</summary>
public string GradeColorTag(int grade) { return MyValue.Get_GradeColor(grade); }
[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 · Q3 결함 D-6 ②).
/// 등급 1 은 원본 팔레트가 `#a6a6a7`(회색 · V≈0.65) 이라 모래 배경에서 사라진다 —
/// 밝기 V 가 <see cref="lootToastMinBrightness"/> 미만이면 색상(H)·채도(S)를 유지한 채 V 를 끌어올린다.
/// 값이 0 이거나 대체 색 알파가 0 이면 **원본 팔레트 그대로**(C8 롤백).
/// </summary>
public string GradeColorTagBright(int grade)
{
if (lootToastMinBrightness <= 0f || lootToastMinBrightColor.a <= 0f) return GradeColorTag(grade);
Color c = GradeColor(grade);
float h, s, v;
Color.RGBToHSV(c, out h, out s, out v);
if (v >= lootToastMinBrightness) return GradeColorTag(grade);
Color lift = s <= 0.05f ? lootToastMinBrightColor : Color.HSVToRGB(h, s, lootToastMinBrightness);
return "<color=#" + ColorUtility.ToHtmlStringRGB(lift) + ">";
}
/// <summary>전체 스위치(textEdgeEdge)를 반영한 테두리 값. 꺼져 있으면 enabled=false 로 준다.</summary>
public WLTextEdge Edge(WLTextEdge e)
{
if (!textEdgeEnabled) e.enabled = false;
return e;
}
}
}