using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; [RequireComponent(typeof(CapsuleCollider))] public class EnemyStateMachine : MonoBehaviour { public enum State { Patrol, Suspicion, Alert } [Header("References")] public Transform[] patrolPoints; public NavMeshAgent agent; public EnemyVision vision; [Header("Movement")] public float patrolSpeed = 2.2f; public float suspicionSpeed = 2.8f; public float alertSpeed = 3.8f; public float manualRotationSpeed = 8f; public float waypointTolerance = 0.7f; [Header("Awareness")] public float hearingRange = 12f; public float suspicionWaitDuration = 2.5f; public float loseSightDelay = 1.3f; public float captureDistance = 1.6f; public int captureDamage = 10; public float captureCooldown = 1f; public State CurrentState { get; private set; } = State.Patrol; public static IReadOnlyList ActiveEnemies => activeEnemies; private static readonly List activeEnemies = new List(); private Transform player; private PlayerHealth playerHealth; private int patrolIndex; private Vector3 investigationTarget; private Vector3 lastKnownPlayerPosition; private float suspicionTimer; private float lastSeenTime = -999f; private float lastCaptureTime = -999f; private float lastHandledNoiseTimestamp = -999f; private void OnEnable() { if (!activeEnemies.Contains(this)) { activeEnemies.Add(this); } } private void OnDisable() { activeEnemies.Remove(this); } private void Awake() { agent = agent != null ? agent : GetComponent(); vision = vision != null ? vision : GetComponent(); } private void Start() { AcquirePlayer(); ApplySpeedForState(CurrentState); } private void Update() { AcquirePlayer(); ListenForNoise(); switch (CurrentState) { case State.Patrol: UpdatePatrol(); break; case State.Suspicion: UpdateSuspicion(); break; case State.Alert: UpdateAlert(); break; } if (CurrentState == State.Alert && Time.time - lastSeenTime > loseSightDelay) { investigationTarget = lastKnownPlayerPosition; EnterState(State.Suspicion); } } public void OnPlayerDetected(Vector3 playerPos) { lastKnownPlayerPosition = playerPos; lastSeenTime = Time.time; if (CurrentState != State.Alert) { EnterState(State.Alert); } } public void OnPlayerLost() { if (CurrentState == State.Alert) { investigationTarget = lastKnownPlayerPosition; } } public void OnNoiseHeard(Vector3 noisePosition) { if (CurrentState == State.Alert) { return; } investigationTarget = noisePosition; suspicionTimer = 0f; EnterState(State.Suspicion); } private void UpdatePatrol() { if (patrolPoints == null || patrolPoints.Length == 0) { return; } Transform targetPoint = patrolPoints[patrolIndex]; MoveTowards(targetPoint.position, patrolSpeed); if (HasReached(targetPoint.position)) { patrolIndex = (patrolIndex + 1) % patrolPoints.Length; } } private void UpdateSuspicion() { MoveTowards(investigationTarget, suspicionSpeed); if (HasReached(investigationTarget)) { suspicionTimer += Time.deltaTime; if (suspicionTimer >= suspicionWaitDuration) { suspicionTimer = 0f; EnterState(State.Patrol); } } else { suspicionTimer = 0f; } } private void UpdateAlert() { if (player == null) { return; } lastKnownPlayerPosition = player.position; MoveTowards(lastKnownPlayerPosition, alertSpeed); if (Vector3.Distance(transform.position, player.position) <= captureDistance && Time.time >= lastCaptureTime + captureCooldown) { lastCaptureTime = Time.time; if (playerHealth != null) { playerHealth.TakeDamage(captureDamage); } Debug.Log("Игрок обнаружен!"); } } private void ListenForNoise() { NoiseManager.NoiseEvent noise = NoiseManager.GetClosestNoise(transform.position, hearingRange); if (noise == null || noise.timestamp <= lastHandledNoiseTimestamp) { return; } lastHandledNoiseTimestamp = noise.timestamp; OnNoiseHeard(noise.position); } private void EnterState(State newState) { CurrentState = newState; ApplySpeedForState(newState); switch (newState) { case State.Patrol: suspicionTimer = 0f; break; case State.Suspicion: suspicionTimer = 0f; break; case State.Alert: lastSeenTime = Time.time; break; } } private void ApplySpeedForState(State state) { if (agent == null) { return; } switch (state) { case State.Patrol: agent.speed = patrolSpeed; break; case State.Suspicion: agent.speed = suspicionSpeed; break; case State.Alert: agent.speed = alertSpeed; break; } } private void MoveTowards(Vector3 destination, float manualSpeed) { if (agent != null && agent.isActiveAndEnabled && agent.isOnNavMesh) { agent.SetDestination(destination); return; } // Fallback нужен на случай, если NavMesh ещё не запечён или агент не смог встать на него. Vector3 flatDestination = new Vector3(destination.x, transform.position.y, destination.z); Vector3 direction = flatDestination - transform.position; direction.y = 0f; if (direction.sqrMagnitude <= 0.001f) { return; } Vector3 step = direction.normalized * manualSpeed * Time.deltaTime; transform.position += step.sqrMagnitude > direction.sqrMagnitude ? direction : step; Quaternion targetRotation = Quaternion.LookRotation(direction.normalized, Vector3.up); transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * manualRotationSpeed); } private bool HasReached(Vector3 destination) { if (agent != null && agent.isActiveAndEnabled && agent.isOnNavMesh) { if (agent.pathPending) { return false; } return agent.remainingDistance <= Mathf.Max(agent.stoppingDistance, waypointTolerance); } Vector3 flatDestination = new Vector3(destination.x, transform.position.y, destination.z); return Vector3.Distance(transform.position, flatDestination) <= waypointTolerance; } private void AcquirePlayer() { if (player != null) { return; } GameObject playerObject = GameObject.FindGameObjectWithTag("Player"); if (playerObject == null) { return; } player = playerObject.transform; playerHealth = playerObject.GetComponent(); } }