140 lines
5.7 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|