Kursach_StealthAction/Assets/Scripts/TitanBossAI.cs
2026-10-23 15:44:09 +03:00

451 lines
16 KiB
C#
Raw 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 System.Collections;
using UnityEngine;
using UnityEngine.AI;
/// <summary>
/// Искусственный интеллект уникального Босса 2-го уровня («Тип-0: Альфа-Мутант / Патриарх»).
/// Механики:
/// 1. Мощная рукопашная атака (40 урона) вблизи.
/// 2. Дальняя атака: кислотный плевок с созданием волны эхолокации при ударе и ядовитой лужи.
/// 3. Акустическое ослепление (Blackout Screech): оглушающий крик, полностью блокирующий эхолокацию на 4.5с.
/// 4. Изолированная комната-арена с карантинной блокировкой дверей до победы над боссом.
/// </summary>
[RequireComponent(typeof(NavMeshAgent))]
[RequireComponent(typeof(MutantHealth))]
public class TitanBossAI : MonoBehaviour
{
[Header("Идентификация босса")]
public string bossDisplayName = "ТИП-0: АЛЬФА-МУТАНТ (ПАТРИАРХ)";
[Header("Параметры движения")]
public float walkSpeed = 2.8f;
public float runSpeed = 4.6f;
public float stoppingDistance = 2.0f;
[Header("Ближний бой")]
public float meleeRange = 2.4f;
public float meleeDamage = 18f;
public float meleeCooldown = 2.2f;
private float nextMeleeTime = 0f;
[Header("Дальний бой (Кислотный плевок)")]
public float spitMinDistance = 5.0f;
public float spitCooldown = 8.5f;
public float spitDamage = 14f;
private float nextSpitTime = 0f;
[Header("Акустическое ослепление (Крик слепоты)")]
public float screechCooldown = 17f;
public float blindnessDuration = 4.5f;
private float nextScreechTime = 0f;
private bool hasEnragedBelowHalf = false;
[Header("Ссылки на компоненты")]
public Transform mouthSpawnPoint;
public BossArenaController arenaController;
private NavMeshAgent agent;
private Animator animator;
private MutantHealth health;
private Transform player;
private PlayerHealth playerHealth;
public enum BossState { Dormant, Roaring, Combat, Screeching, Spitting, Dead }
public BossState CurrentState { get; private set; } = BossState.Dormant;
private void Awake()
{
agent = GetComponent<NavMeshAgent>();
animator = GetComponentInChildren<Animator>();
health = GetComponent<MutantHealth>();
// Увеличиваем здоровье босса (200 HP)
health.maxHealth = 200f;
}
private void Start()
{
// Игрок
GameObject pObj = GameObject.FindWithTag("Player") ?? GameObject.Find("Player");
if (pObj != null)
{
player = pObj.transform;
playerHealth = pObj.GetComponent<PlayerHealth>();
}
if (agent != null)
{
agent.speed = walkSpeed;
agent.stoppingDistance = stoppingDistance;
agent.isStopped = true;
if (!agent.isOnNavMesh)
{
if (NavMesh.SamplePosition(transform.position, out NavMeshHit hit, 5.0f, NavMesh.AllAreas))
{
agent.Warp(hit.position);
}
}
}
// Подписываемся на события здоровья
if (health != null)
{
health.OnHealthChanged += HandleHealthChanged;
health.OnDied += HandleDeath;
}
// Назначаем точку рта для плевка прямо в пасти головы монстра
if (mouthSpawnPoint == null)
{
Transform headBone = null;
foreach (var t in GetComponentsInChildren<Transform>())
{
if (t.name == "head") { headBone = t; break; }
}
GameObject mouth = new GameObject("MouthPoint");
if (headBone != null)
{
mouth.transform.SetParent(headBone, false);
// Смещение в пасть монстра относительно кости головы
mouth.transform.localPosition = new Vector3(-0.08f, 0.25f, 0.32f);
}
else
{
mouth.transform.SetParent(transform, false);
mouth.transform.localPosition = new Vector3(0f, 2.75f, 0.55f);
}
mouthSpawnPoint = mouth.transform;
}
nextSpitTime = Time.time + 4f;
nextScreechTime = Time.time + 12f;
}
/// <summary>
/// Вызывается триггером комнаты, когда игрок заходит на арену и двери блокируются
/// </summary>
public void WakeUp()
{
if (CurrentState != BossState.Dormant) return;
StartCoroutine(RoutineWakeUpRoar());
}
private IEnumerator RoutineWakeUpRoar()
{
CurrentState = BossState.Roaring;
// Поворачиваемся к игроку
if (player != null)
{
Vector3 lookDir = (player.position - transform.position);
lookDir.y = 0;
if (lookDir.sqrMagnitude > 0.01f) transform.rotation = Quaternion.LookRotation(lookDir);
}
// Рев пробуждения
GameAudioSynthesizer.PlayBossRoar();
// Отображаем здоровье на экране
if (BossHealthBarUI.Instance != null && health != null)
{
BossHealthBarUI.Instance.ShowBoss(bossDisplayName, health.CurrentHealth, health.maxHealth);
}
// Создаем мощную волну эхолокации от рева
if (EchoTrigger.Instance != null)
{
EchoTrigger.Instance.TriggerExternalWave(transform.position, 28f, 3.5f);
}
yield return new WaitForSeconds(1.8f);
if (CurrentState != BossState.Dead)
{
CurrentState = BossState.Combat;
if (agent != null && agent.isOnNavMesh) agent.isStopped = false;
}
}
private void Update()
{
if (CurrentState == BossState.Dormant || CurrentState == BossState.Dead)
{
UpdateAnimationSpeed(0f);
return;
}
if (CurrentState == BossState.Roaring || CurrentState == BossState.Screeching || CurrentState == BossState.Spitting)
{
UpdateAnimationSpeed(0f);
return;
}
if (player == null || playerHealth == null || playerHealth.IsDead)
{
if (agent != null && agent.isOnNavMesh) agent.isStopped = true;
UpdateAnimationSpeed(0f);
return;
}
float dist = Vector3.Distance(transform.position, player.position);
// 1. Проверка на фазовый крик ослепления (Acoustic Blackout Screech)
if (Time.time >= nextScreechTime)
{
StartCoroutine(RoutineAcousticScreech());
return;
}
// 2. Ближний бой (если подошли в упор)
if (dist <= meleeRange)
{
if (agent != null && agent.isOnNavMesh) agent.isStopped = true;
UpdateAnimationSpeed(0f);
// Поворот к игроку
FaceTarget(player.position);
if (Time.time >= nextMeleeTime)
{
PerformMeleeAttack();
}
return;
}
// 3. Дальний бой (Кислотный плевок)
if (dist >= spitMinDistance && Time.time >= nextSpitTime)
{
StartCoroutine(RoutineSpitAttack());
return;
}
// 4. Погоня за игроком внутри комнаты
if (agent != null && agent.isOnNavMesh)
{
agent.isStopped = false;
agent.speed = runSpeed;
agent.SetDestination(player.position);
UpdateAnimationSpeed(agent.velocity.magnitude);
}
}
private void PerformMeleeAttack()
{
nextMeleeTime = Time.time + meleeCooldown;
if (animator != null)
{
animator.SetTrigger("Attack");
}
StartCoroutine(RoutineMeleeDamageCheck());
}
private IEnumerator RoutineMeleeDamageCheck()
{
yield return new WaitForSeconds(0.45f);
if (CurrentState == BossState.Dead) yield break;
if (player != null && playerHealth != null && !playerHealth.IsDead)
{
float dist = Vector3.Distance(transform.position, player.position);
if (dist <= meleeRange + 0.8f)
{
playerHealth.TakeDamage(meleeDamage);
Debug.Log($"<color=red>[Босс] Сокрушительный удар вблизи! Нанесено {meleeDamage} урона.</color>");
// Мощный глухой удар
GameAudioSynthesizer.PlayErrorCode();
// Волна от удара о землю
if (EchoTrigger.Instance != null)
{
EchoTrigger.Instance.TriggerExternalWave(transform.position + transform.forward * 1.2f, 15f, 2.0f);
}
}
}
}
public Vector3 GetMouthPosition()
{
if (mouthSpawnPoint != null) return mouthSpawnPoint.position;
Transform headBone = null;
foreach (var t in GetComponentsInChildren<Transform>())
{
if (t.name == "head") { headBone = t; break; }
}
if (headBone != null)
{
return headBone.position + transform.forward * 0.45f - Vector3.up * 0.12f;
}
return transform.position + Vector3.up * 2.75f + transform.forward * 0.55f;
}
private IEnumerator RoutineSpitAttack()
{
CurrentState = BossState.Spitting;
nextSpitTime = Time.time + spitCooldown;
if (agent != null && agent.isOnNavMesh) agent.isStopped = true;
// Поворот к цели
FaceTarget(player.position);
if (animator != null)
{
animator.SetTrigger("Attack");
}
// Звук клокотания в горле перед извержением слюны
GameAudioSynthesizer.PlayAcidHiss();
yield return new WaitForSeconds(0.35f);
if (CurrentState == BossState.Dead) yield break;
// Звук сочного хлёсткого плевка изо рта
GameAudioSynthesizer.PlayAcidSpitSound();
// Спавним очередь брызг слюны прямо из пасти босса
Vector3 target = player != null ? player.position + Vector3.up * 1.1f : transform.position + transform.forward * 10f;
// 1. Основной тяжелый сгусток слюны
Vector3 spawn1 = GetMouthPosition();
AcidProjectile.Spawn(spawn1, target, projSpeed: 19f, damage: spitDamage * 0.7f, isMain: true, spreadAngle: 0f);
// 2. Вторая капля слюны с легким естественным разлетом
yield return new WaitForSeconds(0.06f);
if (CurrentState == BossState.Dead) yield break;
Vector3 spawn2 = GetMouthPosition();
AcidProjectile.Spawn(spawn2, target, projSpeed: 17.5f, damage: spitDamage * 0.2f, isMain: false, spreadAngle: 3f);
// 3. Третья мелкая брызга слюны
yield return new WaitForSeconds(0.05f);
if (CurrentState == BossState.Dead) yield break;
Vector3 spawn3 = GetMouthPosition();
AcidProjectile.Spawn(spawn3, target, projSpeed: 16.5f, damage: spitDamage * 0.1f, isMain: false, spreadAngle: 5f);
yield return new WaitForSeconds(0.4f);
if (CurrentState != BossState.Dead)
{
CurrentState = BossState.Combat;
if (agent != null && agent.isOnNavMesh) agent.isStopped = false;
}
}
private IEnumerator RoutineAcousticScreech()
{
CurrentState = BossState.Screeching;
nextScreechTime = Time.time + screechCooldown;
if (agent != null && agent.isOnNavMesh) agent.isStopped = true;
FaceTarget(player.position);
Debug.Log("<color=red>[Босс] АКУСТИЧЕСКИЙ КРИК: ПОЛНОЕ ОСЛЕПЛЕНИЕ ПРОСТРАНСТВА!</color>");
// Оглушительный крик
GameAudioSynthesizer.PlayAcousticScreech();
// Активируем полную слепоту в эхолокаторе (эхолокация заблокирована)
if (EchoTrigger.Instance != null)
{
EchoTrigger.Instance.ApplyBlindness(blindnessDuration);
}
yield return new WaitForSeconds(1.2f);
if (CurrentState != BossState.Dead)
{
CurrentState = BossState.Combat;
// Во время слепоты босс яростно бежит на игрока!
if (agent != null && agent.isOnNavMesh)
{
agent.isStopped = false;
agent.speed = runSpeed * 1.15f; // Ускоренный рывок в темноте!
if (player != null) agent.SetDestination(player.position);
}
}
}
private void HandleHealthChanged(float current, float max)
{
if (BossHealthBarUI.Instance != null)
{
BossHealthBarUI.Instance.UpdateHealth(current, max);
}
// Ярость при падении ниже 50% HP: внеочередной крик ослепления
if (!hasEnragedBelowHalf && current <= max * 0.5f)
{
hasEnragedBelowHalf = true;
nextScreechTime = Time.time + 0.5f; // Немедленный крик ярости!
}
}
private void HandleDeath()
{
CurrentState = BossState.Dead;
if (agent != null && agent.isOnNavMesh)
{
agent.isStopped = true;
agent.enabled = false;
}
if (animator != null)
{
animator.SetBool("IsDead", true);
animator.SetTrigger("Die");
}
// Скрываем полосу здоровья
if (BossHealthBarUI.Instance != null)
{
BossHealthBarUI.Instance.HideBoss(false);
}
// Сообщаем контроллеру арены о победе (открытие дверей)
if (arenaController != null)
{
arenaController.OnBossDefeated();
}
// Финальная победная волна рассеивания
if (EchoTrigger.Instance != null)
{
EchoTrigger.Instance.TriggerExternalWave(transform.position, 35f, 4.0f);
}
GameAudioSynthesizer.PlaySuccessChime();
Debug.Log("<color=green>[Босс] БОСС ПОВЕРЖЕН! Карантинная блокировка снята, шлюз открыт.</color>");
}
private void FaceTarget(Vector3 targetPos)
{
Vector3 dir = (targetPos - transform.position);
dir.y = 0;
if (dir.sqrMagnitude > 0.001f)
{
Quaternion rot = Quaternion.LookRotation(dir);
transform.rotation = Quaternion.Slerp(transform.rotation, rot, 10f * Time.deltaTime);
}
}
private void UpdateAnimationSpeed(float speedVal)
{
if (animator != null)
{
animator.SetFloat("Speed", speedVal);
}
}
}