using UnityEngine; // 유니티 엔진의 기본 기능을 불러올거에요 -> UnityEngine을 using UnityEngine.Events; // 유니티 이벤트 기능을 사용할거에요 -> UnityEngine.Events를 using System; // C# 기본 이벤트(Action)를 사용할거에요 -> System을 using System.Collections; // 코루틴을 사용할거에요 -> System.Collections를 /// /// 플레이어의 체력, 피격, 무적, 사망을 관리합니다. /// public class PlayerHealth : MonoBehaviour, IDamageable // 클래스를 선언할거에요 -> PlayerHealth를 { [Header("=== 참조 ===")] // 인스펙터 창에 제목을 표시할거에요 -> === 참조 === 를 [SerializeField] private Stats playerStats; // 변수를 선언할거에요 -> 스탯 스크립트를 playerStats에 [Header("=== 설정 ===")] // 인스펙터 창에 제목을 표시할거에요 -> === 설정 === 을 [SerializeField] private float invincibleDuration = 1.0f; // 변수를 선언할거에요 -> 무적 시간을 invincibleDuration에 [SerializeField] private string hitAnimationName = "Player_Hit"; // 변수를 선언할거에요 -> 피격 애니메이션 이름을 hitAnimationName에 [Header("=== 유니티 이벤트 (인스펙터 연결용) ===")] // 인스펙터 창에 제목을 표시할거에요 -> === 유니티 이벤트 === 를 public UnityEvent OnDieEvent; // 이벤트를 선언할거에요 -> 사망 시 발생할 UnityEvent인 OnDieEvent를 public UnityEvent OnHealthChanged; // 이벤트를 선언할거에요 -> 체력 변경 시 발생할 OnHealthChanged를 // ⭐ [이벤트] += 연산자 오류 방지를 위해 event Action 사용 public event Action OnHitEvent; public event Action OnHit; public event Action OnDie; public event Action OnDead; // ⭐ [핵심 수정 1] 외부에서 수정 가능하도록 set 권한 개방 public float CurrentHP { get; private set; } // (체력은 함부로 바꾸면 안되니 private set 유지) // "set 접근자에 액세스할 수 없습니다" 오류 해결 -> private 제거 public bool isInvincible { get; set; } = false; public bool IsDead { get; private set; } = false; // 사망 여부는 내부에서만 관리 private Animator animator; // 변수를 선언할거에요 -> 애니메이터 컴포넌트를 담을 animator를 // ⭐ [핵심 수정 2] "보호 수준 때문에 액세스 불가" 오류 해결 -> public으로 변경 public bool isHit = false; private void Awake() // 함수를 실행할거에요 -> 초기화 Awake를 { animator = GetComponentInChildren(); // 컴포넌트를 가져올거에요 -> 자식의 애니메이터를 if (playerStats == null) playerStats = GetComponent(); // 조건이 맞으면 가져올거에요 -> 스탯 컴포넌트가 비어있다면 내 몸에서 } private void Start() // 함수를 실행할거에요 -> 시작 Start를 { if (playerStats != null) // 조건이 맞으면 실행할거에요 -> 스탯이 있다면 { CurrentHP = playerStats.MaxHealth; // 값을 초기화할거에요 -> 현재 체력을 최대 체력으로 } else // 조건이 틀리면 실행할거에요 -> 스탯이 없다면 { CurrentHP = 100f; // 값을 넣을거에요 -> 기본 체력 100으로 Debug.LogWarning("⚠️ Stats 컴포넌트가 없습니다! 기본 체력 100 적용."); // 경고를 출력할거에요 } RefreshHealthUI(); // 함수를 실행할거에요 -> 초기 체력 UI 갱신을 } public void TakeDamage(float damage) // 함수를 선언할거에요 -> 외부에서 호출할 피격 함수 TakeDamage를 { if (IsDead || isInvincible) return; // 조건이 맞으면 중단할거에요 -> 죽었거나 무적이라면 // 실제 데미지 적용 CurrentHP -= damage; // 값을 뺄거에요 -> 체력에서 데미지를 if (CurrentHP < 0) CurrentHP = 0; // 조건이 맞으면 보정할거에요 -> 체력이 음수가 안 되게 0으로 Debug.Log($"💥 피격! 데미지: {damage}, 남은 체력: {CurrentHP}"); // 로그를 출력할거에요 -> 피격 정보를 RefreshHealthUI(); // 함수를 실행할거에요 -> 체력바 갱신을 if (CurrentHP <= 0) // 조건이 맞으면 실행할거에요 -> 체력이 바닥났다면 { Die(); // 함수를 실행할거에요 -> 사망 처리 Die를 } else // 조건이 틀리면 실행할거에요 -> 아직 살았다면 { StartCoroutine(HitRoutine()); // 코루틴을 실행할거에요 -> 피격 무적/경직 처리를 // 모든 이벤트 호출 OnHit?.Invoke(); OnHitEvent?.Invoke(); } } private void Die() // 함수를 선언할거에요 -> 사망 처리 Die를 { if (IsDead) return; // 조건이 맞으면 중단할거에요 -> 이미 죽었다면 IsDead = true; // 상태를 바꿀거에요 -> 사망 상태를 참으로 OnDieEvent?.Invoke(); // 유니티 이벤트 OnDie?.Invoke(); // C# 이벤트 OnDead?.Invoke(); // C# 이벤트 (호환용) Debug.Log("💀 플레이어 사망!"); // 로그를 출력할거에요 -> 사망 메시지를 } public void RefreshHealthUI() // 함수를 선언할거에요 -> UI를 갱신하는 RefreshHealthUI를 { float ratio = (playerStats != null && playerStats.MaxHealth > 0) ? CurrentHP / playerStats.MaxHealth : 0; // 비율을 계산할거에요 -> 현재 체력 / 최대 체력으로 OnHealthChanged?.Invoke(ratio); // 이벤트를 실행할거에요 -> UI 슬라이더 값을 업데이트하도록 } private IEnumerator HitRoutine() // 코루틴 함수를 선언할거에요 -> 피격 경직 및 무적 처리 HitRoutine을 { isHit = true; // 상태를 바꿀거에요 -> 경직 상태를 참으로 isInvincible = true; // 상태를 바꿀거에요 -> 무적 상태를 참으로 if (animator != null) { animator.Play(hitAnimationName, 0, 0f); // 재생할거에요 -> 설정된 피격 애니메이션을 } yield return new WaitForSeconds(0.3f); // 기다릴거에요 -> 경직 시간(0.3초)만큼 isHit = false; // 상태를 바꿀거에요 -> 경직 해제 yield return new WaitForSeconds(invincibleDuration - 0.3f); // 기다릴거에요 -> 남은 무적 시간만큼 isInvincible = false; // 상태를 바꿀거에요 -> 무적 해제 } public void Heal(float amount) // 함수를 선언할거에요 -> 체력을 회복하는 Heal을 { if (IsDead) return; // 조건이 맞으면 중단할거에요 -> 죽은 상태라면 CurrentHP += amount; // 값을 더할거에요 -> 체력에 회복량을 if (playerStats != null && CurrentHP > playerStats.MaxHealth) // 조건이 맞으면 실행할거에요 -> 최대 체력을 넘었다면 CurrentHP = playerStats.MaxHealth; // 값을 제한할거에요 -> 최대 체력으로 RefreshHealthUI(); // 함수를 실행할거에요 -> UI 갱신을 Debug.Log($"💚 체력 회복: {amount}, 현재: {CurrentHP}"); // 로그를 출력할거에요 -> 회복 정보를 } }