Projext/Assets/Scripts/Enemy/AI/ExplodeMonster.cs
2026-02-06 00:42:48 +09:00

240 lines
8.6 KiB
C#

using UnityEngine;
using UnityEngine.AI;
using System.Collections;
/// <summary>
/// 자폭 몬스터 (Kamikaze)
/// - 플레이어에게 전력 질주
/// - 범위 내 진입 시 제자리에서 카운트다운 후 폭발
/// </summary>
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("애니메이션")]
[SerializeField] private string runAnimation = "Monster_Run"; // 달리기
[SerializeField] private string fuseAnimation = "Monster_Fuse"; // 부들부들(폭발 준비)
[Header("AI 설정")]
[SerializeField] private float chaseSpeed = 6f; // 이동 속도 (다른 애들보다 빨라야 무서움)
[SerializeField] private float patrolRadius = 5f;
[SerializeField] private float patrolInterval = 2f;
private bool isExploding = false; // 폭발 진행 중?
private bool hasExploded = false; // 이미 터졌나?
private float nextPatrolTime;
private bool isPlayerInZone;
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 초기화
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
protected override void Init()
{
if (agent != null)
{
agent.speed = chaseSpeed; // 속도 설정
agent.stoppingDistance = 0.5f; // 바짝 붙음
}
if (animator != null) animator.applyRootMotion = false;
if (fuseEffect != null) fuseEffect.Stop();
}
protected override void OnResetStats()
{
isExploding = false;
hasExploded = false;
if (fuseEffect != null) fuseEffect.Stop();
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// AI 로직
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
protected override void ExecuteAILogic()
{
if (isHit || isExploding || hasExploded) return;
float distance = Vector3.Distance(transform.position, playerTransform.position);
// 플레이어가 감지 범위(15m) 안에 있거나 트리거에 닿았으면 추격
if (isPlayerInZone || distance <= 15f)
{
ChasePlayer(distance);
}
else
{
Patrol();
UpdateMovementAnimation();
}
}
void ChasePlayer(float distance)
{
// 1. 폭발 시작 거리 안으로 들어왔다? -> 점화!
if (distance <= triggerRange)
{
StartCoroutine(ExplodeRoutine());
return;
}
// 2. 아직 멀었다? -> 전력 질주
if (agent.isOnNavMesh)
{
agent.isStopped = false;
agent.SetDestination(playerTransform.position);
// 달리기 애니메이션
animator.Play(runAnimation);
}
}
void UpdateMovementAnimation()
{
if (isExploding || isHit) return;
if (agent.velocity.magnitude > 0.1f)
animator.Play(runAnimation);
else
animator.Play(Monster_Idle);
}
void Patrol()
{
if (Time.time < nextPatrolTime) return;
Vector3 randomPoint = transform.position + new Vector3(
Random.Range(-patrolRadius, patrolRadius),
0,
Random.Range(-patrolRadius, patrolRadius)
);
if (NavMesh.SamplePosition(randomPoint, out NavMeshHit hit, 3f, NavMesh.AllAreas))
if (agent.isOnNavMesh) agent.SetDestination(hit.position);
nextPatrolTime = Time.time + patrolInterval;
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 💣 폭발 시퀀스 (점화 -> 대기 -> 쾅!)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
IEnumerator ExplodeRoutine()
{
if (hasExploded) yield break;
isExploding = true;
hasExploded = true;
isAttacking = true; // 부모 클래스 간섭 방지
// ⭐ [핵심] 급브레이크! (문워크 방지)
if (agent.isOnNavMesh)
{
agent.isStopped = true;
agent.ResetPath(); // 목적지 삭제
agent.velocity = Vector3.zero; // 속도 0
}
Debug.Log($"💣 자폭 카운트다운! ({fuseTime}초 뒤 폭발)");
// 1. 부들부들 떨기 (폭발 준비 모션)
if (!string.IsNullOrEmpty(fuseAnimation)) animator.Play(fuseAnimation);
// 2. 치익~ 소리 & 이펙트
if (fuseEffect != null) fuseEffect.Play();
if (fuseSound != null && audioSource != null) audioSource.PlayOneShot(fuseSound);
// 3. 도망갈 시간 주기
yield return new WaitForSeconds(fuseTime);
// 4. 쾅!
PerformExplosion();
}
void PerformExplosion()
{
Debug.Log("💥💥💥 쾅!!!");
// 폭발 이펙트 생성 (Particle System)
if (explosionEffectPrefab != null)
{
Instantiate(explosionEffectPrefab, transform.position, Quaternion.identity);
}
// 폭발음
if (explosionSound != null)
{
AudioSource.PlayClipAtPoint(explosionSound, transform.position, 1f);
}
// 주변 데미지 처리
DamageNearbyTargets();
// 몬스터 사망 처리 (MonsterClass 기능 사용)
Die();
}
void DamageNearbyTargets()
{
// 폭발 범위(Sphere) 안에 있는 모든 물체 검사
Collider[] hitColliders = Physics.OverlapSphere(transform.position, explodeRange);
foreach (Collider hit in hitColliders)
{
// 플레이어 데미지
if (hit.CompareTag("Player"))
{
if (hit.TryGetComponent<PlayerHealth>(out var playerHealth))
{
if (!playerHealth.isInvincible)
{
playerHealth.TakeDamage(explosionDamage);
Debug.Log($"💥 플레이어 폭사! 데미지: {explosionDamage}");
}
}
}
// (선택사항) 주변 몬스터도 팀킬?
else if (hit.CompareTag("Enemy") && hit.gameObject != gameObject)
{
// 팀킬 로직이 필요하면 여기에 추가
}
}
}
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 유틸리티
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 자폭병은 때려도 폭발 안 멈춤 (취향에 따라 수정 가능)
protected override void OnStartHit() { }
// 죽을 때 퓨즈 끄기
protected override void OnDie()
{
if (fuseEffect != null && fuseEffect.isPlaying) fuseEffect.Stop();
}
private void OnTriggerEnter(Collider other) { if (other.CompareTag("Player")) isPlayerInZone = true; }
private void OnTriggerExit(Collider other) { if (other.CompareTag("Player")) isPlayerInZone = false; }
// 에디터에서 범위 보여주기
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, triggerRange); // 감지 범위
Gizmos.color = new Color(1f, 0.5f, 0f, 0.3f);
Gizmos.DrawSphere(transform.position, explodeRange); // 폭발 데미지 범위
}
}