first commit
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
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>();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user