451 lines
16 KiB
C#
451 lines
16 KiB
C#
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);
|
||
}
|
||
}
|
||
}
|