Projext/Assets/Scripts/Enemy/AI/ExplodeMonster.cs

208 lines
13 KiB
C#
Raw Normal View History

2026-02-20 16:39:14 +00:00
using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을
2026-02-13 09:11:54 +00:00
using System.Collections; // 코루틴을 사용할거에요 -> System.Collections를
public class ExplodeMonster : MonsterClass // 클래스를 선언할거에요 -> 자폭 몬스터를
2026-02-04 14:06:25 +00:00
{
2026-02-13 09:11:54 +00:00
[Header("=== 자폭 설정 ===")] // 인스펙터 제목을 달거에요 -> 자폭 설정을
2026-02-19 18:14:55 +00:00
[SerializeField] private float explodeRange = 4f; // 변수를 선언할거에요 -> 폭발 범위를
[SerializeField] private float triggerRange = 2.5f; // 변수를 선언할거에요 -> 자폭 감지 거리를
[SerializeField] private float fuseTime = 1.5f; // 변수를 선언할거에요 -> 점화 후 폭발까지 대기 시간을
[SerializeField] private float explosionDamage = 50f; // 변수를 선언할거에요 -> 폭발 데미지를
2026-02-04 14:06:25 +00:00
2026-02-19 18:14:55 +00:00
[Header("이펙트")] // 인스펙터 제목을 달거에요 -> 이펙트 설정을
[SerializeField] private GameObject explosionEffectPrefab; // 변수를 선언할거에요 -> 폭발 이펙트 프리팹을
[SerializeField] private ParticleSystem fuseEffect; // 변수를 선언할거에요 -> 도화선 파티클을
[SerializeField] private AudioClip fuseSound; // 변수를 선언할거에요 -> 도화선 소리를
[SerializeField] private AudioClip explosionSound; // 변수를 선언할거에요 -> 폭발 소리를
2026-02-13 09:11:54 +00:00
2026-02-19 18:14:55 +00:00
[Header("애니메이션")] // 인스펙터 제목을 달거에요 -> 애니메이션 State 이름을
[SerializeField] private string runAnim = "scavenger_run"; // 변수를 선언할거에요 -> 달리기 State 이름을
[SerializeField] private string fuseAnim = "scavenger_Fuse"; // 변수를 선언할거에요 -> 점화 State 이름을
2026-02-13 09:11:54 +00:00
2026-02-19 18:14:55 +00:00
[Header("=== 디버그 ===")] // 인스펙터 제목을 달거에요 -> 디버그 설정을
[SerializeField] private bool showExplodeDebugLog = true; // 변수를 선언할거에요 -> 디버그 로그 on/off를
2026-02-04 14:06:25 +00:00
2026-02-19 18:14:55 +00:00
private bool hasExploded = false; // 변수를 선언할거에요 -> 폭발 완료 여부를
2026-02-20 16:39:14 +00:00
// ─────────────────────────────────────────────────────────
// 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(); // 실행할거에요 -> 추격을
}
2026-02-19 18:14:55 +00:00
private string _lastLoggedState = ""; // 변수를 선언할거에요 -> 직전 로그 State 이름을
// ─────────────────────────────────────────────────────────
// 초기화
// ─────────────────────────────────────────────────────────
protected override void Init() // 함수를 실행할거에요 -> 추가 초기화를
2026-02-04 14:06:25 +00:00
{
2026-02-19 18:14:55 +00:00
if (agent != null) agent.stoppingDistance = 0.5f; // 설정할거에요 -> 정지 거리를 짧게
2026-02-04 14:06:25 +00:00
}
2026-02-19 18:14:55 +00:00
protected override void OnResetStats() // 함수를 정의할거에요 -> 재활성화 시 추가 초기화를
2026-02-04 14:06:25 +00:00
{
2026-02-19 18:14:55 +00:00
hasExploded = false; // 초기화할거에요 -> 폭발 상태를
_lastLoggedState = ""; // 초기화할거에요 -> 디버그 State 캐시를
}
2026-02-04 14:06:25 +00:00
2026-02-19 18:14:55 +00:00
// ─────────────────────────────────────────────────────────
// 매 프레임 State 감시 (디버그)
// ─────────────────────────────────────────────────────────
2026-02-04 14:06:25 +00:00
2026-02-19 18:14:55 +00:00
public override void OnManagedUpdate() // 함수를 정의할거에요 -> 매 프레임 상태 감시를
{
// ⭐ IsName 진단 로그 — 어떤 State가 실제로 재생 중인지 확인용
if (showExplodeDebugLog && animator != null && animator.isInitialized) // 조건이 맞으면 실행할거에요 -> 디버그 켜져있고 애니메이터 준비됐으면
2026-02-04 14:06:25 +00:00
{
2026-02-19 18:14:55 +00:00
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")}"
); // 출력할거에요 -> 두 가지 형식 모두 확인
2026-02-04 14:06:25 +00:00
}
2026-02-19 18:14:55 +00:00
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 호출 없음)
if (hitEffect != null) hitEffect.Play(); // 재생할거에요 -> 피격 이펙트를
if (hitSound != null && audioSource != null) audioSource.PlayOneShot(hitSound); // 재생할거에요 -> 소리를
if (currentHP <= 0) // 조건이 맞으면 실행할거에요 -> 사망했으면
2026-02-04 14:06:25 +00:00
{
2026-02-19 18:14:55 +00:00
StopAllCoroutines(); // 중단할거에요 -> 자폭 코루틴을
Die(); // 실행할거에요 -> 사망 처리를
2026-02-04 14:06:25 +00:00
}
}
{
2026-02-19 18:14:55 +00:00
if (agent != null && agent.isOnNavMesh) // 조건이 맞으면 실행할거에요 -> 에이전트가 정상이면
2026-02-04 14:06:25 +00:00
{
2026-02-19 18:14:55 +00:00
agent.isStopped = false; // 켤거에요 -> 이동을
agent.SetDestination(playerTransform.position); // 설정할거에요 -> 목적지를
2026-02-04 14:06:25 +00:00
}
2026-02-19 18:14:55 +00:00
if (animator == null || !animator.isInitialized) return; // 중단할거에요 -> 애니메이터가 없으면
if (!animator.GetCurrentAnimatorStateInfo(0).IsName(runAnim)) // 조건이 맞으면 실행할거에요 -> 달리기 State가 아니면
animator.Play(runAnim, 0, 0f); // 재생할거에요 -> 달리기 애니를
2026-02-20 16:39:14 +00:00
}
2026-02-04 14:06:25 +00:00
2026-02-19 18:14:55 +00:00
// ─────────────────────────────────────────────────────────
// 자폭 코루틴
// ─────────────────────────────────────────────────────────
private IEnumerator ExplodeRoutine() // 코루틴을 정의할거에요 -> 자폭 과정을
2026-02-04 14:06:25 +00:00
{
2026-02-19 18:14:55 +00:00
isAttacking = true; // 설정할거에요 -> 공격 중으로
hasExploded = true; // 설정할거에요 -> 폭발 됨으로
if (agent != null && agent.isOnNavMesh) // 조건이 맞으면 실행할거에요 -> 에이전트가 정상이면
{ agent.isStopped = true; agent.velocity = Vector3.zero; } // 멈출거에요 -> 이동을
2026-02-04 14:06:25 +00:00
2026-02-19 18:14:55 +00:00
if (showExplodeDebugLog) Debug.Log($"[ExplodeMonster] 🔥 점화!"); // 출력할거에요
2026-02-04 14:06:25 +00:00
2026-02-19 18:14:55 +00:00
if (animator != null && animator.isInitialized) // 조건이 맞으면 실행할거에요 -> 애니메이터가 있으면
animator.Play(fuseAnim, 0, 0f); // 재생할거에요 -> 점화 애니를
2026-02-04 14:06:25 +00:00
2026-02-19 18:14:55 +00:00
if (fuseEffect != null) fuseEffect.Play(); // 재생할거에요 -> 도화선 파티클을
if (fuseSound != null && audioSource != null) audioSource.PlayOneShot(fuseSound); // 재생할거에요 -> 도화선 소리를
2026-02-04 14:06:25 +00:00
2026-02-19 18:14:55 +00:00
yield return new WaitForSeconds(fuseTime); // 기다릴거에요 -> 점화 대기 시간만큼
if (showExplodeDebugLog) Debug.Log($"[ExplodeMonster] 💥 폭발!"); // 출력할거에요
Explode(); // 실행할거에요 -> 폭발 처리를
2026-02-04 14:06:25 +00:00
}
2026-02-19 18:14:55 +00:00
// ─────────────────────────────────────────────────────────
// 폭발 처리
// ─────────────────────────────────────────────────────────
2026-02-13 09:11:54 +00:00
private void Explode() // 함수를 선언할거에요 -> 실제 폭발 로직을
2026-02-04 14:06:25 +00:00
{
2026-02-19 18:14:55 +00:00
if (explosionEffectPrefab != null) // 조건이 맞으면 실행할거에요 -> 이펙트 프리팹이 있으면
Instantiate(explosionEffectPrefab, transform.position, Quaternion.identity); // 생성할거에요 -> 폭발 이펙트를
if (explosionSound != null) // 조건이 맞으면 실행할거에요 -> 폭발 소리가 있으면
AudioSource.PlayClipAtPoint(explosionSound, transform.position); // 재생할거에요 -> 폭발 소리를
2026-02-05 15:42:48 +00:00
2026-02-19 18:14:55 +00:00
Collider[] hits = Physics.OverlapSphere(transform.position, explodeRange); // 가져올거에요 -> 폭발 범위 내 충돌체들을
2026-02-13 09:11:54 +00:00
foreach (var hit in hits) // 반복할거에요 -> 충돌체마다
2026-02-04 14:06:25 +00:00
{
2026-02-13 09:11:54 +00:00
if (hit.CompareTag("Player")) // 조건이 맞으면 실행할거에요 -> 플레이어라면
2026-02-04 14:06:25 +00:00
{
2026-02-19 18:14:55 +00:00
IDamageable hp = hit.GetComponent<IDamageable>(); // 가져올거에요 -> 데미지 인터페이스를
if (hp != null) hp.TakeDamage(explosionDamage); // 입힐거에요 -> 폭발 데미지를
2026-02-04 14:06:25 +00:00
}
}
2026-02-19 18:14:55 +00:00
currentHP = 0; // 설정할거에요 -> 체력을 0으로
Die(); // 실행할거에요 -> 사망 처리를
2026-02-04 14:06:25 +00:00
}
2026-02-19 18:14:55 +00:00
// ─────────────────────────────────────────────────────────
// 디버그 기즈모
// ─────────────────────────────────────────────────────────
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
}
2026-02-05 15:42:48 +00:00
}