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

126 lines
4.8 KiB
C#

using TMPro;
using UnityEngine;
using WL.Combat;
namespace WL.UI
{
/// <summary>
/// 월드 좌표를 화면 좌표로 옮겨 피해량 숫자를 띄우는 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).
/// </summary>
[RequireComponent(typeof(RectTransform))]
public class DamagePopup : MonoBehaviour
{
/// <summary>힘민지 HUDBase.Off 의 "멀리 보내야 다음에 켜질 때 깜빡임 없어짐" 오프스크린 좌표</summary>
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; } }
/// <summary>스포너가 크기·굵기·아웃라인 머티리얼을 적용할 때 쓴다.</summary>
public TextMeshProUGUI Label
{
get
{
if (label == null) label = GetComponentInChildren<TextMeshProUGUI>(true);
return label;
}
}
private void Awake()
{
_rect = GetComponent<RectTransform>();
if (label == null) label = GetComponentInChildren<TextMeshProUGUI>(true);
}
public void Bind(RectTransform canvasRect, Camera cam, CombatSettings settings)
{
if (_rect == null) _rect = GetComponent<RectTransform>();
_canvasRect = canvasRect;
_camera = cam;
_settings = settings;
}
/// <summary>피해량 표시 시작. worldPos = 대상의 몸통 중심 월드 좌표.</summary>
public void Show(Vector3 worldPos, float amount, Color color)
{
if (_rect == null) _rect = GetComponent<RectTransform>();
_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();
}
/// <summary>월드 → 뷰포트 → 캔버스 좌표 (힘민지 HUDBase.Set_Pos 규약)</summary>
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);
}
/// <summary>끄고 앵커를 화면 밖으로 밀어둔다(재사용 시 첫 프레임 깜빡임 방지 · 힘민지 노하우).</summary>
public void Park()
{
gameObject.SetActive(false);
if (_rect != null) _rect.anchoredPosition = Vector2.one * ParkOffset;
}
}
}