Files
PIM_Laba5/Assets/Scripts/EnemyStateMachine.cs
T
2026-05-26 01:16:57 +03:00

297 lines
7.8 KiB
C#

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<EnemyStateMachine> ActiveEnemies => activeEnemies;
private static readonly List<EnemyStateMachine> activeEnemies = new List<EnemyStateMachine>();
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<NavMeshAgent>();
vision = vision != null ? vision : GetComponent<EnemyVision>();
}
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<PlayerHealth>();
}
}