using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을 using System.Collections; // 코루틴 기능을 불러올거에요 -> IEnumerator를 // ============================================================ // NorcielBoss.Animation.cs — 애니메이션 유틸리티 (partial class) // // [역할] // ① 애니메이션 재생 (직접 재생 / 대기 포함 재생) // ② 이동/대기 애니 자동 전환 // ③ 애니메이션 이벤트 수신 (OnAnimEnd, OnThrowRelease) // ④ 클립 길이 조회 // // [설계] // partial class로 NorcielBoss의 animator, _animEndFired 등 필드에 직접 접근. // FSM, 패턴 코루틴 등 여러 partial에서 공통으로 호출되는 핵심 유틸리티. // ============================================================ public partial class NorcielBoss : MonsterClass // 부분 클래스를 선언할거에요 -> NorcielBoss의 애니메이션 부분으로 { // ══════════════════════════════════════════════════════════ // 애니메이션 이벤트 수신 플래그 // Unity Animation Event에서 호출 → 코루틴이 대기 후 진행 // ══════════════════════════════════════════════════════════ private bool _animEndFired = false; // 변수를 선언할거에요 -> OnAnimEnd 수신 여부를 (애니 종료 이벤트) private bool _throwFired = false; // 변수를 선언할거에요 -> OnThrowRelease 수신 여부를 (던지기 발사 이벤트) // ══════════════════════════════════════════════════════════ // 애니메이션 State 이름 (Inspector 설정) // ══════════════════════════════════════════════════════════ [Header("=== 애니메이션 State 이름 (Animator Controller의 State 이름과 동일하게) ===")] [SerializeField] private string anim_Idle = "Monster_Idle"; // 변수를 선언할거에요 -> 대기 애니 이름을 [SerializeField] private string anim_Walk = "Monster_Walk"; // 변수를 선언할거에요 -> 걷기 애니 이름을 [SerializeField] private string anim_Run = "Monster_Run"; // 변수를 선언할거에요 -> 달리기 애니 이름을 (대시 추격 시 사용, Inspector에서 실제 State 이름과 맞춰주세요) [SerializeField] private string anim_Roar = "Roar"; // 변수를 선언할거에요 -> 포효 애니 이름을 [SerializeField] private string anim_Throw = "Attack_Throw"; // 변수를 선언할거에요 -> 던지기 애니 이름을 [SerializeField] private string anim_Pickup = "Skill_Pickup"; // 변수를 선언할거에요 -> 줍기 애니 이름을 [Header("=== 근접 패턴 애니메이션 State 이름 ===")] [SerializeField] private string anim_Smash = "Attack_Smash"; // 변수를 선언할거에요 -> 내려찍기 애니 이름을 [SerializeField] private string anim_Sweep = "Skill_Sweep"; // 변수를 선언할거에요 -> 휩쓸기 애니 이름을 (Animator State: Skill_Sweep) [SerializeField] private string anim_Dash = "Skill_Dash_Ready"; // 변수를 선언할거에요 -> 대시 준비 애니 이름을 (Animator State: Skill_Dash_Ready) // ══════════════════════════════════════════════════════════ // 애니메이션 즉시 재생 // ══════════════════════════════════════════════════════════ /// 지정된 이름의 Animator State를 즉시 재생 (처음부터) private void PlayAnimDirect(string name) // 함수를 선언할거에요 -> 애니메이션을 즉시 재생하는 { if (animator == null) return; // 중단할거에요 -> Animator 없으면 int hash = Animator.StringToHash(name); // 변환할거에요 -> 이름을 해시로 if (!animator.HasState(0, hash)) // 조건이 맞으면 실행할거에요 -> State 없으면 { Debug.LogError($"[Boss] Animator State \"{name}\" 없음! Inspector의 State 이름 확인 필요"); // 에러를 찍을거에요 return; // 중단할거에요 } _animEndFired = false; // 초기화할거에요 -> 종료 플래그를 animator.Play(name, 0, 0f); // 재생할거에요 -> 처음부터 } // ══════════════════════════════════════════════════════════ // 애니메이션 재생 + 종료 대기 (코루틴) // ══════════════════════════════════════════════════════════ /// 애니 재생 후 OnAnimEnd 이벤트 또는 타임아웃(maxWait)까지 대기 private IEnumerator PlayAnimAndWait(string name, float maxWait) // 코루틴을 정의할거에요 -> 애니 재생 후 종료까지 대기하는 { PlayAnimDirect(name); // 재생할거에요 -> 애니를 yield return null; // 기다릴거에요 -> 1프레임 (상태 전환 완료) float elapsed = 0f; // 초기화할거에요 -> 경과 시간을 while (!_animEndFired && elapsed < maxWait) // 반복할거에요 -> OnAnimEnd 또는 타임아웃까지 { elapsed += Time.deltaTime; // 더할거에요 -> 경과 시간을 yield return null; // 기다릴거에요 -> 1프레임을 } if (!_animEndFired) // 조건이 맞으면 실행할거에요 -> 이벤트 못 받았으면 Debug.LogWarning($"[Boss] \"{name}\" OnAnimEnd 미수신 → 타임아웃 강제 진행 ({elapsed:F1}s)"); // 경고를 찍을거에요 _animEndFired = false; // 초기화할거에요 -> 플래그를 } // ══════════════════════════════════════════════════════════ // 이동/대기 애니 자동 전환 // ══════════════════════════════════════════════════════════ /// 이동 중이면 Walk, 정지면 Idle 재생 (중복 재생 방지) private void PlayMoveAnim(bool isMoving) // 함수를 선언할거에요 -> 이동/대기 애니를 상황에 맞게 재생하는 { if (animator == null) return; // 중단할거에요 -> Animator 없으면 string target = isMoving ? anim_Walk : anim_Idle; // 결정할거에요 -> 재생할 애니를 if (!animator.GetCurrentAnimatorStateInfo(0).IsName(target)) // 조건이 맞으면 실행할거에요 -> 현재 State가 아니면 animator.Play(target); // 재생할거에요 -> 해당 애니를 } /// 대기 애니 재생 단축 메서드 private void PlayIdleAnim() => PlayMoveAnim(false); // 함수를 선언할거에요 -> 대기 애니를 재생하는 (단축) // ══════════════════════════════════════════════════════════ // 클립 길이 조회 // ══════════════════════════════════════════════════════════ /// 현재 재생 중인 클립의 전체 길이 반환 (없으면 1초) private float GetClipLength() // 함수를 선언할거에요 -> 현재 재생 중인 클립 길이를 반환하는 { if (animator == null) return 1f; // 반환할거에요 -> 기본값을 var clips = animator.GetCurrentAnimatorClipInfo(0); // 가져올거에요 -> 클립 정보를 return clips.Length > 0 ? clips[0].clip.length : 1f; // 반환할거에요 -> 클립 길이를 (없으면 1초) } // ══════════════════════════════════════════════════════════ // 애니메이션 이벤트 수신 메서드 // Unity Animation Event 시스템이 이름으로 직접 호출합니다. // Animation 클립의 특정 프레임에 등록해두면 자동 호출돼요. // ══════════════════════════════════════════════════════════ /// Animation Event — 공격 클립 마지막 프레임에서 호출 (모든 공격 패턴 공용) public void OnAnimEnd() // 함수를 선언할거에요 -> 애니메이션 종료 이벤트를 수신하는 (Unity Animation Event) { _animEndFired = true; // 설정할거에요 -> 종료 플래그를 (대기 중인 코루틴이 감지하고 진행) } /// Animation Event — Attack_Throw 클립에서 공이 손을 떠나는 프레임에서 호출 public void OnThrowRelease() // 함수를 선언할거에요 -> 던지기 발사 이벤트를 수신하는 (Unity Animation Event) { _throwFired = true; // 설정할거에요 -> 발사 플래그를 (Pattern_Throw 코루틴이 감지하고 쇠공 발사) } }