using TMPro; using UnityEngine; using WL.Combat; namespace WL.UI { /// /// 월드 좌표를 화면 좌표로 옮겨 피해량 숫자를 띄우는 HUD 1개. /// /// 힘민지 `Assets/Script/HUD/HUDBase.cs` + `HUDDMGUI.cs` 규약 이식(코드는 새로 작성): /// · 월드→뷰포트→캔버스 anchoredPosition 변환 (HUDBase.Set_Pos L53~60) /// · 표시 시작 시 ±jitter 위치 흔들기 (HUDDMGUI L26~30) /// · 위로 이동 + 알파 Lerp 감쇠 (HUDDMGUI.LateUpdate L44~48) /// · 끌 때 앵커를 멀리(10000) 밀어 다음에 켤 때 깜빡임 제거 (HUDBase.Off L85) ← 실전 노하우 /// NGUI 의존은 없었고, 라벨은 힘민지도 TMP 라 그대로 TextMeshProUGUI 를 쓴다. /// 수치는 CombatSettings 에서 읽는다(C45). /// [RequireComponent(typeof(RectTransform))] public class DamagePopup : MonoBehaviour { /// 힘민지 HUDBase.Off 의 "멀리 보내야 다음에 켜질 때 깜빡임 없어짐" 오프스크린 좌표 private const float ParkOffset = 10000f; [SerializeField] private TextMeshProUGUI label; private RectTransform _rect; private RectTransform _canvasRect; private Camera _camera; private CombatSettings _settings; private Vector3 _worldPos; private float _life; private float _alpha; private float _riseOffset; private Vector2 _jitter; public bool IsFree { get { return !gameObject.activeSelf; } } /// 스포너가 크기·굵기·아웃라인 머티리얼을 적용할 때 쓴다. public TextMeshProUGUI Label { get { if (label == null) label = GetComponentInChildren(true); return label; } } private void Awake() { _rect = GetComponent(); if (label == null) label = GetComponentInChildren(true); } public void Bind(RectTransform canvasRect, Camera cam, CombatSettings settings) { if (_rect == null) _rect = GetComponent(); _canvasRect = canvasRect; _camera = cam; _settings = settings; } /// 피해량 표시 시작. worldPos = 대상의 몸통 중심 월드 좌표. public void Show(Vector3 worldPos, float amount, Color color) { if (_rect == null) _rect = GetComponent(); _worldPos = worldPos; _life = _settings != null ? _settings.dmgPopupLifetime : 0.9f; _alpha = 1f; _riseOffset = 0f; float j = _settings != null ? _settings.dmgPopupJitter : 25f; _jitter = new Vector2(Random.Range(-j, j), Random.Range(-j, j)); if (label != null) { label.text = Mathf.RoundToInt(amount).ToString(); label.color = color; label.alpha = 1f; if (_settings != null) label.fontSize = _settings.dmgPopupFontSize; } gameObject.SetActive(true); UpdatePosition(); } private void LateUpdate() { float dt = Time.deltaTime; _life -= dt; _riseOffset += (_settings != null ? _settings.dmgPopupRiseSpeed : 90f) * dt; _alpha = Mathf.Lerp(_alpha, 0f, dt * (_settings != null ? _settings.dmgPopupFadeSpeed : 2.2f)); if (label != null) label.alpha = _alpha; UpdatePosition(); if (_life <= 0f) Park(); } /// 월드 → 뷰포트 → 캔버스 좌표 (힘민지 HUDBase.Set_Pos 규약) private void UpdatePosition() { if (_canvasRect == null || _camera == null) return; Vector3 vp = _camera.WorldToViewportPoint(_worldPos); // 카메라 뒤쪽이면 화면 밖으로 치운다(반사 좌표로 튀는 것 방지) if (vp.z < 0f) { _rect.anchoredPosition = Vector2.one * ParkOffset; return; } Vector2 size = _canvasRect.sizeDelta; float offY = _settings != null ? _settings.dmgPopupScreenOffsetY : 20f; Vector2 p = new Vector2( (vp.x * size.x) - (size.x * 0.5f), (vp.y * size.y) - (size.y * 0.5f) + offY); _rect.anchoredPosition = p + _jitter + new Vector2(0f, _riseOffset); } /// 끄고 앵커를 화면 밖으로 밀어둔다(재사용 시 첫 프레임 깜빡임 방지 · 힘민지 노하우). public void Park() { gameObject.SetActive(false); if (_rect != null) _rect.anchoredPosition = Vector2.one * ParkOffset; } } }