221 lines
14 KiB
C#
221 lines
14 KiB
C#
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<IDamageable>(); // 가져올거에요 -> 데미지 인터페이스를
|
|
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
|
|
}
|
|
} |