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(); 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(); // Автоматически находим игрока на сцене var playerObj = FindAnyObjectByType(); if (playerObj != null) { targetPlayerHealth = playerObj; } if (echoTrigger == null) { echoTrigger = FindAnyObjectByType(); } // Если точки патрулирования не заданы или массив состоит из 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($"[BlindMutantAI] Автоматически обнаружено {patrolPoints.Length} точек патрулирования в объекте 'PatrolWaypoints'."); } } 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); } /// /// Проверяет, стоит ли игрок неподвижно и без звуков (шейдер волн эхолокации не активен) /// public bool IsPlayerSilentAndStill() { if (echoTrigger == null) { echoTrigger = EchoTrigger.Instance != null ? EchoTrigger.Instance : FindAnyObjectByType(); } if (echoTrigger != null) { return !echoTrigger.IsPlayerMakingNoise(); } // Резервная проверка, если EchoTrigger не найден if (targetPlayerHealth != null) { var cc = targetPlayerHealth.GetComponent(); if (cc != null && cc.velocity.sqrMagnitude > 0.05f) return false; var anim = targetPlayerHealth.GetComponentInChildren(); 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(); } 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); } }