Kursach_StealthAction/Assets/Scripts/BlindMutantAI.cs

404 lines
14 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
using UnityEngine.AI;
public class BlindMutantAI : MonoBehaviour
{
private NavMeshAgent agent;
private Animator animator;
[Header("Настройки скорости")]
public float walkSpeed = 2f;
public float runSpeed = 5f;
[Header("Патрулирование")]
public Transform[] patrolPoints;
[Tooltip("Время паузы на точке патрулирования для прислушивания")]
public float waitTimeAtPoint = 1.5f;
private float waitTimer = 0f;
private int currentPatrolIndex;
private bool isInvestigating = false;
private float investigationTimer = 0f;
[Header("Режим охраны комнаты")]
[Tooltip("Если включено, мутант приписан к комнате и патрулирует строго ее, но реагирует на звуки снаружи")]
public bool isRoomConfined = false;
[Header("Обнаружение игрока")]
public float detectionRadius = 3.5f; // Радиус обостренных чувств (слух/запах в упор)
public float losePlayerDistance = 14f; // Дистанция потери цели
private Transform targetPlayer;
private PlayerHealth targetPlayerHealth;
private EchoTrigger echoTrigger;
private float silentPlayerTimer = 0f;
[Header("Настройки атаки")]
public float attackRange = 1.8f; // Дистанция ближнего боя
public float attackDamage = 8f; // Урон за один удар (снижен с 20 для баланса)
public float attackCooldown = 1.35f; // Кулдаун между ударами
private float nextAttackTime = 0f;
void Start()
{
agent = GetComponent<NavMeshAgent>();
if (agent != null)
{
agent.speed = walkSpeed;
// Слегка варьируем приоритет избегания, чтобы мутанты уступали друг другу дорогу
agent.avoidancePriority = Random.Range(30, 70);
// Если агент чуть выше пола или не на сетке, привязываем его к ближайшей точке NavMesh
if (!agent.isOnNavMesh)
{
if (NavMesh.SamplePosition(transform.position, out NavMeshHit hit, 5.0f, NavMesh.AllAreas))
{
agent.Warp(hit.position);
}
}
}
animator = GetComponentInChildren<Animator>();
// Автоматически находим игрока на сцене
var playerObj = FindAnyObjectByType<PlayerHealth>();
if (playerObj != null)
{
targetPlayerHealth = playerObj;
}
if (echoTrigger == null)
{
echoTrigger = FindAnyObjectByType<EchoTrigger>();
}
// Если точки патрулирования не заданы или массив состоит из null, ищем PatrolWaypoints на сцене
if (IsPatrolArrayEmptyOrNull())
{
AutoAssignPatrolWaypoints();
}
// Рассинхронизируем начальные точки и таймеры, чтобы толпа мутантов не двигалась синхронно
if (patrolPoints != null && patrolPoints.Length > 0)
{
currentPatrolIndex = Random.Range(0, patrolPoints.Length);
}
waitTimer = Random.Range(0.2f, waitTimeAtPoint);
GoToNextPatrolPoint();
}
private bool IsPatrolArrayEmptyOrNull()
{
if (patrolPoints == null || patrolPoints.Length == 0) return true;
for (int i = 0; i < patrolPoints.Length; i++)
{
if (patrolPoints[i] != null) return false;
}
return true;
}
private void AutoAssignPatrolWaypoints()
{
var patrolRoot = GameObject.Find("PatrolWaypoints");
if (patrolRoot != null && patrolRoot.transform.childCount > 0)
{
patrolPoints = new Transform[patrolRoot.transform.childCount];
for (int i = 0; i < patrolRoot.transform.childCount; i++)
{
patrolPoints[i] = patrolRoot.transform.GetChild(i);
}
Debug.Log($"<color=cyan>[BlindMutantAI] Автоматически обнаружено {patrolPoints.Length} точек патрулирования в объекте 'PatrolWaypoints'.</color>");
}
}
void Update()
{
UpdateAnimator();
// 1. Поведение при активном преследовании игрока
if (targetPlayer != null)
{
HandlePlayerChaseAndAttack();
return;
}
// 2. Проверка близости игрока (если подошел слишком близко — чует даже слепой)
CheckProximityDetection();
// 3. Патрулирование и расследование звуков
HandlePatrolAndInvestigation();
}
private void UpdateAnimator()
{
if (animator == null) return;
float currentSpeed = agent != null && agent.enabled && agent.isOnNavMesh ? agent.velocity.magnitude : 0f;
animator.SetFloat("Speed", currentSpeed);
}
/// <summary>
/// Проверяет, стоит ли игрок неподвижно и без звуков (шейдер волн эхолокации не активен)
/// </summary>
public bool IsPlayerSilentAndStill()
{
if (echoTrigger == null)
{
echoTrigger = EchoTrigger.Instance != null ? EchoTrigger.Instance : FindAnyObjectByType<EchoTrigger>();
}
if (echoTrigger != null)
{
return !echoTrigger.IsPlayerMakingNoise();
}
// Резервная проверка, если EchoTrigger не найден
if (targetPlayerHealth != null)
{
var cc = targetPlayerHealth.GetComponent<CharacterController>();
if (cc != null && cc.velocity.sqrMagnitude > 0.05f) return false;
var anim = targetPlayerHealth.GetComponentInChildren<Animator>();
if (anim != null && anim.GetBool("IsMoving")) return false;
}
return true;
}
private void HandlePlayerChaseAndAttack()
{
if (agent == null || !agent.isOnNavMesh) return;
if (targetPlayerHealth != null && targetPlayerHealth.IsDead)
{
LosePlayerTarget();
return;
}
// Если игрок замер на месте и не издает звуков — слепой мутант не может его заметить и атаковать!
if (IsPlayerSilentAndStill())
{
agent.isStopped = true;
silentPlayerTimer += Time.deltaTime;
// Если игрок сохраняет тишину и неподвижность 0.8 секунды — мутант теряет цель и возвращается к патрулю
if (silentPlayerTimer >= 0.8f)
{
LosePlayerTarget();
}
return;
}
else
{
silentPlayerTimer = 0f;
}
float distanceToPlayer = Vector3.Distance(transform.position, targetPlayer.position);
// Игрок убежал слишком далеко
if (distanceToPlayer > losePlayerDistance)
{
LosePlayerTarget();
return;
}
// Игрок в зоне удара
if (distanceToPlayer <= attackRange)
{
agent.isStopped = true;
// Разворачиваемся к игроку
Vector3 lookDirection = targetPlayer.position - transform.position;
lookDirection.y = 0f;
if (lookDirection.sqrMagnitude > 0.001f)
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(lookDirection), Time.deltaTime * 10f);
}
// Наносим урон по кулдауну
if (Time.time >= nextAttackTime)
{
nextAttackTime = Time.time + attackCooldown;
if (animator != null)
{
animator.SetTrigger("Attack");
}
if (targetPlayerHealth != null)
{
targetPlayerHealth.TakeDamage(attackDamage);
Debug.Log($"Мутант нанес удар игроку! Урон: {attackDamage}");
}
}
}
else
{
// Бежим к игроку
agent.isStopped = false;
agent.speed = runSpeed;
agent.SetDestination(targetPlayer.position);
}
}
private void CheckProximityDetection()
{
if (targetPlayerHealth == null || targetPlayerHealth.IsDead) return;
// Если игрок стоит на месте и не издает звуков (нет волн эхолокации в шейдере) — слепой мутант его не замечает!
if (IsPlayerSilentAndStill()) return;
float distance = Vector3.Distance(transform.position, targetPlayerHealth.transform.position);
if (distance <= detectionRadius)
{
EngagePlayer(targetPlayerHealth.transform);
}
}
private void HandlePatrolAndInvestigation()
{
if (agent == null || !agent.isOnNavMesh) return;
if (isInvestigating)
{
investigationTimer += Time.deltaTime;
// Защита от застревания: если не дошел до точки звука за 10 сек, возвращаемся к патрулю
if (investigationTimer >= 10f)
{
isInvestigating = false;
investigationTimer = 0f;
agent.speed = walkSpeed;
waitTimer = waitTimeAtPoint;
}
}
if (!agent.pathPending && (agent.remainingDistance < 0.8f || !agent.hasPath))
{
if (isInvestigating)
{
isInvestigating = false;
investigationTimer = 0f;
agent.speed = walkSpeed;
waitTimer = waitTimeAtPoint;
}
if (waitTimer > 0f)
{
waitTimer -= Time.deltaTime;
agent.isStopped = true;
return;
}
agent.isStopped = false;
waitTimer = waitTimeAtPoint;
GoToNextPatrolPoint();
}
}
private void EngagePlayer(Transform playerTransform)
{
if (agent == null || !agent.isOnNavMesh) return;
targetPlayer = playerTransform;
if (targetPlayerHealth == null)
{
targetPlayerHealth = playerTransform.GetComponent<PlayerHealth>();
}
isInvestigating = false;
waitTimer = 0f;
agent.isStopped = false;
agent.speed = runSpeed;
agent.SetDestination(targetPlayer.position);
Debug.Log("Мутант обнаружил игрока и начинает атаку!");
}
private void LosePlayerTarget()
{
targetPlayer = null;
silentPlayerTimer = 0f;
if (agent != null && agent.isOnNavMesh)
{
agent.isStopped = false;
agent.speed = walkSpeed;
}
isInvestigating = false;
waitTimer = 0f;
GoToNextPatrolPoint();
Debug.Log("Мутант потерял игрока (игрок замер в тишине) и возвращается к патрулированию.");
}
private void GoToNextPatrolPoint()
{
if (agent == null || !agent.isOnNavMesh) return;
if (patrolPoints != null && patrolPoints.Length > 0)
{
for (int attempts = 0; attempts < patrolPoints.Length; attempts++)
{
var wp = patrolPoints[currentPatrolIndex];
currentPatrolIndex = (currentPatrolIndex + 1) % patrolPoints.Length;
if (wp != null)
{
agent.isStopped = false;
agent.speed = walkSpeed;
agent.SetDestination(wp.position);
return;
}
}
}
// Если точек нет вообще — свободное блуждание по комплексу
WanderRandomly();
}
private void WanderRandomly()
{
if (agent == null || !agent.isOnNavMesh) return;
Vector3 randomDir = Random.insideUnitSphere * 15f;
randomDir += transform.position;
if (NavMesh.SamplePosition(randomDir, out NavMeshHit hit, 15f, NavMesh.AllAreas))
{
agent.isStopped = false;
agent.speed = walkSpeed;
agent.SetDestination(hit.position);
}
}
public void HearSound(Vector3 soundPosition)
{
// Если уже бьем игрока в упор, не отвлекаемся на случайный шум вдалеке
if (targetPlayer != null && Vector3.Distance(transform.position, targetPlayer.position) <= attackRange)
{
return;
}
if (agent == null || !agent.isOnNavMesh) return;
isInvestigating = true;
investigationTimer = 0f;
waitTimer = 0f;
agent.isStopped = false;
agent.speed = runSpeed;
agent.SetDestination(soundPosition);
}
public void OnDamagedByPlayer(Transform player)
{
if (animator != null)
{
animator.SetTrigger("GetHit");
}
silentPlayerTimer = 0f;
EngagePlayer(player);
}
private void OnDrawGizmosSelected()
{
// Радиус ближнего обнаружения
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, detectionRadius);
// Радиус атаки
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, attackRange);
}
}