using System; using UnityEngine; using UnityEngine.UI; using TMPro; using Colosseum.Enemy; namespace Colosseum.UI { /// /// 보스 체력바 UI 컴포넌트. /// BossEnemy의 체력 변화를 자동으로 UI에 반영합니다. /// public class BossHealthBarUI : MonoBehaviour { [Header("References")] [Tooltip("체력 슬라이더 (없으면 자동 검색)")] [SerializeField] private Slider healthSlider; [Tooltip("체력 텍스트 (예: '999 / 999')")] [SerializeField] private TMP_Text healthText; [Tooltip("보스 이름 텍스트")] [SerializeField] private TMP_Text bossNameText; [Header("Target")] [Tooltip("추적할 보스 (런타임에 설정 가능)")] [SerializeField] private BossEnemy targetBoss; [Header("Settings")] [Tooltip("보스 사망 시 UI 숨김 여부")] [SerializeField] private bool hideOnDeath = true; [Tooltip("슬라이더 값 변환 속도")] [Min(0f)] [SerializeField] private float lerpSpeed = 5f; private float displayHealthRatio; private float targetHealthRatio; private bool isSubscribed; private bool isSubscribedToStaticEvent; /// /// 현재 추적 중인 보스 /// public BossEnemy TargetBoss => targetBoss; /// /// 보스 수동 설정 (런타임에서 호출) /// public void SetBoss(BossEnemy boss) { // 기존 보스 이벤트 구독 해제 UnsubscribeFromBoss(); targetBoss = boss; // 새 보스 이벤트 구독 SubscribeToBoss(); // 초기 UI 업데이트 if (targetBoss != null) { UpdateBossName(); UpdateHealthImmediate(); gameObject.SetActive(true); } else { gameObject.SetActive(false); } } /// /// 보스 스폰 이벤트 핸들러 /// private void OnBossSpawned(BossEnemy boss) { if (boss == null) return; SetBoss(boss); } private void Awake() { // 컴포넌트 자동 검색 if (healthSlider == null) healthSlider = GetComponentInChildren(); if (healthText == null) healthText = transform.Find("SliderBox/Label_HP")?.GetComponent(); if (bossNameText == null) bossNameText = transform.Find("SliderBox/Label_BossName")?.GetComponent(); } private void OnEnable() { // 정적 이벤트 구독 (보스 스폰 자동 감지) if (!isSubscribedToStaticEvent) { BossEnemy.OnBossSpawned += OnBossSpawned; isSubscribedToStaticEvent = true; } } private void OnDisable() { // 정적 이벤트 구독 해제 if (isSubscribedToStaticEvent) { BossEnemy.OnBossSpawned -= OnBossSpawned; isSubscribedToStaticEvent = false; } } private void Start() { // 이미 활성화된 보스가 있으면 연결 if (BossEnemy.ActiveBoss != null) { SetBoss(BossEnemy.ActiveBoss); } // 인스펙터에서 설정된 보스가 있으면 구독 else if (targetBoss != null) { SubscribeToBoss(); UpdateBossName(); UpdateHealthImmediate(); } else { // 보스가 없으면 비활성화 (이벤트 대기) gameObject.SetActive(false); } } private void Update() { // 부드러운 체력바 애니메이션 if (!Mathf.Approximately(displayHealthRatio, targetHealthRatio)) { displayHealthRatio = Mathf.Lerp(displayHealthRatio, targetHealthRatio, lerpSpeed * Time.deltaTime); if (Mathf.Abs(displayHealthRatio - targetHealthRatio) < 0.01f) displayHealthRatio = targetHealthRatio; UpdateSliderVisual(); } } private void OnDestroy() { UnsubscribeFromBoss(); // 정적 이벤트 구독 해제 if (isSubscribedToStaticEvent) { BossEnemy.OnBossSpawned -= OnBossSpawned; isSubscribedToStaticEvent = false; } } private void SubscribeToBoss() { if (targetBoss == null || isSubscribed) return; targetBoss.OnHealthChanged += OnBossHealthChanged; targetBoss.OnDeath += OnBossDeath; isSubscribed = true; } private void UnsubscribeFromBoss() { if (targetBoss == null || !isSubscribed) return; targetBoss.OnHealthChanged -= OnBossHealthChanged; targetBoss.OnDeath -= OnBossDeath; isSubscribed = false; } private void OnBossHealthChanged(float currentHealth, float maxHealth) { if (maxHealth <= 0f) return; targetHealthRatio = Mathf.Clamp01(currentHealth / maxHealth); UpdateHealthText(currentHealth, maxHealth); } private void OnBossDeath() { if (hideOnDeath) { gameObject.SetActive(false); } } private void UpdateHealthImmediate() { if (targetBoss == null) return; float currentHealth = targetBoss.CurrentHealth; float maxHealth = targetBoss.MaxHealth; if (maxHealth <= 0f) return; targetHealthRatio = Mathf.Clamp01(currentHealth / maxHealth); displayHealthRatio = targetHealthRatio; UpdateSliderVisual(); UpdateHealthText(currentHealth, maxHealth); } private void UpdateSliderVisual() { if (healthSlider != null) { healthSlider.value = displayHealthRatio; } } private void UpdateHealthText(float currentHealth, float maxHealth) { if (healthText != null) { healthText.text = $"{Mathf.CeilToInt(currentHealth)} / {Mathf.CeilToInt(maxHealth)}"; } } private void UpdateBossName() { if (bossNameText == null || targetBoss == null) return; // EnemyData에서 보스 이름 가져오기 if (targetBoss.Data != null && !string.IsNullOrEmpty(targetBoss.Data.EnemyName)) { bossNameText.text = targetBoss.Data.EnemyName; } else { // 폴백: GameObject 이름 사용 bossNameText.text = targetBoss.name; } } #if UNITY_EDITOR private void OnValidate() { if (healthSlider == null) healthSlider = GetComponentInChildren(); } #endif } }