// ───────────────────────────────────────────────────────────────────────────── // WLTextFxUtil.cs — 전투 텍스트 연출 3종이 공유하는 소소한 UI 조립 헬퍼 (WL-813g · #813) // // KillChainText · LootToast · StatPopup 이 자기 노드를 스스로 만든다(BuildIfNeeded 패턴 · 813c BossHpBar 선례). // 프리팹에는 **빈 노드 1개 + 컴포넌트**만 들어가고 자식·좌표는 런타임에 만든다 → // NewGameUI.prefab diff 를 "노드 추가"로만 유지한다(발주서 §2 ⓒ). // // 값은 하나도 들고 있지 않다(전부 WLCombatTextSettings). 여기 있는 숫자는 Unity 기본값(앵커 0/1)뿐이다. // ───────────────────────────────────────────────────────────────────────────── using TMPro; using UnityEngine; namespace WL.UI { internal static class WLTextFxUtil { internal const int UILayer = 5; // 813c 와 같은 UI 레이어 인덱스 /// 이름으로 자식을 찾고 없으면 RectTransform 자식을 만든다. internal static RectTransform NewChild(RectTransform parent, string name) { var t = parent.Find(name) as RectTransform; if (t != null) return t; var go = new GameObject(name, typeof(RectTransform)); go.layer = UILayer; t = (RectTransform)go.transform; t.SetParent(parent, false); t.localScale = Vector3.one; t.localRotation = Quaternion.identity; return t; } /// 부모를 가득 채우도록 늘린다. internal static void Stretch(RectTransform rt) { rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one; rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero; rt.pivot = new Vector2(0.5f, 0.5f); } /// 자식 TextMeshProUGUI 를 만들거나 찾는다(레이캐스트 off · 폰트는 있으면 주입). internal static TextMeshProUGUI NewText(RectTransform parent, string name, TMP_FontAsset font, TextAlignmentOptions align) { var t = NewChild(parent, name); var label = t.GetComponent(); if (label == null) label = t.gameObject.AddComponent(); label.raycastTarget = false; label.alignment = align; // 줄바꿈·오버플로 API 는 TMP 버전마다 이름이 갈려(enableWordWrapping ↔ textWrappingMode) 건드리지 않는다 — 기본값 유지. if (font != null) label.font = font; return label; } /// px → 캔버스 유닛 계수(802b 규약 · 캔버스 폭 / 기준 폭). 캔버스가 없으면 1. internal static float UnitsPerPx(Component c, WLCombatTextSettings s) { if (s == null) return 1f; var canvas = c != null ? c.GetComponentInParent() : null; return s.UnitsPerPx(canvas != null ? canvas.rootCanvas : null); } /// 0 → 1 로 지난 비율(life 는 남은 시간). internal static float Passed(float life, float lifeMax) { return lifeMax > 0f ? 1f - Mathf.Clamp01(life / lifeMax) : 1f; } /// fadeStart 비율 이후에만 1 → 0 으로 떨어지는 알파. internal static float FadeAlpha(float passed, float fadeStart) { fadeStart = Mathf.Clamp01(fadeStart); if (passed < fadeStart) return 1f; if (fadeStart >= 1f) return 0f; return 1f - Mathf.Clamp01((passed - fadeStart) / (1f - fadeStart)); } /// 등장 펀치 스케일(punchSeconds 안에 punchScale → 1 로 수렴). internal static float PunchScale(float elapsed, float punchScale, float punchSeconds) { if (punchSeconds <= 0f || punchScale <= 1f) return 1f; float k = Mathf.Clamp01(elapsed / punchSeconds); return Mathf.Lerp(punchScale, 1f, k * k); } } }