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:
@@ -257,15 +257,14 @@ namespace Colosseum.Combat.Simulation
|
||||
};
|
||||
|
||||
float resolvedAnimationSpeed = loadoutEntry.GetResolvedAnimationSpeed();
|
||||
float mainClipDuration = ResolveClipDuration(skill.SkillClip, resolvedAnimationSpeed);
|
||||
float endClipDuration = ResolveClipDuration(skill.EndClip, 1f);
|
||||
float totalClipDuration = ResolveTotalClipDuration(skill.AnimationClips, resolvedAnimationSpeed);
|
||||
int repeatCount = loadoutEntry.GetResolvedRepeatCount();
|
||||
snapshot.castDuration = Mathf.Max(MinimumActionDuration, (mainClipDuration * repeatCount) + endClipDuration + ruleSet.MovementLossSecondsPerCast);
|
||||
snapshot.castDuration = Mathf.Max(MinimumActionDuration, (totalClipDuration * repeatCount) + ruleSet.MovementLossSecondsPerCast);
|
||||
|
||||
Dictionary<int, List<SkillEffect>> effectMap = new Dictionary<int, List<SkillEffect>>();
|
||||
loadoutEntry.CollectTriggeredEffects(effectMap);
|
||||
|
||||
BuildDamageEvents(snapshot, effectMap, context, weaponDamageMultiplier, ruleSet, mainClipDuration, resolvedAnimationSpeed, repeatCount, warnings);
|
||||
BuildDamageEvents(snapshot, effectMap, context, weaponDamageMultiplier, ruleSet, totalClipDuration, resolvedAnimationSpeed, repeatCount, warnings);
|
||||
snapshots[slotIndex] = snapshot;
|
||||
}
|
||||
|
||||
@@ -278,7 +277,7 @@ namespace Colosseum.Combat.Simulation
|
||||
SimulationContext context,
|
||||
float weaponDamageMultiplier,
|
||||
SimulationRuleSet ruleSet,
|
||||
float mainClipDuration,
|
||||
float totalClipDuration,
|
||||
float resolvedAnimationSpeed,
|
||||
int repeatCount,
|
||||
List<string> warnings)
|
||||
@@ -286,15 +285,30 @@ namespace Colosseum.Combat.Simulation
|
||||
if (snapshot == null || effectMap == null || effectMap.Count == 0)
|
||||
return;
|
||||
|
||||
// 모든 클립에서 OnEffect 이벤트를 수집합니다.
|
||||
List<AnimationEvent> effectEvents = new List<AnimationEvent>();
|
||||
AnimationClip clip = snapshot.skill.SkillClip;
|
||||
if (clip != null)
|
||||
IReadOnlyList<AnimationClip> clips = snapshot.skill.AnimationClips;
|
||||
if (clips != null)
|
||||
{
|
||||
AnimationEvent[] clipEvents = clip.events;
|
||||
for (int i = 0; i < clipEvents.Length; i++)
|
||||
float timeOffset = 0f;
|
||||
for (int clipIndex = 0; clipIndex < clips.Count; clipIndex++)
|
||||
{
|
||||
if (string.Equals(clipEvents[i].functionName, "OnEffect", StringComparison.Ordinal))
|
||||
effectEvents.Add(clipEvents[i]);
|
||||
AnimationClip clip = clips[clipIndex];
|
||||
if (clip == null) continue;
|
||||
|
||||
AnimationEvent[] clipEvents = clip.events;
|
||||
for (int i = 0; i < clipEvents.Length; i++)
|
||||
{
|
||||
if (string.Equals(clipEvents[i].functionName, "OnEffect", StringComparison.Ordinal))
|
||||
{
|
||||
// 이벤트 시간에 이전 클립들의 누적 길이를 더합니다.
|
||||
AnimationEvent offsetEvent = clipEvents[i];
|
||||
offsetEvent.time += timeOffset;
|
||||
effectEvents.Add(offsetEvent);
|
||||
}
|
||||
}
|
||||
|
||||
timeOffset += clip.length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +317,7 @@ namespace Colosseum.Combat.Simulation
|
||||
|
||||
for (int iteration = 0; iteration < repeatCount; iteration++)
|
||||
{
|
||||
float iterationOffset = mainClipDuration * iteration;
|
||||
float iterationOffset = totalClipDuration * iteration;
|
||||
|
||||
for (int eventIndex = 0; eventIndex < effectEvents.Count; eventIndex++)
|
||||
{
|
||||
@@ -546,6 +560,23 @@ namespace Colosseum.Combat.Simulation
|
||||
return clip.length / Mathf.Max(0.05f, speed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 클립 목록 전체의 재생 시간을 합산합니다.
|
||||
/// </summary>
|
||||
private static float ResolveTotalClipDuration(IReadOnlyList<AnimationClip> clips, float speed)
|
||||
{
|
||||
if (clips == null || clips.Count == 0)
|
||||
return 0f;
|
||||
|
||||
float total = 0f;
|
||||
for (int i = 0; i < clips.Count; i++)
|
||||
{
|
||||
total += ResolveClipDuration(clips[i], speed);
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
private static List<int> CollectValidPrioritySlots(RotationPolicy rotationPolicy, SkillRuntimeSnapshot[] snapshots)
|
||||
{
|
||||
List<int> validSlots = new List<int>();
|
||||
|
||||
Reference in New Issue
Block a user