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