Projext/Assets/02_Scripts/Enemy/BossAI/NorcielBoss.Animation.cs
hydrozen e989d20668 카툰 쉐이더 추가 + 중복 스크립트 수정 + 전체 업데이트
- ToonPostProcess.shader: 횃불 고딕 스타일 후처리 쉐이더 (Built-in RP)
- ToonCameraEffect.cs: 카메라 자동 부착 후처리 스크립트
- 중복 UI 스크립트 제거 (MenuIntroController, ToggleCustom)
- 씬, 프리팹, 애니메이션 등 전체 업데이트

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:31:16 +09:00

134 lines
9.7 KiB
C#

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)
// ══════════════════════════════════════════════════════════
// 애니메이션 즉시 재생
// ══════════════════════════════════════════════════════════
/// <summary>지정된 이름의 Animator State를 즉시 재생 (처음부터)</summary>
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); // 재생할거에요 -> 처음부터
}
// ══════════════════════════════════════════════════════════
// 애니메이션 재생 + 종료 대기 (코루틴)
// ══════════════════════════════════════════════════════════
/// <summary>애니 재생 후 OnAnimEnd 이벤트 또는 타임아웃(maxWait)까지 대기</summary>
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; // 초기화할거에요 -> 플래그를
}
// ══════════════════════════════════════════════════════════
// 이동/대기 애니 자동 전환
// ══════════════════════════════════════════════════════════
/// <summary>이동 중이면 Walk, 정지면 Idle 재생 (중복 재생 방지)</summary>
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); // 재생할거에요 -> 해당 애니를
}
/// <summary>대기 애니 재생 단축 메서드</summary>
private void PlayIdleAnim() => PlayMoveAnim(false); // 함수를 선언할거에요 -> 대기 애니를 재생하는 (단축)
// ══════════════════════════════════════════════════════════
// 클립 길이 조회
// ══════════════════════════════════════════════════════════
/// <summary>현재 재생 중인 클립의 전체 길이 반환 (없으면 1초)</summary>
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 클립의 특정 프레임에 등록해두면 자동 호출돼요.
// ══════════════════════════════════════════════════════════
/// <summary>Animation Event — 공격 클립 마지막 프레임에서 호출 (모든 공격 패턴 공용)</summary>
public void OnAnimEnd() // 함수를 선언할거에요 -> 애니메이션 종료 이벤트를 수신하는 (Unity Animation Event)
{
_animEndFired = true; // 설정할거에요 -> 종료 플래그를 (대기 중인 코루틴이 감지하고 진행)
}
/// <summary>Animation Event — Attack_Throw 클립에서 공이 손을 떠나는 프레임에서 호출</summary>
public void OnThrowRelease() // 함수를 선언할거에요 -> 던지기 발사 이벤트를 수신하는 (Unity Animation Event)
{
_throwFired = true; // 설정할거에요 -> 발사 플래그를 (Pattern_Throw 코루틴이 감지하고 쇠공 발사)
}
}