using UnityEngine; using UnityEngine.AI; using System.Collections; /// /// 돌진 공격 몬스터 (두 번째 돌진 고장 수정판) /// - Agent On/Off 타이밍 완벽하게 정리 /// public class ChargeMonster : MonsterClass { [Header("=== 돌진 공격 설정 ===")] [SerializeField] private float chargeSpeed = 15f; // 돌진 속도 [SerializeField] private float chargeRange = 10f; // 돌진 시작 거리 [SerializeField] private float chargeDuration = 2f; // 돌진 지속 시간 [SerializeField] private float chargeDelay = 3f; // 돌진 쿨타임 [SerializeField] private float prepareTime = 0.5f; // 돌진 준비 시간 private float lastChargeTime; private bool isCharging = false; private bool isPreparing = false; private Vector3 chargeDirection; [Header("공격 / 이동 애니메이션")] [SerializeField] private string chargeAnimation = "Monster_Charge"; [SerializeField] private string prepareAnimation = "Monster_ChargePrepare"; [SerializeField] private string Monster_Walk = "Monster_Walk"; [Header("AI 상세 설정")] [SerializeField] private float patrolRadius = 5f; [SerializeField] private float patrolInterval = 2f; private float nextPatrolTime; private bool isPlayerInZone; private Rigidbody _rigidbody; protected override void Awake() { base.Awake(); _rigidbody = GetComponent(); if (_rigidbody == null) _rigidbody = gameObject.AddComponent(); _rigidbody.isKinematic = true; } protected override void Init() { if (agent != null) agent.stoppingDistance = 1f; if (animator != null) animator.applyRootMotion = false; } protected override void ExecuteAILogic() { if (isHit || isCharging || isPreparing) return; float distance = Vector3.Distance(transform.position, playerTransform.position); if (isPlayerInZone || distance <= chargeRange * 1.5f) { HandleChargeCombat(distance); } else { Patrol(); } UpdateMovementAnimation(); } void HandleChargeCombat(float distance) { // 쿨타임 체크 & 사거리 체크 if (distance <= chargeRange && Time.time >= lastChargeTime + chargeDelay) { StartCoroutine(PrepareCharge()); } // 추적 모드 else if (!isCharging && !isPreparing) { if (agent.isOnNavMesh) { // ⭐ [수정] 추적 시작할 때 "나 멈춰있니?" 확인하고 풀어주기 if (agent.isStopped) agent.isStopped = false; agent.SetDestination(playerTransform.position); } } } IEnumerator PrepareCharge() { isPreparing = true; // 이동 완전 정지 if (agent.isOnNavMesh) { agent.isStopped = true; agent.ResetPath(); agent.velocity = Vector3.zero; } // 방향 설정 chargeDirection = (playerTransform.position - transform.position).normalized; chargeDirection.y = 0; if (chargeDirection != Vector3.zero) transform.rotation = Quaternion.LookRotation(chargeDirection); // 준비 애니메이션 if (!string.IsNullOrEmpty(prepareAnimation)) animator.Play(prepareAnimation, 0, 0f); Debug.Log("[ChargeMonster] 돌진 준비..."); yield return new WaitForSeconds(prepareTime); StartCoroutine(Charge()); } IEnumerator Charge() { isPreparing = false; isCharging = true; lastChargeTime = Time.time; // ⭐ [핵심 수정] 조건 없이 강제로 끄기! (NavMesh 위에 있든 말든 일단 꺼야 물리 이동 가능) if (agent != null) agent.enabled = false; // 물리 켜기 if (_rigidbody != null) _rigidbody.isKinematic = false; animator.Play(chargeAnimation, 0, 0f); Debug.Log("[ChargeMonster] 돌진 시작!"); float chargeStartTime = Time.time; while (Time.time < chargeStartTime + chargeDuration) { // 돌진 중에도 방향을 잃지 않도록 if (_rigidbody != null) { _rigidbody.velocity = chargeDirection * chargeSpeed; } else { transform.position += chargeDirection * chargeSpeed * Time.deltaTime; } yield return null; } // 돌진 끝 정리 if (_rigidbody != null) { _rigidbody.velocity = Vector3.zero; _rigidbody.isKinematic = true; } // Agent 다시 켜기 if (agent != null) { agent.enabled = true; // 켜지자마자 미끄러짐 방지 agent.ResetPath(); agent.velocity = Vector3.zero; } isCharging = false; Debug.Log("[ChargeMonster] 돌진 종료, 쿨타임 시작"); } void UpdateMovementAnimation() { if (isCharging || isPreparing || isHit || isResting) return; // Agent가 켜져 있고 움직일 때만 걷기 모션 if (agent.enabled && agent.velocity.magnitude > 0.1f) animator.Play(Monster_Walk); 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; } private void OnCollisionEnter(Collision collision) { if (!isCharging) return; if (collision.gameObject.CompareTag("Player")) { if (collision.gameObject.TryGetComponent(out var playerHealth)) { if (!playerHealth.isInvincible) { playerHealth.TakeDamage(attackDamage); Debug.Log($"[ChargeMonster] 쾅! 데미지: {attackDamage}"); } } } } private void OnTriggerEnter(Collider other) { if (other.CompareTag("Player")) isPlayerInZone = true; } private void OnTriggerExit(Collider other) { if (other.CompareTag("Player")) isPlayerInZone = false; } }