using System; using System.Collections.Generic; using UnityEngine; using Unity.Netcode; using Unity.Behavior; using Colosseum.Stats; namespace Colosseum.Enemy { /// /// 보스 캐릭터. 페이즈 시스템과 동적 AI 전환을 지원합니다. /// Unity Behavior 패키지를 사용하여 Behavior Tree 기반 AI를 구현합니다. /// public class BossEnemy : EnemyBase { [Header("Boss Settings")] [Tooltip("보스 페이즈 데이터 목록 (순서대로 전환)")] [SerializeField] private List phases = new(); [Tooltip("초기 Behavior Graph")] [SerializeField] private BehaviorGraph initialBehaviorGraph; [Header("Phase Settings")] [Tooltip("페이즈 전환 시 무적 시간")] [Min(0f)] [SerializeField] private float phaseTransitionInvincibilityTime = 2f; [Tooltip("페이즈 전환 연출 시간")] [Min(0f)] [SerializeField] private float phaseTransitionDuration = 3f; [Header("Debug")] [SerializeField] private bool debugMode = true; // 컴포넌트 private BehaviorGraphAgent behaviorAgent; // 페이즈 상태 private int currentPhaseIndex = 0; private bool isTransitioning = false; private float phaseStartTime; private float phaseElapsedTime; private bool isInvincible = false; // 커스텀 조건 딕셔너리 private Dictionary customConditions = new Dictionary(); // 이벤트 public event System.Action OnPhaseChanged; // phaseIndex public event System.Action OnPhaseTransitionStart; // transitionDuration public event System.Action OnPhaseTransitionEnd; // 정적 이벤트 (UI 자동 연결용) /// /// 보스 스폰 시 발생하는 정적 이벤트 /// public static event System.Action OnBossSpawned; /// /// 현재 활성화된 보스 (Scene에 하나만 존재한다고 가정) /// public static BossEnemy ActiveBoss { get; private set; } // Properties public int CurrentPhaseIndex => currentPhaseIndex; public BossPhaseData CurrentPhase => phases.Count > currentPhaseIndex ? phases[currentPhaseIndex] : null; public int TotalPhases => phases.Count; public bool IsTransitioning => isTransitioning; public float PhaseElapsedTime => phaseElapsedTime; public override void OnNetworkSpawn() { base.OnNetworkSpawn(); // BehaviorGraphAgent 컴포넌트 확인/추가 behaviorAgent = GetComponent(); if (behaviorAgent == null) { behaviorAgent = gameObject.AddComponent(); } // 초기 AI 설정 if (IsServer && initialBehaviorGraph != null) { behaviorAgent.Graph = initialBehaviorGraph; } // 정적 이벤트 발생 (UI 자동 연결용) ActiveBoss = this; OnBossSpawned?.Invoke(this); if (debugMode) { Debug.Log($"[Boss] Boss spawned: {name}"); } } protected override void InitializeStats() { base.InitializeStats(); phaseStartTime = Time.time; phaseElapsedTime = 0f; currentPhaseIndex = 0; isTransitioning = false; isInvincible = false; customConditions.Clear(); } protected override void OnServerUpdate() { if (isTransitioning) return; phaseElapsedTime = Time.time - phaseStartTime; CheckPhaseTransition(); } /// /// 페이즈 전환 조건 확인 /// private void CheckPhaseTransition() { int nextPhaseIndex = currentPhaseIndex + 1; if (nextPhaseIndex >= phases.Count) return; BossPhaseData nextPhase = phases[nextPhaseIndex]; if (nextPhase == null) return; if (nextPhase.CheckTransitionCondition(this, phaseElapsedTime)) { StartPhaseTransition(nextPhaseIndex); } } /// /// 페이즈 전환 시작 /// private void StartPhaseTransition(int newPhaseIndex) { if (newPhaseIndex >= phases.Count || isTransitioning) return; isTransitioning = true; isInvincible = true; StartCoroutine(PhaseTransitionCoroutine(newPhaseIndex)); } private System.Collections.IEnumerator PhaseTransitionCoroutine(int newPhaseIndex) { BossPhaseData newPhase = phases[newPhaseIndex]; // 전환 이벤트 OnPhaseTransitionStart?.Invoke(phaseTransitionDuration); // 전환 연출 yield return PlayPhaseTransitionEffect(newPhase); // AI 그래프 교체 if (newPhase.BehaviorGraph != null && behaviorAgent != null) { behaviorAgent.End(); behaviorAgent.Graph = newPhase.BehaviorGraph; } // 페이즈 전환 완료 currentPhaseIndex = newPhaseIndex; phaseStartTime = Time.time; phaseElapsedTime = 0f; // 무적 해제 yield return new WaitForSeconds(phaseTransitionInvincibilityTime); isInvincible = false; isTransitioning = false; OnPhaseTransitionEnd?.Invoke(); OnPhaseChanged?.Invoke(currentPhaseIndex); if (debugMode) { Debug.Log($"[Boss] Phase transition: {currentPhaseIndex} ({newPhase.PhaseName})"); } } /// /// 페이즈 전환 연출 재생 /// private System.Collections.IEnumerator PlayPhaseTransitionEffect(BossPhaseData newPhase) { // 애니메이션 재생 if (animator != null && newPhase.PhaseStartAnimation != null) { animator.Play(newPhase.PhaseStartAnimation.name); } // 이펙트 생성 if (newPhase.PhaseTransitionEffect != null) { var effect = Instantiate(newPhase.PhaseTransitionEffect, transform.position, transform.rotation); Destroy(effect, phaseTransitionDuration); } // 전환 시간 대기 yield return new WaitForSeconds(phaseTransitionDuration); } /// /// 대미지 적용 (무적 상태 고려) /// public override float TakeDamage(float damage, object source = null) { if (isInvincible) return 0f; return base.TakeDamage(damage, source); } /// /// 커스텀 조건 설정 /// public void SetCustomCondition(string conditionId, bool value) { customConditions[conditionId] = value; } /// /// 커스텀 조건 확인 /// public bool CheckCustomCondition(string conditionId) { return customConditions.TryGetValue(conditionId, out bool value) && value; } /// /// 수동으로 페이즈 전환 /// public void ForcePhaseTransition(int phaseIndex) { if (!IsServer) return; if (phaseIndex >= 0 && phaseIndex < phases.Count && phaseIndex != currentPhaseIndex) { StartPhaseTransition(phaseIndex); } } /// /// 현재 페이즈 재시작 /// public void RestartCurrentPhase() { if (!IsServer) return; phaseStartTime = Time.time; phaseElapsedTime = 0f; if (behaviorAgent != null) { behaviorAgent.Restart(); } } protected override void HandleDeath() { // 마지막 페이즈에서만 사망 처리 if (currentPhaseIndex < phases.Count - 1 && !isTransitioning) { // 아직 페이즈가 남아있으면 강제로 다음 페이즈로 StartPhaseTransition(currentPhaseIndex + 1); return; } // AI 완전 중단 (순서 중요: enabled=false를 먼저 호출하여 Update() 차단) if (behaviorAgent != null) { behaviorAgent.enabled = false; // 가장 먼저: Update() 호출 방지 behaviorAgent.End(); behaviorAgent.Graph = null; } behaviorAgent = null; base.HandleDeath(); } #region Debug private void OnDrawGizmosSelected() { if (!debugMode) return; // 현재 페이즈 정보 표시 #if UNITY_EDITOR if (phases != null && currentPhaseIndex < phases.Count) { var phase = phases[currentPhaseIndex]; if (phase != null) { UnityEditor.Handles.Label( transform.position + Vector3.up * 3f, $"Phase {currentPhaseIndex + 1}/{phases.Count}\n{phase.PhaseName}" ); } } #endif } #endregion } }