Projext/Assets/Scripts/Enemy/BossAI/BossMonster.cs
2026-02-11 22:47:53 +09:00

460 lines
14 KiB
C#

using UnityEngine;
using System.Collections;
public class NorcielBoss : MonsterClass
{
[Header("--- 🧠 두뇌 연결 ---")]
[SerializeField] private BossCounterSystem counterSystem;
[Header("--- ⚔️ 패턴 설정 ---")]
[SerializeField] private float patternInterval = 3f;
[Header("--- 🎱 무기(쇠공) 설정 (필수!) ---")]
[SerializeField] private GameObject ironBall;
[SerializeField] private Transform handHolder;
[Header("--- 📊 UI 연결 ---")]
[SerializeField] private GameObject bossHealthBar;
// --- 내부 변수들 ---
private float _timer;
private Rigidbody rb;
private Rigidbody ballRb;
private bool isBattleStarted = false;
private bool isWeaponless = false;
private bool isPerformingAction = false; // ⭐ 신규: 연출 중 플래그 (Roar 등)
private Transform target;
protected override void Awake()
{
base.Awake();
rb = GetComponent<Rigidbody>();
if (ironBall != null) ballRb = ironBall.GetComponent<Rigidbody>();
}
// ⭐ [수정] Start → OnEnable 이후 호출되도록 변경
private void Start()
{
StartBossBattle();
}
protected override void Init()
{
_timer = patternInterval;
isBattleStarted = false;
isWeaponless = false;
isPerformingAction = false;
// 플레이어 찾기
GameObject playerObj = GameObject.FindWithTag("Player");
if (playerObj != null) target = playerObj.transform;
else
{
var playerScript = FindObjectOfType<PlayerMovement>();
if (playerScript != null) target = playerScript.transform;
}
// HP 초기화
currentHP = maxHP;
StartCoroutine(SafeInitNavMesh());
if (bossHealthBar != null) bossHealthBar.SetActive(false);
if (ballRb != null) ballRb.isKinematic = true;
}
private IEnumerator SafeInitNavMesh()
{
yield return null;
if (agent != null)
{
int retry = 0;
while (!agent.isOnNavMesh && retry < 5)
{
retry++;
yield return new WaitForSeconds(0.1f);
}
if (agent.isOnNavMesh)
{
agent.isStopped = true;
}
agent.enabled = false;
}
}
// ════════════════════════════════════════
// ⭐ [핵심 수정] 부모의 OnManagedUpdate 흐름 제어
// ════════════════════════════════════════
/// <summary>
/// 부모 MonsterClass의 StopMovement()가 매 프레임 Monster_Idle을
/// 강제 재생해서 Roar/다른 애니메이션을 덮어쓰는 문제 해결.
///
/// 보스는 일반 몬스터와 다르게:
/// - 전투 시작 전에는 아무 AI도 돌리지 않음
/// - 연출 중(Roar 등)에는 애니메이션을 건드리지 않음
/// </summary>
protected override void ExecuteAILogic()
{
// 연출 중이거나 전투 미시작 → 아무것도 안 함
if (isPerformingAction || !isBattleStarted || target == null) return;
// 무기 없으면 회수 우선
if (isWeaponless)
{
RetrieveWeaponLogic();
return;
}
// --- 일반 전투 로직 ---
_timer -= Time.deltaTime;
if (_timer <= 0 && !isAttacking && !isHit && !isDead && !isResting)
{
_timer = patternInterval;
DecideAttack();
}
// 이동 (공격/피격 중이 아닐 때만)
if (!isAttacking && !isHit && !isResting && agent != null && agent.enabled && agent.isOnNavMesh)
{
agent.SetDestination(target.position);
if (animator != null) animator.SetFloat("Speed", agent.velocity.magnitude);
if (agent.remainingDistance <= agent.stoppingDistance + 0.5f)
{
agent.isStopped = true;
LookAtTarget(target.position);
if (animator != null) animator.SetFloat("Speed", 0f);
}
else
{
agent.isStopped = false;
}
}
}
// ════════════════════════════════════════
// 🏃‍♂️ 무기 회수 로직
// ════════════════════════════════════════
private void RetrieveWeaponLogic()
{
if (ironBall == null || !agent.enabled || !agent.isOnNavMesh) return;
agent.SetDestination(ironBall.transform.position);
agent.isStopped = false;
if (animator != null) animator.SetFloat("Speed", agent.velocity.magnitude);
float dist = Vector3.Distance(transform.position, ironBall.transform.position);
if (dist <= 3.0f && !isAttacking)
{
StartCoroutine(PickUpBallRoutine());
}
}
private IEnumerator PickUpBallRoutine()
{
OnAttackStart();
if (agent != null && agent.isOnNavMesh)
{
agent.isStopped = true;
agent.velocity = Vector3.zero;
}
if (animator != null)
{
animator.SetFloat("Speed", 0);
animator.Play("Skill_Pickup");
}
yield return new WaitForSeconds(0.8f);
ironBall.transform.SetParent(handHolder);
ironBall.transform.localPosition = Vector3.zero;
ironBall.transform.localRotation = Quaternion.identity;
if (ballRb != null)
{
ballRb.isKinematic = true;
ballRb.velocity = Vector3.zero;
}
yield return new WaitForSeconds(1.0f);
isWeaponless = false;
OnAttackEnd();
if (agent != null && agent.isOnNavMesh) agent.isStopped = false;
}
// ════════════════════════════════════════
// 🎬 전투 입장
// ════════════════════════════════════════
public void StartBossBattle()
{
if (isBattleStarted) return;
StartCoroutine(BattleStartRoutine());
}
private IEnumerator BattleStartRoutine()
{
Debug.Log("🔥 보스 전투 시작! (Roar)");
// ⭐ [핵심] 연출 중 플래그 ON → ExecuteAILogic이 아무것도 안 함
isPerformingAction = true;
if (bossHealthBar != null) bossHealthBar.SetActive(true);
if (counterSystem != null) counterSystem.InitializeBattle();
// ⭐ [핵심] Roar 재생
if (animator != null)
{
animator.Play("Roar", 0, 0f);
}
// Roar 애니메이션 대기 (길이에 맞게 조절)
yield return new WaitForSeconds(2.0f);
// ⭐ [핵심] Roar 끝나면 Idle로 전환
if (animator != null)
{
animator.Play("Monster_Idle", 0, 0f);
}
// 연출 종료 → 전투 시작
isPerformingAction = false;
isBattleStarted = true;
// NavMeshAgent 활성화
if (agent != null)
{
agent.enabled = true;
// NavMesh 위에 올라갈 때까지 잠시 대기
int retry = 0;
while (!agent.isOnNavMesh && retry < 10)
{
retry++;
yield return new WaitForSeconds(0.1f);
}
if (agent.isOnNavMesh)
{
agent.isStopped = false;
}
}
Debug.Log("⚔️ 보스 전투 루프 시작!");
}
private void LookAtTarget(Vector3 targetPos)
{
Vector3 dir = targetPos - transform.position;
dir.y = 0;
if (dir != Vector3.zero)
{
transform.rotation = Quaternion.Slerp(
transform.rotation,
Quaternion.LookRotation(dir),
Time.deltaTime * 5f
);
}
}
// ════════════════════════════════════════
// 🧠 AI 공격 판단
// ════════════════════════════════════════
private void DecideAttack()
{
if (animator != null) animator.SetFloat("Speed", 0f);
// 공격 전 이동 멈춤
if (agent != null && agent.isOnNavMesh)
{
agent.isStopped = true;
agent.velocity = Vector3.zero;
}
// 플레이어를 바라봄
if (target != null) LookAtTarget(target.position);
string patternName = (counterSystem != null) ? counterSystem.SelectBossPattern() : "Normal";
Debug.Log($"🧠 보스 패턴 선택: {patternName}");
switch (patternName)
{
case "DashAttack": StartCoroutine(Pattern_DashAttack()); break;
case "Smash": StartCoroutine(Pattern_SmashAttack()); break;
case "ShieldWall": StartCoroutine(Pattern_ShieldWall()); break;
default: StartCoroutine(Pattern_ThrowBall()); break;
}
}
// ════════════════════════════════════════
// ⚔️ 공격 패턴
// ════════════════════════════════════════
// 1. 돌진
private IEnumerator Pattern_DashAttack()
{
OnAttackStart();
if (agent != null) agent.enabled = false;
if (animator != null) animator.Play("Skill_Dash_Ready", 0, 0f);
yield return new WaitForSeconds(0.5f);
// 돌진
if (rb != null)
{
rb.isKinematic = false;
rb.velocity = transform.forward * 20f;
}
if (animator != null) animator.Play("Skill_Dash_Go", 0, 0f);
yield return new WaitForSeconds(1.0f);
// 정지
if (rb != null)
{
rb.velocity = Vector3.zero;
rb.isKinematic = true;
}
// NavMeshAgent 복구
if (agent != null && isBattleStarted)
{
agent.enabled = true;
// NavMesh 복귀 대기
int retry = 0;
while (agent != null && !agent.isOnNavMesh && retry < 10)
{
retry++;
yield return new WaitForSeconds(0.1f);
}
}
OnAttackEnd();
}
// 2. 스매시
private IEnumerator Pattern_SmashAttack()
{
OnAttackStart();
if (animator != null) animator.Play("Skill_Smash_Impact", 0, 0f);
yield return new WaitForSeconds(1.2f);
if (animator != null) animator.Play("Skill_Smash_Impact", 0, 0f);
yield return new WaitForSeconds(1.0f);
OnAttackEnd();
}
// 3. 쓸기
private IEnumerator Pattern_ShieldWall()
{
OnAttackStart();
if (animator != null) animator.Play("Skill_Sweep", 0, 0f);
yield return new WaitForSeconds(2.0f);
OnAttackEnd();
}
// 4. 공 던지기
private IEnumerator Pattern_ThrowBall()
{
OnAttackStart();
if (animator != null) animator.Play("Attack_Throw", 0, 0f);
yield return new WaitForSeconds(0.5f);
if (ironBall != null && ballRb != null)
{
ironBall.transform.SetParent(null);
ballRb.isKinematic = false;
Vector3 dir = (target.position - transform.position).normalized;
ballRb.AddForce(dir * 20f + Vector3.up * 5f, ForceMode.Impulse);
ballRb.angularDrag = 5f;
}
isWeaponless = true;
yield return new WaitForSeconds(1.0f);
OnAttackEnd();
}
// ════════════════════════════════════════
// ⭐ [신규] 피격 시 부모 클래스 StopAllCoroutines 대응
// ════════════════════════════════════════
/// <summary>
/// 부모의 StartHit()이 StopAllCoroutines()를 호출하므로,
/// 보스 전용 상태를 여기서 복구합니다.
/// </summary>
protected override void OnStartHit()
{
isPerformingAction = false; // 연출 중이었으면 해제
// 공격 중이었으면 해제
if (isAttacking)
{
isAttacking = false;
if (myWeapon != null) myWeapon.DisableHitBox();
}
// 돌진 중 물리 복구
if (rb != null && !rb.isKinematic)
{
rb.velocity = Vector3.zero;
rb.isKinematic = true;
}
}
/// <summary>
/// 피격 종료 후 NavMeshAgent 복구
/// </summary>
public override void OnHitEnd()
{
base.OnHitEnd();
// 전투 중이면 Agent 다시 활성화
if (isBattleStarted && agent != null && !agent.enabled)
{
agent.enabled = true;
}
}
// ════════════════════════════════════════
// ⭐ [신규] 사망 처리
// ════════════════════════════════════════
protected override void OnDie()
{
isBattleStarted = false;
isPerformingAction = false;
isWeaponless = false;
// 물리 정리
if (rb != null)
{
rb.velocity = Vector3.zero;
rb.isKinematic = true;
}
// 체력바 숨기기
if (bossHealthBar != null) bossHealthBar.SetActive(false);
}
}