Files
VRProject/Assets/_Scripts/EnemyAI.cs
ohgushiyuga 52341100ed otamesi
2026-08-27 12:51:15 +09:00

436 lines
12 KiB
C#

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 attackDamage = 10f;
[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<NavMeshAgent>();
anim = GetComponent<Animator>();
}
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);
}
}