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

140 lines
5.7 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using WL.Combat;
namespace WL.UI
{
/// <summary>
/// 데미지 숫자 HUD 풀. 힘민지 `Info/InGameInfo.cs:198~243`(`Get_HUDDMGUI`) 의
/// "비활성 항목을 찾아 재사용하고, 없으면 0번을 복제해 증설" 규약을 이식했다(코드는 새로 작성).
///
/// 전투 코드는 이 클래스만 호출하고 UI 를 직접 알지 않는다
/// (himminji-reuse §7-3 권고 — 전투 코어에서 UI 호출을 걷어내는 방향).
/// </summary>
public class DamagePopupSpawner : MonoBehaviour
{
public enum Kind { ToEnemy, ToPlayer }
private static DamagePopupSpawner _instance;
public static DamagePopupSpawner Instance { get { return _instance; } }
[Header("수치 설정 (ScriptableObject)")]
[SerializeField] private CombatSettings settings;
[Header("참조")]
[Tooltip("복제 원본. 이 오브젝트 자식으로 두고 꺼둔 상태로 유지한다")]
[SerializeField] private DamagePopup template;
[Tooltip("비우면 부모 Canvas 를 자동으로 찾는다")]
[SerializeField] private Canvas canvas;
private readonly List<DamagePopup> _pool = new List<DamagePopup>();
private RectTransform _canvasRect;
private Camera _camera;
private Material _fontMaterial; // 아웃라인 프리셋 — 풀 전체가 인스턴스 1개를 공유
private void Awake()
{
_instance = this;
if (canvas == null) canvas = GetComponentInParent<Canvas>();
_canvasRect = canvas != null ? canvas.GetComponent<RectTransform>() : null;
_camera = Camera.main;
if (template != null)
{
BuildFontMaterial();
ApplyTextStyle(template);
template.Park();
Prewarm(settings != null ? settings.dmgPopupPoolSize : 12);
}
}
private void OnDestroy()
{
if (_instance == this) _instance = null;
if (_fontMaterial != null) Destroy(_fontMaterial);
}
/// <summary>
/// 검은 아웃라인 폰트 머티리얼을 1개만 만들어 풀 전체가 공유한다 (PD #703 ③).
/// TMP 기본 SDF 머티리얼은 폰트 에셋 안의 서브에셋이라 직접 고치면 다른 UI 글자까지 전부 바뀐다 → 복제본을 쓴다.
/// </summary>
private void BuildFontMaterial()
{
var label = template.Label;
if (label == null || label.fontSharedMaterial == null) return;
_fontMaterial = new Material(label.fontSharedMaterial);
_fontMaterial.name = "WL_DamageFont (Outline)";
float w = settings != null ? settings.dmgPopupOutlineWidth : 0.22f;
Color c = settings != null ? settings.dmgPopupOutlineColor : Color.black;
float dilate = settings != null ? settings.dmgPopupFaceDilate : 0.1f;
if (w > 0f) _fontMaterial.EnableKeyword("OUTLINE_ON");
if (_fontMaterial.HasProperty("_OutlineWidth")) _fontMaterial.SetFloat("_OutlineWidth", w);
if (_fontMaterial.HasProperty("_OutlineColor")) _fontMaterial.SetColor("_OutlineColor", c);
if (_fontMaterial.HasProperty("_OutlineSoftness")) _fontMaterial.SetFloat("_OutlineSoftness", 0f);
if (_fontMaterial.HasProperty("_FaceDilate")) _fontMaterial.SetFloat("_FaceDilate", dilate);
}
/// <summary>크기·굵기·아웃라인 머티리얼을 팝업 1개에 적용한다.</summary>
private void ApplyTextStyle(DamagePopup p)
{
var label = p != null ? p.Label : null;
if (label == null) return;
if (settings != null)
{
label.fontSize = settings.dmgPopupFontSize;
label.fontStyle = settings.dmgPopupBold ? TMPro.FontStyles.Bold : TMPro.FontStyles.Normal;
}
if (_fontMaterial != null) label.fontSharedMaterial = _fontMaterial;
}
private void Prewarm(int count)
{
for (int i = _pool.Count; i < count; i++) Grow();
}
private DamagePopup Grow()
{
var go = Instantiate(template.gameObject, template.transform.parent);
go.name = "DamagePopup_" + _pool.Count.ToString("D2");
var p = go.GetComponent<DamagePopup>();
p.Bind(_canvasRect, _camera, settings);
ApplyTextStyle(p);
p.Park();
_pool.Add(p);
return p;
}
/// <summary>비활성 항목 재사용 → 없으면 증설 (힘민지 Get_HUDDMGUI 규약)</summary>
private DamagePopup GetFree()
{
for (int i = 0; i < _pool.Count; i++)
if (_pool[i] != null && _pool[i].IsFree) return _pool[i];
return template != null ? Grow() : null;
}
public void Show(Vector3 worldPos, float amount, Kind kind)
{
if (_canvasRect == null) return;
if (_camera == null) _camera = Camera.main;
var p = GetFree();
if (p == null) return;
p.Bind(_canvasRect, _camera, settings);
Color c = kind == Kind.ToPlayer
? (settings != null ? settings.dmgPopupColorToPlayer : new Color(1f, 0.35f, 0.35f, 1f))
: (settings != null ? settings.dmgPopupColorToEnemy : new Color(1f, 0.95f, 0.35f, 1f));
p.Show(worldPos, amount, c);
}
/// <summary>스포너가 없어도 전투가 죽지 않도록 하는 안전 호출구.</summary>
public static void Spawn(Vector3 worldPos, float amount, Kind kind)
{
if (_instance != null) _instance.Show(worldPos, amount, kind);
}
}
}