310 lines
11 KiB
C#
310 lines
11 KiB
C#
using UnityEngine;
|
|
using UnityEngine.AI;
|
|
using System.Collections;
|
|
|
|
/// <summary>
|
|
/// 자폭 몬스터
|
|
/// - 플레이어에게 빠르게 접근
|
|
/// - 일정 거리 내에 들어오면 폭발
|
|
/// - 범위 내 모든 대상에게 데미지
|
|
/// - 자신도 사망
|
|
/// </summary>
|
|
public class ExplodeMonster : MonsterClass
|
|
{
|
|
[Header("=== 자폭 설정 ===")]
|
|
[SerializeField] private float explodeRange = 3f; // 폭발 범위
|
|
[SerializeField] private float triggerRange = 2f; // 폭발 시작 거리
|
|
[SerializeField] private float fuseTime = 1.5f; // 폭발까지 지연 시간
|
|
[SerializeField] private float explosionDamage = 50f; // 폭발 데미지 (기본 공격력 무시)
|
|
[SerializeField] private bool damagesSelf = true; // 자신도 죽는가?
|
|
|
|
[Header("폭발 효과")]
|
|
[SerializeField] private GameObject explosionEffectPrefab; // 폭발 이펙트
|
|
[SerializeField] private ParticleSystem fuseEffect; // 퓨즈 불꽃 효과
|
|
[SerializeField] private AudioClip fuseSound; // 치익~ 소리
|
|
[SerializeField] private AudioClip explosionSound; // 폭발음
|
|
[SerializeField] private float cameraShakeIntensity = 0.5f; // 화면 흔들림
|
|
|
|
[Header("애니메이션")]
|
|
[SerializeField] private string runAnimation = "Monster_Run"; // 달리기
|
|
[SerializeField] private string fuseAnimation = "Monster_Fuse"; // 폭발 준비
|
|
[SerializeField] private string explodeAnimation = "Monster_Explode"; // 폭발
|
|
|
|
[Header("AI 설정")]
|
|
[SerializeField] private float chaseSpeed = 5f; // 추격 속도 (빠르게!)
|
|
[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);
|
|
|
|
// 플레이어 발견하면 무조건 돌진!
|
|
if (isPlayerInZone || distance <= 15f)
|
|
{
|
|
ChasePlayer(distance);
|
|
}
|
|
else
|
|
{
|
|
Patrol();
|
|
}
|
|
|
|
UpdateMovementAnimation();
|
|
}
|
|
|
|
void ChasePlayer(float distance)
|
|
{
|
|
// 폭발 범위 안에 들어오면 폭발!
|
|
if (distance <= triggerRange)
|
|
{
|
|
StartCoroutine(Explode());
|
|
return;
|
|
}
|
|
|
|
// 플레이어에게 전력 질주!
|
|
if (agent.isOnNavMesh)
|
|
{
|
|
agent.SetDestination(playerTransform.position);
|
|
}
|
|
}
|
|
|
|
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 Explode()
|
|
{
|
|
if (hasExploded) yield break;
|
|
|
|
isExploding = true;
|
|
hasExploded = true;
|
|
|
|
// 이동 멈춤
|
|
if (agent.isOnNavMesh)
|
|
{
|
|
agent.isStopped = true;
|
|
agent.velocity = Vector3.zero;
|
|
}
|
|
|
|
Debug.Log($"[ExplodeMonster] 💣 폭발 카운트다운 시작! ({fuseTime}초)");
|
|
|
|
// 폭발 준비 애니메이션
|
|
if (!string.IsNullOrEmpty(fuseAnimation))
|
|
{
|
|
animator.Play(fuseAnimation, 0, 0f);
|
|
}
|
|
|
|
// 퓨즈 이펙트 시작
|
|
if (fuseEffect != null)
|
|
{
|
|
fuseEffect.Play();
|
|
}
|
|
|
|
// 치익~ 소리
|
|
if (fuseSound != null && audioSource != null)
|
|
{
|
|
audioSource.PlayOneShot(fuseSound);
|
|
}
|
|
|
|
// 대기 (퓨즈 타는 시간)
|
|
yield return new WaitForSeconds(fuseTime);
|
|
|
|
// 💥 폭발!
|
|
PerformExplosion();
|
|
}
|
|
|
|
void PerformExplosion()
|
|
{
|
|
Debug.Log("[ExplodeMonster] 💥💥💥 폭발!!!");
|
|
|
|
// 폭발 애니메이션
|
|
if (!string.IsNullOrEmpty(explodeAnimation))
|
|
{
|
|
animator.Play(explodeAnimation, 0, 0f);
|
|
}
|
|
|
|
// 폭발 이펙트 생성
|
|
if (explosionEffectPrefab != null)
|
|
{
|
|
Instantiate(explosionEffectPrefab, transform.position, Quaternion.identity);
|
|
}
|
|
|
|
// 폭발음
|
|
if (explosionSound != null)
|
|
{
|
|
AudioSource.PlayClipAtPoint(explosionSound, transform.position, 1f);
|
|
}
|
|
|
|
//// 화면 흔들림 (CameraShake가 있다면)
|
|
//CameraShake cameraShake = Camera.main?.GetComponent<CameraShake>();
|
|
//if (cameraShake != null)
|
|
//{
|
|
// cameraShake.Shake(cameraShakeIntensity, 0.5f);
|
|
//}
|
|
|
|
// 범위 내 모든 대상에게 데미지
|
|
DamageNearbyTargets();
|
|
|
|
// 자신도 사망
|
|
if (damagesSelf)
|
|
{
|
|
Die();
|
|
}
|
|
}
|
|
|
|
void DamageNearbyTargets()
|
|
{
|
|
// 폭발 범위 내 모든 Collider 찾기
|
|
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($"[ExplodeMonster] 💥 플레이어 적중! 데미지: {explosionDamage}");
|
|
}
|
|
}
|
|
}
|
|
// 다른 몬스터도 데미지 받게 하려면 (선택사항)
|
|
else if (hit.CompareTag("Enemy") && hit.gameObject != gameObject)
|
|
{
|
|
if (hit.TryGetComponent<IDamageable>(out var damageable))
|
|
{
|
|
damageable.TakeDamage(explosionDamage * 0.5f); // 절반 데미지
|
|
Debug.Log($"[ExplodeMonster] 💥 다른 몬스터도 피해! {hit.name}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
// 피격 시: 즉시 폭발! (선택사항)
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
protected override void OnStartHit()
|
|
{
|
|
// 선택사항: 맞으면 바로 폭발하게 하려면 주석 해제
|
|
// if (!hasExploded && !isExploding)
|
|
// {
|
|
// StartCoroutine(Explode());
|
|
// }
|
|
}
|
|
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
// 사망 시: 폭발 효과 정리
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
protected override void OnDie()
|
|
{
|
|
if (fuseEffect != null && fuseEffect.isPlaying)
|
|
{
|
|
fuseEffect.Stop();
|
|
}
|
|
}
|
|
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
// Trigger 감지
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
private void OnTriggerEnter(Collider other)
|
|
{
|
|
if (other.CompareTag("Player")) isPlayerInZone = true;
|
|
}
|
|
|
|
private void OnTriggerExit(Collider other)
|
|
{
|
|
if (other.CompareTag("Player")) isPlayerInZone = false;
|
|
}
|
|
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
// Gizmos (폭발 범위 시각화)
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
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);
|
|
|
|
// 외곽선
|
|
Gizmos.color = Color.yellow;
|
|
Gizmos.DrawWireSphere(transform.position, explodeRange);
|
|
}
|
|
}
|