2026-01-29 06:58:38 +00:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using System;
|
2026-02-01 15:49:12 +00:00
|
|
|
|
using System.Collections; // ⭐ IEnumerator 사용을 위해 추가
|
2026-01-29 06:58:38 +00:00
|
|
|
|
|
|
|
|
|
|
public class PlayerHealth : MonoBehaviour, IDamageable
|
|
|
|
|
|
{
|
|
|
|
|
|
[SerializeField] private Stats stats;
|
2026-01-29 16:11:10 +00:00
|
|
|
|
[SerializeField] private Animator animator;
|
2026-01-29 06:58:38 +00:00
|
|
|
|
|
|
|
|
|
|
public bool IsDead { get; private set; }
|
2026-01-29 16:11:10 +00:00
|
|
|
|
public bool isHit { get; private set; }
|
2026-02-01 15:49:12 +00:00
|
|
|
|
public bool isInvincible; // 대시 중 무적 플래그
|
2026-01-29 06:58:38 +00:00
|
|
|
|
|
|
|
|
|
|
public event Action OnHit, OnDead;
|
|
|
|
|
|
public event Action<float, float> OnHealthChanged;
|
|
|
|
|
|
|
|
|
|
|
|
private float _currentHealth;
|
|
|
|
|
|
|
2026-02-01 15:49:12 +00:00
|
|
|
|
// ⭐ [수정] Start를 코루틴으로 변경하여 실행 순서 문제를 해결합니다.
|
|
|
|
|
|
private IEnumerator Start()
|
2026-01-29 06:58:38 +00:00
|
|
|
|
{
|
2026-02-01 15:49:12 +00:00
|
|
|
|
// 모든 오브젝트가 깨어날 때까지 한 프레임 기다립니다.
|
|
|
|
|
|
yield return null;
|
|
|
|
|
|
|
2026-01-29 06:58:38 +00:00
|
|
|
|
if (stats != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_currentHealth = stats.MaxHealth;
|
2026-02-01 15:49:12 +00:00
|
|
|
|
// 이제 UI가 확실히 준비되었으므로 수치를 전달합니다.
|
2026-01-29 06:58:38 +00:00
|
|
|
|
OnHealthChanged?.Invoke(_currentHealth, stats.MaxHealth);
|
2026-02-01 15:49:12 +00:00
|
|
|
|
Debug.Log($"<color=cyan>[UI Sync]</color> 초기 체력 설정 완료: {_currentHealth}/{stats.MaxHealth}");
|
2026-01-29 06:58:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
if (animator == null) animator = GetComponent<Animator>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-29 16:11:10 +00:00
|
|
|
|
public void RefreshHealthUI()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (stats != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_currentHealth = Mathf.Min(_currentHealth, stats.MaxHealth);
|
2026-02-01 15:49:12 +00:00
|
|
|
|
OnHealthChanged?.Invoke(_currentHealth, stats.MaxHealth);
|
2026-01-29 16:11:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-29 06:58:38 +00:00
|
|
|
|
public void TakeDamage(float amount)
|
|
|
|
|
|
{
|
2026-02-01 15:49:12 +00:00
|
|
|
|
if (isInvincible || IsDead) return;
|
2026-01-29 06:58:38 +00:00
|
|
|
|
_currentHealth = Mathf.Max(0, _currentHealth - amount);
|
|
|
|
|
|
OnHealthChanged?.Invoke(_currentHealth, stats.MaxHealth);
|
|
|
|
|
|
OnHit?.Invoke();
|
|
|
|
|
|
if (!IsDead) StartHit();
|
|
|
|
|
|
if (_currentHealth <= 0) Die();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void StartHit()
|
|
|
|
|
|
{
|
|
|
|
|
|
isHit = true;
|
2026-01-29 16:11:10 +00:00
|
|
|
|
if (animator != null) animator.Play("HitAnime", 0, 0f);
|
2026-01-29 06:58:38 +00:00
|
|
|
|
CancelInvoke(nameof(OnHitEnd));
|
2026-02-01 15:49:12 +00:00
|
|
|
|
Invoke(nameof(OnHitEnd), 0.25f);
|
2026-01-29 06:58:38 +00:00
|
|
|
|
}
|
2026-01-29 16:11:10 +00:00
|
|
|
|
public void OnHitEnd() { isHit = false; }
|
|
|
|
|
|
|
2026-02-01 15:49:12 +00:00
|
|
|
|
private void Die() { IsDead = true; Cursor.visible = true; Cursor.lockState = CursorLockMode.None; OnDead?.Invoke(); }
|
|
|
|
|
|
public void Heal(float amount) { if (IsDead) return; _currentHealth = Mathf.Min(_currentHealth + amount, stats.MaxHealth); OnHealthChanged?.Invoke(_currentHealth, stats.MaxHealth); }
|
2026-01-29 06:58:38 +00:00
|
|
|
|
}
|