feat: 스킬 애니메이션 N클립 순차 재생 및 이름 기반 자동 매칭 시스템

- SkillData: skillClip/endClip 단일 필드를 animationClips 리스트로 통합
  - Data_Skill_ 접두사 애셋 이름과 Anim_{key}_{순서} 클립을 자동 매칭
  - 레거시 skillClip/endClip 데이터 자동 마이그레이션
- SkillController: 클립 시퀀스 내 순차 재생 로직 (TryPlayNextClipInSequence)
  - baseSkillClip을 컨트롤러 Skill state에서 OnValidate로 자동 발견
  - waitingForEndAnimation / IsInEndAnimation 제거
- BuildSimulationEngine: 전체 클립 duration 합산 및 모든 클립 OnEffect 이벤트 파싱
- PlayerAbnormalityVerificationRunner: GetSkillDuration 전체 클립 길이 합산으로 변경
- EnemyBase: IsInEndAnimation 참조 제거
- AnimationClipExtractor: animationClips 리스트 기반 relink/collect로 변경
- AnimationClipSkillDataMatcher: 클립 변경 시 관련 SkillData 자동 갱신 (AssetPostprocessor)
- BaseSkillClipAssigner: 모든 컨트롤러의 Skill state에 base clip 일괄 할당 에디터 메뉴
- pre-commit hook: Anim_ 네이밍 규칙에 {순서} 패턴 추가 및 Anim_↔Data_Skill_ 매칭 검증
This commit is contained in:
2026-04-02 18:55:32 +09:00
parent 08b1e3d95a
commit 52b0e682a8
10 changed files with 505 additions and 86 deletions

View File

@@ -345,13 +345,28 @@ public static class AnimationClipExtractor
SerializedObject so = new SerializedObject(skillData);
bool modified = false;
if (TryRemapClip(so.FindProperty("skillClip"), fbxToAnimMap, path, "skillClip"))
// animationClips 리스트의 각 요소를 remap합니다.
SerializedProperty clipsProp = so.FindProperty("animationClips");
if (clipsProp != null)
{
for (int i = 0; i < clipsProp.arraySize; i++)
{
if (TryRemapClip(clipsProp.GetArrayElementAtIndex(i), fbxToAnimMap, path, $"animationClips[{i}]"))
{
modified = true;
relinkCount++;
}
}
}
// 레거시 필드도 remap (마이그레이션 전 애셋 보호)
if (TryRemapClip(so.FindProperty("legacySkillClip"), fbxToAnimMap, path, "legacySkillClip"))
{
modified = true;
relinkCount++;
}
if (TryRemapClip(so.FindProperty("endClip"), fbxToAnimMap, path, "endClip"))
if (TryRemapClip(so.FindProperty("legacyEndClip"), fbxToAnimMap, path, "legacyEndClip"))
{
modified = true;
relinkCount++;
@@ -567,8 +582,11 @@ public static class AnimationClipExtractor
SkillData skillData = AssetDatabase.LoadAssetAtPath<SkillData>(path);
if (skillData == null) continue;
if (skillData.SkillClip != null) clips.Add(skillData.SkillClip);
if (skillData.EndClip != null) clips.Add(skillData.EndClip);
IReadOnlyList<AnimationClip> skillClips = skillData.AnimationClips;
for (int i = 0; i < skillClips.Count; i++)
{
if (skillClips[i] != null) clips.Add(skillClips[i]);
}
}
// ── BossPhaseData ──