using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; using System; public class NormalMonster : MonsterClass { [Header("전투")] [SerializeField] private float damage = 10f; [SerializeField] private float attackRange = 2f; [SerializeField] private float attackDelay = 1.5f; // [SerializeField] private float postAttackDelay = 1.5f; // ⭐ MonsterClass의 attackRestDuration을 사용하므로 삭제 권장 private float lastAttackTime; [Header("공격 애니메이션")] [SerializeField] private string[] attackAnimations = { "Monster_Attack_1" }; [Header("이동 애니메이션")] [SerializeField] private string Monster_Idle = "Monster_Idle"; [SerializeField] private string Monster_Walk = "Monster_Walk"; [Header("AI")] [SerializeField] private float stopBuffer = 0.3f; [SerializeField] private float patrolRadius = 5f; [SerializeField] private float patrolInterval = 2f; private float nextPatrolTime; private float repathInterval = 0.3f; private float nextRepathTime; private Transform player; private int attackIndex; // ❌ [삭제] bool isAttacking; // ⭐ 부모(MonsterClass)에 이미 있으므로 자식에서 또 선언하면 에러가 납니다! private bool isPlayerInZone; protected override void Start() { base.Start(); // 부모의 Start 실행 player = GameObject.FindWithTag("Player")?.transform; agent.stoppingDistance = attackRange - 0.4f; animator.applyRootMotion = false; } protected override void Update() { // ⭐ 부모의 Update(이동 제한 로직)를 먼저 실행합니다. base.Update(); if (isDead || player == null) return; // 피격 중이거나 공격 중, 혹은 쉬는 중(isResting)에는 AI 로직을 타지 않습니다. if (isHit || isAttacking || isResting) return; if (isPlayerInZone) HandlePlayerTarget(); else Patrol(); UpdateMovementAnimation(); } void HandlePlayerTarget() { float distance = Vector3.Distance(transform.position, player.position); // 공격 범위 안에 들어왔을 때 if (distance <= attackRange - stopBuffer) { TryAttack(); } else if (Time.time >= nextRepathTime) { agent.SetDestination(player.position); nextRepathTime = Time.time + repathInterval; } } void TryAttack() { if (Time.time < lastAttackTime + attackDelay) return; lastAttackTime = Time.time; string attackName = attackAnimations[attackIndex]; attackIndex = (attackIndex + 1) % attackAnimations.Length; // ⭐ 부모의 OnAttackStart를 호출하여 isAttacking을 true로 만들고 이동을 멈춥니다. OnAttackStart(); animator.Play(attackName, 0, 0f); } // ⭐ [수정] 부모의 가상 메서드를 오버라이드합니다. public override void OnAttackStart() { base.OnAttackStart(); // 부모의 isAttacking = true 로직 실행 } // ⭐ [수정] 부모의 가상 메서드를 오버라이드하여 '휴식' 기능을 활성화합니다. public override void OnAttackEnd() { base.OnAttackEnd(); // 부모의 'n초간 휴식' 코루틴을 실행합니다! } public void OnAttackHit() // 애니메이션 이벤트 (공격 판정 시점) { if (player == null || isHit || isDead) return; PlayerHealth playerHealth = player.GetComponent(); if (playerHealth != null) playerHealth.TakeDamage(damage); } void UpdateMovementAnimation() { if (isAttacking || isHit || isResting) return; if (agent.velocity.magnitude < 0.1f) animator.Play(Monster_Idle); else animator.Play(Monster_Walk); } void Patrol() { if (Time.time < nextPatrolTime) return; Vector3 randomPoint = transform.position + new Vector3(UnityEngine.Random.Range(-patrolRadius, patrolRadius), 0, UnityEngine.Random.Range(-patrolRadius, patrolRadius)); if (NavMesh.SamplePosition(randomPoint, out NavMeshHit hit, 3f, NavMesh.AllAreas)) agent.SetDestination(hit.position); nextPatrolTime = Time.time + patrolInterval; } private void OnTriggerEnter(Collider other) { if (other.CompareTag("Player")) isPlayerInZone = true; } private void OnTriggerExit(Collider other) { if (other.CompareTag("Player")) isPlayerInZone = false; } }