using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을 using System.Collections; // 코루틴을 사용할거에요 -> System.Collections를 public class ExplodeMonster : MonsterClass // 클래스를 선언할거에요 -> 자폭 몬스터를 { [Header("=== 자폭 설정 ===")] // 인스펙터 제목을 달거에요 -> 자폭 설정을 [SerializeField] private float explodeRange = 4f; // 변수를 선언할거에요 -> 폭발 범위를 [SerializeField] private float triggerRange = 2.5f; // 변수를 선언할거에요 -> 자폭 감지 거리를 [SerializeField] private float fuseTime = 1.5f; // 변수를 선언할거에요 -> 점화 후 폭발까지 대기 시간을 [SerializeField] private float explosionDamage = 50f; // 변수를 선언할거에요 -> 폭발 데미지를 [Header("이펙트")] // 인스펙터 제목을 달거에요 -> 이펙트 설정을 [SerializeField] private GameObject explosionEffectPrefab; // 변수를 선언할거에요 -> 폭발 이펙트 프리팹을 [SerializeField] private ParticleSystem fuseEffect; // 변수를 선언할거에요 -> 도화선 파티클을 [SerializeField] private AudioClip fuseSound; // 변수를 선언할거에요 -> 도화선 소리를 [SerializeField] private AudioClip explosionSound; // 변수를 선언할거에요 -> 폭발 소리를 [Header("애니메이션")] // 인스펙터 제목을 달거에요 -> 애니메이션 State 이름을 [SerializeField] private string runAnim = "scavenger_run"; // 변수를 선언할거에요 -> 달리기 State 이름을 [SerializeField] private string fuseAnim = "scavenger_Fuse"; // 변수를 선언할거에요 -> 점화 State 이름을 [Header("=== 디버그 ===")] // 인스펙터 제목을 달거에요 -> 디버그 설정을 [SerializeField] private bool showExplodeDebugLog = true; // 변수를 선언할거에요 -> 디버그 로그 on/off를 private bool hasExploded = false; // 변수를 선언할거에요 -> 폭발 완료 여부를 private string _lastLoggedState = ""; // 변수를 선언할거에요 -> 직전 로그 State 이름을 // ───────────────────────────────────────────────────────── // 초기화 // ───────────────────────────────────────────────────────── protected override void Init() // 함수를 실행할거에요 -> 추가 초기화를 { if (agent != null) agent.stoppingDistance = 0.5f; // 설정할거에요 -> 정지 거리를 짧게 } protected override void OnResetStats() // 함수를 정의할거에요 -> 재활성화 시 추가 초기화를 { hasExploded = false; // 초기화할거에요 -> 폭발 상태를 _lastLoggedState = ""; // 초기화할거에요 -> 디버그 State 캐시를 } // ───────────────────────────────────────────────────────── // 매 프레임 State 감시 (디버그) // ───────────────────────────────────────────────────────── public override void OnManagedUpdate() // 함수를 정의할거에요 -> 매 프레임 상태 감시를 { // ⭐ IsName 진단 로그 — 어떤 State가 실제로 재생 중인지 확인용 if (showExplodeDebugLog && animator != null && animator.isInitialized) // 조건이 맞으면 실행할거에요 -> 디버그 켜져있고 애니메이터 준비됐으면 { AnimatorStateInfo diagInfo = animator.GetCurrentAnimatorStateInfo(0); // 가져올거에요 -> 현재 State 정보를 Debug.Log( $"[진단] fullPathHash={diagInfo.fullPathHash}\n" + $" IsName('scavenger_idle')={diagInfo.IsName("scavenger_idle")}\n" + $" IsName('Base Layer.scavenger_idle')={diagInfo.IsName("Base Layer.scavenger_idle")}" ); // 출력할거에요 -> 두 가지 형식 모두 확인 } base.OnManagedUpdate(); // 실행할거에요 -> 부모 업데이트를 if (!showExplodeDebugLog || animator == null) return; // 중단할거에요 -> 디버그 꺼져있으면 AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(0); // 가져올거에요 -> 현재 State 정보를 string cur; if (info.IsName(runAnim)) cur = runAnim; else if (info.IsName(fuseAnim)) cur = fuseAnim; else if (info.IsName(Monster_Idle)) cur = Monster_Idle; else if (info.IsName(Monster_GetDamage)) cur = Monster_GetDamage; else if (info.IsName(Monster_Die)) cur = Monster_Die; else cur = $"UNKNOWN({info.fullPathHash})"; if (cur == _lastLoggedState) return; // 중단할거에요 -> 같은 State면 중복 출력 안 함 _lastLoggedState = cur; // 저장할거에요 -> 현재 State를 Debug.Log( $"[ExplodeMonster] 🎬 {cur}\n" + $" IsAggroed={IsAggroed} | isHit={isHit} | isAttacking={isAttacking} | hasExploded={hasExploded}" ); // 출력할거에요 -> State + 상태 변수들을 } // ───────────────────────────────────────────────────────── // TakeDamage 오버라이드 — 완전한 슈퍼아머 // ───────────────────────────────────────────────────────── public override void TakeDamage(float amount) // 함수를 정의할거에요 -> 피격 처리 오버라이드를 { if (isDead) return; // 중단할거에요 -> 이미 죽었으면 ApplyDamageOnly(amount); // 실행할거에요 -> 체력 감소와 UI만 (StartHit 호출 없음) // 피격 사운드/이펙트는 MonsterBehaviour가 OnHitEvent 구독해서 처리 InvokeHitEvent((transform.position - (playerTransform != null ? playerTransform.position : transform.position)).normalized); // 발생시킬거에요 -> 피격 이벤트를 if (currentHP <= 0) // 조건이 맞으면 실행할거에요 -> 사망했으면 { StopAllCoroutines(); // 중단할거에요 -> 자폭 코루틴을 Die(); // 실행할거에요 -> 사망 처리를 } } // ───────────────────────────────────────────────────────── // AI 로직 // ───────────────────────────────────────────────────────── protected override void ExecuteAILogic() // 함수를 실행할거에요 -> AI 로직을 { if (isAttacking || hasExploded) return; // 중단할거에요 -> 자폭 진행 중이면 float dist = Vector3.Distance(transform.position, playerTransform.position); // 계산할거에요 -> 플레이어와의 거리를 if (dist <= triggerRange) // 조건이 맞으면 실행할거에요 -> 자폭 감지 거리 안이면 StartCoroutine(ExplodeRoutine()); // 실행할거에요 -> 자폭 코루틴을 else // 조건이 틀리면 실행할거에요 -> 멀면 ChasePlayer(); // 실행할거에요 -> 추격을 } private void ChasePlayer() // 함수를 선언할거에요 -> 추격 로직을 { if (agent != null && agent.isOnNavMesh) // 조건이 맞으면 실행할거에요 -> 에이전트가 정상이면 { agent.isStopped = false; // 켤거에요 -> 이동을 agent.SetDestination(playerTransform.position); // 설정할거에요 -> 목적지를 } if (animator == null || !animator.isInitialized) return; // 중단할거에요 -> 애니메이터가 없으면 if (!animator.GetCurrentAnimatorStateInfo(0).IsName(runAnim)) // 조건이 맞으면 실행할거에요 -> 달리기 State가 아니면 animator.Play(runAnim, 0, 0f); // 재생할거에요 -> 달리기 애니를 } // ───────────────────────────────────────────────────────── // 자폭 코루틴 // ───────────────────────────────────────────────────────── private IEnumerator ExplodeRoutine() // 코루틴을 정의할거에요 -> 자폭 과정을 { isAttacking = true; // 설정할거에요 -> 공격 중으로 hasExploded = true; // 설정할거에요 -> 폭발 됨으로 if (agent != null && agent.isOnNavMesh) // 조건이 맞으면 실행할거에요 -> 에이전트가 정상이면 { agent.isStopped = true; agent.velocity = Vector3.zero; } // 멈출거에요 -> 이동을 if (showExplodeDebugLog) Debug.Log($"[ExplodeMonster] 🔥 점화!"); // 출력할거에요 if (animator != null && animator.isInitialized) // 조건이 맞으면 실행할거에요 -> 애니메이터가 있으면 animator.Play(fuseAnim, 0, 0f); // 재생할거에요 -> 점화 애니를 if (fuseEffect != null) fuseEffect.Play(); // 재생할거에요 -> 도화선 파티클을 if (fuseSound != null && audioSource != null) audioSource.PlayOneShot(fuseSound); // 재생할거에요 -> 도화선 소리를 yield return new WaitForSeconds(fuseTime); // 기다릴거에요 -> 점화 대기 시간만큼 if (showExplodeDebugLog) Debug.Log($"[ExplodeMonster] 💥 폭발!"); // 출력할거에요 Explode(); // 실행할거에요 -> 폭발 처리를 } // ───────────────────────────────────────────────────────── // 폭발 처리 // ───────────────────────────────────────────────────────── private void Explode() // 함수를 선언할거에요 -> 실제 폭발 로직을 { if (explosionEffectPrefab != null) // 조건이 맞으면 실행할거에요 -> 이펙트 프리팹이 있으면 Instantiate(explosionEffectPrefab, transform.position, Quaternion.identity); // 생성할거에요 -> 폭발 이펙트를 if (explosionSound != null) // 조건이 맞으면 실행할거에요 -> 폭발 소리가 있으면 AudioSource.PlayClipAtPoint(explosionSound, transform.position); // 재생할거에요 -> 폭발 소리를 Collider[] hits = Physics.OverlapSphere(transform.position, explodeRange); // 가져올거에요 -> 폭발 범위 내 충돌체들을 foreach (var hit in hits) // 반복할거에요 -> 충돌체마다 { if (hit.CompareTag("Player")) // 조건이 맞으면 실행할거에요 -> 플레이어라면 { IDamageable hp = hit.GetComponent(); // 가져올거에요 -> 데미지 인터페이스를 if (hp != null) hp.TakeDamage(explosionDamage); // 입힐거에요 -> 폭발 데미지를 } } currentHP = 0; // 설정할거에요 -> 체력을 0으로 // ⭐ 자폭 사망은 경험치 없음 — 날먹 방지 // 일반 Die()는 OnMonsterKilled(expReward)를 호출해서 경험치를 줌 // DieWithoutExp()는 expReward를 0으로 바꿔서 사망 처리 후 복구 DieWithoutExp(); // 실행할거에요 -> 경험치 없는 사망 처리를 } private void DieWithoutExp() // 함수를 선언할거에요 -> 경험치 없이 사망 처리를 { int savedExp = expReward; // 저장할거에요 -> 기존 경험치 보상을 expReward = 0; // 설정할거에요 -> 경험치를 0으로 (Die() 내부에서 Invoke될 때 0 전달됨) Die(); // 실행할거에요 -> 사망 처리를 (경험치 0으로) expReward = savedExp; // 복구할거에요 -> 원래 경험치 보상을 (풀에서 재사용 시 정상 작동) } // ───────────────────────────────────────────────────────── // 디버그 기즈모 // ───────────────────────────────────────────────────────── protected override void OnDrawGizmosSelected() // 함수를 선언할거에요 -> 기즈모 오버라이드를 { base.OnDrawGizmosSelected(); // 실행할거에요 -> 부모 기즈모를 먼저 if (!showDebugGizmos) return; // 중단할거에요 -> 기즈모 꺼져있으면 Gizmos.color = new Color(1f, 0.5f, 0f, 0.15f); // 설정할거에요 -> 주황 반투명을 Gizmos.DrawSphere(transform.position, triggerRange); // 그릴거에요 -> 점화 트리거 범위를 Gizmos.color = new Color(1f, 0.5f, 0f, 1f); // 설정할거에요 -> 주황 외곽선을 Gizmos.DrawWireSphere(transform.position, triggerRange); // 그릴거에요 -> 점화 범위 외곽선을 Gizmos.color = new Color(1f, 0f, 0f, 0.10f); // 설정할거에요 -> 빨강 반투명을 Gizmos.DrawSphere(transform.position, explodeRange); // 그릴거에요 -> 폭발 범위를 Gizmos.color = Color.red; // 설정할거에요 -> 빨강 외곽선을 Gizmos.DrawWireSphere(transform.position, explodeRange); // 그릴거에요 -> 폭발 범위 외곽선을 #if UNITY_EDITOR if (Application.isPlaying && hasExploded) // 조건이 맞으면 실행할거에요 -> 점화됐으면 UnityEditor.Handles.Label(transform.position + Vector3.up * 0.5f, "🔥 점화!"); // 표시할거에요 #endif } }