Files
Colosseum/Assets/_Game/Scripts/Editor/BaseSkillClipAssigner.cs
dal4segno 52b0e682a8 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_ 매칭 검증
2026-04-02 18:57:03 +09:00

104 lines
3.6 KiB
C#

using System;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
namespace Colosseum.Editor
{
/// <summary>
/// AnimatorController의 Skill 상태에 base clip을 일괄 할당하는 에디터 도구입니다.
/// </summary>
public static class BaseSkillClipAssigner
{
private const string SkillStateName = "Skill";
/// <summary>
/// 선택한 애니메이션 클립을 모든 AnimatorController의 Skill 상태에 할당합니다.
/// </summary>
[MenuItem("Tools/Animation/Assign Base Skill Clip to All Controllers")]
private static void AssignBaseClipToAllControllers()
{
AnimationClip selectedClip = Selection.activeObject as AnimationClip;
if (selectedClip == null)
{
EditorUtility.DisplayDialog(
"Base Skill Clip 할당",
"Animations 폴더에서 할당할 AnimationClip을 선택한 후 다시 실행하세요.",
"확인");
return;
}
string[] controllerGuids = AssetDatabase.FindAssets("t:AnimatorController", new[] { "Assets/_Game" });
int assignedCount = 0;
for (int i = 0; i < controllerGuids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(controllerGuids[i]);
AnimatorController ac = AssetDatabase.LoadAssetAtPath<AnimatorController>(path);
if (ac == null) continue;
if (TryAssignClipToState(ac, SkillStateName, selectedClip))
assignedCount++;
}
if (assignedCount > 0)
{
AssetDatabase.SaveAssets();
EditorUtility.DisplayDialog(
"Base Skill Clip 할당 완료",
$"{selectedClip.name}을(를) {assignedCount}개 컨트롤러의 Skill 상태에 할당했습니다.",
"확인");
}
else
{
EditorUtility.DisplayDialog(
"Base Skill Clip 할당",
"Skill 상태를 가진 컨트롤러를 찾지 못했습니다.",
"확인");
}
}
/// <summary>
/// AnimatorController의 지정한 상태에 클립을 할당합니다.
/// </summary>
private static bool TryAssignClipToState(AnimatorController ac, string stateName, AnimationClip clip)
{
for (int i = 0; i < ac.layers.Length; i++)
{
if (TryAssignClipInStateMachine(ac.layers[i].stateMachine, stateName, clip))
{
EditorUtility.SetDirty(ac);
return true;
}
}
return false;
}
/// <summary>
/// StateMachine을 재귀적으로 탐색하여 지정한 이름의 상태에 클립을 할당합니다.
/// </summary>
private static bool TryAssignClipInStateMachine(AnimatorStateMachine sm, string stateName, AnimationClip clip)
{
for (int i = 0; i < sm.states.Length; i++)
{
if (sm.states[i].state.name == stateName)
{
sm.states[i].state.motion = clip;
return true;
}
}
for (int i = 0; i < sm.stateMachines.Length; i++)
{
if (TryAssignClipInStateMachine(sm.stateMachines[i].stateMachine, stateName, clip))
return true;
}
return false;
}
}
}