using System.Collections; using UnityEngine; using UnityEngine.AI; [RequireComponent(typeof(NavMeshAgent))] [RequireComponent(typeof(Animator))] public class EnemyAI : MonoBehaviour, IPauseable { public enum AIState { Wander, Chase, Attack, Stun } [Header("現在の状態")] [SerializeField] private AIState currentState = AIState.Wander; [Header("索敵設定")] [SerializeField] private float detectionRange = 10f; [SerializeField] private float loseRange = 15f; [SerializeField] private string playerTag = "Player"; [Header("攻撃設定")] [SerializeField] private float attackRange = 1.5f; [SerializeField] private float attackCooldown = 2.0f; [SerializeField] private float telegraphDuration = 0.6f; [SerializeField] private Collider rightHandCollider; [SerializeField] private float comboInterval = 0.2f; [Header("徘徊設定")] [SerializeField] private float wanderRadius = 8f; [SerializeField] private float minWaitTime = 2f; [SerializeField] private float maxWaitTime = 4f; [SerializeField] private float wanderTimeout = 7f; private NavMeshAgent agent; private Animator anim; private Transform playerTransform; private bool isCooldown; private bool isAttacking; private bool isWandering; private bool isKnockbacking; private bool isDead; private bool isPaused; private static readonly int SpeedHash = Animator.StringToHash("Speed"); private static readonly int StunHash = Animator.StringToHash("Stun"); private static readonly int HitHash = Animator.StringToHash("Hit"); private static readonly int TelegraphHash = Animator.StringToHash("Telegraph"); private static readonly int AttackHash = Animator.StringToHash("Attack"); private void Awake() { agent = GetComponent(); anim = GetComponent(); } private void Start() { SetFistCollider(false); if (WorldPauseManager.Instance != null) { WorldPauseManager.Instance.Register(this); } StartCoroutine(AILoop()); } private void OnDestroy() { if (WorldPauseManager.Instance != null) { WorldPauseManager.Instance.Unregister(this); } } public void Pause() { isPaused = true; if (anim != null) anim.speed = 0f; if (agent != null && agent.enabled && agent.isOnNavMesh) { agent.isStopped = true; } } public void Resume() { isPaused = false; if (anim != null) anim.speed = 1f; if (agent != null && agent.enabled && agent.isOnNavMesh && !isDead && !isKnockbacking && currentState != AIState.Stun) { agent.isStopped = false; } } private IEnumerator WaitForUnpausedSeconds(float duration) { float timer = 0f; while (timer < duration) { if (!isPaused) { timer += Time.deltaTime; } yield return null; } } private void Update() { if (isDead || isPaused) return; UpdateAnimation(); UpdateRotationOnAttack(); } private IEnumerator AILoop() { while (!isDead) { if (isPaused) { yield return null; continue; } if (playerTransform == null) FindPlayer(); if (!isAttacking && !isKnockbacking && currentState != AIState.Stun) { switch (currentState) { case AIState.Wander: WanderBehavior(); break; case AIState.Chase: ChaseBehavior(); break; case AIState.Attack: AttackBehavior(); break; } } yield return WaitForUnpausedSeconds(0.2f); } } private void FindPlayer() { GameObject playerObj = GameObject.FindGameObjectWithTag(playerTag); if (playerObj != null) playerTransform = playerObj.transform; } private void WanderBehavior() { if (playerTransform != null) { float distanceToPlayer = Vector3.Distance(transform.position, playerTransform.position); if (distanceToPlayer <= detectionRange) { currentState = AIState.Chase; isWandering = false; agent.ResetPath(); return; } } if (!isWandering) { StartCoroutine(WanderMoveRoutine()); } } private void ChaseBehavior() { if (playerTransform == null) { currentState = AIState.Wander; return; } float distanceToPlayer = Vector3.Distance(transform.position, playerTransform.position); if (distanceToPlayer <= attackRange) { currentState = AIState.Attack; agent.ResetPath(); return; } if (distanceToPlayer > loseRange) { currentState = AIState.Wander; agent.ResetPath(); return; } if (!isKnockbacking) { agent.SetDestination(playerTransform.position); } } private void AttackBehavior() { if (playerTransform == null) { currentState = AIState.Wander; return; } float distanceToPlayer = Vector3.Distance(transform.position, playerTransform.position); if (distanceToPlayer > attackRange) { currentState = AIState.Chase; return; } if (!isCooldown) { StartCoroutine(PerformPunchAttack()); } } private void UpdateAnimation() { float targetSpeed = 0f; if (!isAttacking && !isKnockbacking && currentState != AIState.Stun && agent.hasPath && agent.velocity.magnitude > 0.1f) { targetSpeed = 1f; } anim.SetFloat(SpeedHash, targetSpeed, 0.1f, Time.deltaTime); } private void UpdateRotationOnAttack() { if (currentState == AIState.Attack && playerTransform != null && !isKnockbacking && currentState != AIState.Stun) { Vector3 direction = (playerTransform.position - transform.position).normalized; direction.y = 0; if (direction != Vector3.zero) { transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), Time.deltaTime * 10f); } } } public void TriggerKnockback(float duration) { if (isDead || currentState == AIState.Stun) return; SetFistCollider(false); StopAllCoroutines(); isAttacking = false; isWandering = false; if (agent.isOnNavMesh) agent.isStopped = false; StartCoroutine(KnockbackRoutine(duration)); } public void TriggerParryStun(float duration) { if (isDead) return; SetFistCollider(false); StopAllCoroutines(); isAttacking = false; isWandering = false; isKnockbacking = false; StartCoroutine(ParryStunRoutine(duration)); } public void DisableAIOnDeath() { isDead = true; StopAllCoroutines(); if (agent != null && agent.isOnNavMesh) { agent.isStopped = true; agent.ResetPath(); } } private IEnumerator ParryStunRoutine(float duration) { currentState = AIState.Stun; if (agent.isOnNavMesh) { agent.isStopped = true; agent.ResetPath(); } anim.SetTrigger(StunHash); yield return WaitForUnpausedSeconds(duration); if (agent.isOnNavMesh) agent.isStopped = false; currentState = AIState.Chase; isCooldown = false; StartCoroutine(AILoop()); } private IEnumerator KnockbackRoutine(float duration) { isKnockbacking = true; if (agent.isOnNavMesh) { agent.isStopped = true; agent.ResetPath(); } anim.SetTrigger(HitHash); yield return WaitForUnpausedSeconds(duration); if (agent.isOnNavMesh) agent.isStopped = false; isKnockbacking = false; isCooldown = false; StartCoroutine(AILoop()); } private IEnumerator WanderMoveRoutine() { isWandering = true; Vector3 newPos = Vector3.zero; bool isPathValid = false; for (int i = 0; i < 10; i++) { newPos = GetRandomNavMeshPoint(transform.position, wanderRadius); NavMeshPath path = new NavMeshPath(); if (agent.CalculatePath(newPos, path) && path.status == NavMeshPathStatus.PathComplete) { isPathValid = true; break; } } if (isPathValid) { agent.SetDestination(newPos); float elapsedTime = 0f; while (agent.remainingDistance > agent.stoppingDistance) { yield return WaitForUnpausedSeconds(0.5f); elapsedTime += 0.5f; if (elapsedTime >= wanderTimeout) { agent.ResetPath(); break; } } } yield return WaitForUnpausedSeconds(Random.Range(minWaitTime, maxWaitTime)); isWandering = false; } private IEnumerator PerformPunchAttack() { isAttacking = true; isCooldown = true; anim.SetTrigger(TelegraphHash); yield return WaitForUnpausedSeconds(telegraphDuration); if (isKnockbacking || isDead) { isAttacking = false; yield break; } anim.SetTrigger(AttackHash); } public void OnPunchHit() { if (isKnockbacking || currentState == AIState.Stun || isPaused) return; SetFistCollider(true); } public void OnRecoveryEnd() { if (isKnockbacking || currentState == AIState.Stun) return; SetFistCollider(false); isAttacking = false; if (playerTransform == null) { currentState = AIState.Wander; isCooldown = false; return; } float distanceToPlayer = Vector3.Distance(transform.position, playerTransform.position); if (distanceToPlayer <= attackRange) { StartCoroutine(ResetCooldownQuickly()); } else { currentState = AIState.Chase; StartCoroutine(ResetCooldown()); } } private IEnumerator ResetCooldownQuickly() { yield return WaitForUnpausedSeconds(comboInterval); isCooldown = false; } private IEnumerator ResetCooldown() { yield return WaitForUnpausedSeconds(attackCooldown); isCooldown = false; } private void SetFistCollider(bool enabledState) { if (rightHandCollider != null) rightHandCollider.enabled = enabledState; } private Vector3 GetRandomNavMeshPoint(Vector3 center, float radius) { Vector3 randomDirection = Random.insideUnitSphere * radius + center; if (NavMesh.SamplePosition(randomDirection, out NavMeshHit hit, radius, NavMesh.AllAreas)) { return hit.position; } return center; } private void OnDrawGizmosSelected() { Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, detectionRange); Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(transform.position, loseRange); Gizmos.color = Color.blue; Gizmos.DrawWireSphere(transform.position, attackRange); } }