fix: Section Speed Editor에서 본 곡선 없는 클립 length 0으로 표시되는 문제 수정

- Mixamo export 클립 등 m_FloatCurves만 있는 경우 bone transform 곡선이 없어 length가 0으로 계산되던 문제 수정
- bone 곡선이 없으면 m_StopTime을 기준으로 폴백하도록 effectiveLength 로직 추가
This commit is contained in:
2026-04-02 13:45:06 +09:00
parent d6d120cb61
commit 08b1e3d95a

View File

@@ -54,18 +54,28 @@ public class AnimationSectionSpeedEditor : EditorWindow
// ── 클립 정보 (실제 키프레임 기반) ── // ── 클립 정보 (실제 키프레임 기반) ──
float actualLength = GetActualLastKeyframeTime(clip); float actualLength = GetActualLastKeyframeTime(clip);
bool hasBoneCurves = actualLength > 0f;
// 본 트랜스폼 곡선이 없으면 m_StopTime을 기준으로 사용
// (Mixamo export 클립 등 m_FloatCurves만 있는 경우)
float effectiveLength = hasBoneCurves ? actualLength : clip.length;
EditorGUILayout.LabelField("Clip Info", EditorStyles.boldLabel); EditorGUILayout.LabelField("Clip Info", EditorStyles.boldLabel);
EditorGUI.indentLevel++; EditorGUI.indentLevel++;
EditorGUILayout.LabelField("Length (keyframes)", $"{actualLength:F3}s"); EditorGUILayout.LabelField("Length", $"{effectiveLength:F3}s");
EditorGUILayout.LabelField("m_StopTime", $"{clip.length:F3}s");
if (Mathf.Abs(clip.length - actualLength) > 0.01f) if (hasBoneCurves && Mathf.Abs(clip.length - actualLength) > 0.01f)
{ {
EditorGUILayout.LabelField("m_StopTime", $"{clip.length:F3}s");
EditorGUILayout.HelpBox($"m_StopTime({clip.length:F3}s)이 실제 키프레임 길이({actualLength:F3}s)와 다릅니다.\n" + EditorGUILayout.HelpBox($"m_StopTime({clip.length:F3}s)이 실제 키프레임 길이({actualLength:F3}s)와 다릅니다.\n" +
"스피드 변경 시 실제 키프레임 길이를 기준으로 계산합니다.", MessageType.Warning); "스피드 변경 시 실제 키프레임 길이를 기준으로 계산합니다.", MessageType.Warning);
} }
else if (!hasBoneCurves)
{
EditorGUILayout.LabelField(" (본 곡선 없음, m_StopTime 기준)", EditorStyles.miniLabel);
}
EditorGUILayout.LabelField("Total Frames ({fps}fps)", $"{Mathf.Max(1, Mathf.FloorToInt(actualLength * fps))}"); EditorGUILayout.LabelField("Total Frames ({fps}fps)", $"{Mathf.Max(1, Mathf.FloorToInt(effectiveLength * fps))}");
EditorGUI.indentLevel--; EditorGUI.indentLevel--;
EditorGUILayout.Space(); EditorGUILayout.Space();
@@ -74,7 +84,7 @@ public class AnimationSectionSpeedEditor : EditorWindow
fps = EditorGUILayout.IntField("FPS", fps); fps = EditorGUILayout.IntField("FPS", fps);
fps = Mathf.Max(1, fps); fps = Mathf.Max(1, fps);
int totalFrames = Mathf.Max(1, Mathf.FloorToInt(actualLength * fps)); int totalFrames = Mathf.Max(1, Mathf.FloorToInt(effectiveLength * fps));
// ── 프레임 범위 ── // ── 프레임 범위 ──
EditorGUILayout.LabelField("Target Frame Range", EditorStyles.boldLabel); EditorGUILayout.LabelField("Target Frame Range", EditorStyles.boldLabel);
@@ -108,7 +118,7 @@ public class AnimationSectionSpeedEditor : EditorWindow
EditorGUILayout.Space(); EditorGUILayout.Space();
// ── 타임라인 시각화 ── // ── 타임라인 시각화 ──
DrawTimeline(startTime, endTime, actualLength); DrawTimeline(startTime, endTime, effectiveLength);
EditorGUILayout.Space(); EditorGUILayout.Space();
@@ -133,7 +143,7 @@ public class AnimationSectionSpeedEditor : EditorWindow
// ── 미리보기 ── // ── 미리보기 ──
float newDuration = duration / speedMultiplier; float newDuration = duration / speedMultiplier;
float timeDelta = newDuration - duration; float timeDelta = newDuration - duration;
float newLength = actualLength + timeDelta; float newLength = effectiveLength + timeDelta;
EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel); EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel);
EditorGUI.indentLevel++; EditorGUI.indentLevel++;
@@ -301,6 +311,7 @@ public class AnimationSectionSpeedEditor : EditorWindow
float newDuration = duration / speedMultiplier; float newDuration = duration / speedMultiplier;
float timeDelta = newDuration - duration; float timeDelta = newDuration - duration;
float actualLength = GetActualLastKeyframeTime(clip); float actualLength = GetActualLastKeyframeTime(clip);
float effectiveLength = actualLength > 0f ? actualLength : clip.length;
Undo.RegisterCompleteObjectUndo(clip, $"Section Speed {speedMultiplier}x (frames {startFrame}~{endFrame})"); Undo.RegisterCompleteObjectUndo(clip, $"Section Speed {speedMultiplier}x (frames {startFrame}~{endFrame})");
@@ -392,7 +403,7 @@ public class AnimationSectionSpeedEditor : EditorWindow
} }
// ── 5. m_StopTime 갱신 (SerializedObject로) ── // ── 5. m_StopTime 갱신 (SerializedObject로) ──
float newStopTime = actualLength + timeDelta; float newStopTime = effectiveLength + timeDelta;
var serializedClip = new SerializedObject(clip); var serializedClip = new SerializedObject(clip);
SerializedProperty stopTimeProp = serializedClip.FindProperty("m_StopTime"); SerializedProperty stopTimeProp = serializedClip.FindProperty("m_StopTime");
if (stopTimeProp != null) if (stopTimeProp != null)
@@ -433,7 +444,7 @@ public class AnimationSectionSpeedEditor : EditorWindow
Debug.Log($"[SectionSpeedEditor] {clip.name}: frames {startFrame}~{endFrame} → {speedMultiplier}x " + Debug.Log($"[SectionSpeedEditor] {clip.name}: frames {startFrame}~{endFrame} → {speedMultiplier}x " +
$"({duration:F3}s → {newDuration:F3}s) " + $"({duration:F3}s → {newDuration:F3}s) " +
$"| clip: {actualLength:F3}s → {newStopTime:F3}s " + $"| clip: {effectiveLength:F3}s → {newStopTime:F3}s " +
$"| curves: {modifiedCurves}, keyframes: {modifiedKeyframes}, skipped: {skippedCurves}" + $"| curves: {modifiedCurves}, keyframes: {modifiedKeyframes}, skipped: {skippedCurves}" +
$" | events: {(eventsModified ? "modified" : "none")}"); $" | events: {(eventsModified ? "modified" : "none")}");
} }