fix: 애니메이션 클립 트리머 경계값과 참조 커브 보존
- 트리밍 구간에 기존 키가 없더라도 시작점과 종료점 값을 샘플링해 경계 구간이 비지 않도록 보정 - ObjectReference 커브를 함께 잘라 VFX와 소품 전환 키가 새 클립에서도 유지되도록 수정 - 새 클립 생성 시 루트 모션, 루프, 미러 등 AnimationClipSettings를 원본에서 복사하도록 보강
This commit is contained in:
@@ -158,7 +158,7 @@ public class AnimationClipTrimmerWindow : EditorWindow
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
AnimationCurve trimmedCurve = TrimCurve(sourceCurve, startTime, endTime);
|
AnimationCurve trimmedCurve = TrimCurve(sourceCurve, startTime, endTime);
|
||||||
if (trimmedCurve.keys.Length > 0)
|
if (trimmedCurve != null && trimmedCurve.keys.Length > 0)
|
||||||
{
|
{
|
||||||
trimmedClip.SetCurve(binding.path, binding.type, binding.propertyName, trimmedCurve);
|
trimmedClip.SetCurve(binding.path, binding.type, binding.propertyName, trimmedCurve);
|
||||||
}
|
}
|
||||||
@@ -191,8 +191,21 @@ public class AnimationClipTrimmerWindow : EditorWindow
|
|||||||
AnimationUtility.SetAnimationEvents(trimmedClip, trimmedEvents.ToArray());
|
AnimationUtility.SetAnimationEvents(trimmedClip, trimmedEvents.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Object reference 커브도 함께 복사해 VFX/소품 전환이 유지되도록 합니다.
|
||||||
|
EditorCurveBinding[] objectBindings = AnimationUtility.GetObjectReferenceCurveBindings(sourceClip);
|
||||||
|
foreach (EditorCurveBinding binding in objectBindings)
|
||||||
|
{
|
||||||
|
ObjectReferenceKeyframe[] sourceKeys = AnimationUtility.GetObjectReferenceCurve(sourceClip, binding);
|
||||||
|
ObjectReferenceKeyframe[] trimmedKeys = TrimObjectReferenceCurve(sourceKeys, startTime, endTime);
|
||||||
|
if (trimmedKeys != null && trimmedKeys.Length > 0)
|
||||||
|
{
|
||||||
|
AnimationUtility.SetObjectReferenceCurve(trimmedClip, binding, trimmedKeys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 저장
|
// 저장
|
||||||
AssetDatabase.CreateAsset(trimmedClip, savePath);
|
AssetDatabase.CreateAsset(trimmedClip, savePath);
|
||||||
|
CopyClipSettings(sourceClip, trimmedClip, endTime - startTime);
|
||||||
AssetDatabase.SaveAssets();
|
AssetDatabase.SaveAssets();
|
||||||
|
|
||||||
EditorUtility.FocusProjectWindow();
|
EditorUtility.FocusProjectWindow();
|
||||||
@@ -210,8 +223,12 @@ public class AnimationClipTrimmerWindow : EditorWindow
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static AnimationCurve TrimCurve(AnimationCurve curve, float startTime, float endTime)
|
private static AnimationCurve TrimCurve(AnimationCurve curve, float startTime, float endTime)
|
||||||
{
|
{
|
||||||
|
if (curve == null || endTime < startTime)
|
||||||
|
return null;
|
||||||
|
|
||||||
Keyframe[] sourceKeys = curve.keys;
|
Keyframe[] sourceKeys = curve.keys;
|
||||||
List<Keyframe> trimmedKeys = new List<Keyframe>();
|
List<Keyframe> trimmedKeys = new List<Keyframe>();
|
||||||
|
float duration = endTime - startTime;
|
||||||
|
|
||||||
foreach (Keyframe key in sourceKeys)
|
foreach (Keyframe key in sourceKeys)
|
||||||
{
|
{
|
||||||
@@ -226,8 +243,50 @@ public class AnimationClipTrimmerWindow : EditorWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 선택 구간에 기존 키가 없더라도 경계 값을 샘플링해 고정 구간을 유지합니다.
|
||||||
if (trimmedKeys.Count == 0)
|
if (trimmedKeys.Count == 0)
|
||||||
return null;
|
{
|
||||||
|
float startValue = curve.Evaluate(startTime);
|
||||||
|
float endValue = curve.Evaluate(endTime);
|
||||||
|
|
||||||
|
trimmedKeys.Add(new Keyframe(0f, startValue));
|
||||||
|
|
||||||
|
if (duration > Mathf.Epsilon)
|
||||||
|
trimmedKeys.Add(new Keyframe(duration, endValue));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float startValue = curve.Evaluate(startTime);
|
||||||
|
float endValue = curve.Evaluate(endTime);
|
||||||
|
|
||||||
|
if (!Mathf.Approximately(trimmedKeys[0].time, 0f))
|
||||||
|
{
|
||||||
|
trimmedKeys.Insert(0, new Keyframe(0f, startValue));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Keyframe firstKey = trimmedKeys[0];
|
||||||
|
firstKey.time = 0f;
|
||||||
|
firstKey.value = startValue;
|
||||||
|
trimmedKeys[0] = firstKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duration > Mathf.Epsilon)
|
||||||
|
{
|
||||||
|
int lastIndex = trimmedKeys.Count - 1;
|
||||||
|
if (!Mathf.Approximately(trimmedKeys[lastIndex].time, duration))
|
||||||
|
{
|
||||||
|
trimmedKeys.Add(new Keyframe(duration, endValue));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Keyframe lastKey = trimmedKeys[lastIndex];
|
||||||
|
lastKey.time = duration;
|
||||||
|
lastKey.value = endValue;
|
||||||
|
trimmedKeys[lastIndex] = lastKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new AnimationCurve(trimmedKeys.ToArray())
|
return new AnimationCurve(trimmedKeys.ToArray())
|
||||||
{
|
{
|
||||||
@@ -235,4 +294,150 @@ public class AnimationClipTrimmerWindow : EditorWindow
|
|||||||
postWrapMode = curve.postWrapMode
|
postWrapMode = curve.postWrapMode
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Object reference 커브에서 선택 구간만 잘라내고 시간을 0부터 다시 시작합니다.
|
||||||
|
/// </summary>
|
||||||
|
private static ObjectReferenceKeyframe[] TrimObjectReferenceCurve(
|
||||||
|
ObjectReferenceKeyframe[] sourceKeys,
|
||||||
|
float startTime,
|
||||||
|
float endTime)
|
||||||
|
{
|
||||||
|
if (sourceKeys == null || sourceKeys.Length == 0 || endTime < startTime)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
List<ObjectReferenceKeyframe> trimmedKeys = new List<ObjectReferenceKeyframe>();
|
||||||
|
float duration = endTime - startTime;
|
||||||
|
|
||||||
|
for (int i = 0; i < sourceKeys.Length; i++)
|
||||||
|
{
|
||||||
|
ObjectReferenceKeyframe key = sourceKeys[i];
|
||||||
|
if (key.time >= startTime && key.time <= endTime)
|
||||||
|
{
|
||||||
|
trimmedKeys.Add(new ObjectReferenceKeyframe
|
||||||
|
{
|
||||||
|
time = key.time - startTime,
|
||||||
|
value = key.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmedKeys.Count == 0)
|
||||||
|
{
|
||||||
|
ObjectReferenceKeyframe sampledKey = sourceKeys[0];
|
||||||
|
for (int i = sourceKeys.Length - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (sourceKeys[i].time <= startTime)
|
||||||
|
{
|
||||||
|
sampledKey = sourceKeys[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmedKeys.Add(new ObjectReferenceKeyframe
|
||||||
|
{
|
||||||
|
time = 0f,
|
||||||
|
value = sampledKey.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Mathf.Approximately(trimmedKeys[0].time, 0f))
|
||||||
|
{
|
||||||
|
trimmedKeys.Insert(0, new ObjectReferenceKeyframe
|
||||||
|
{
|
||||||
|
time = 0f,
|
||||||
|
value = trimmedKeys[0].value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastIndex = trimmedKeys.Count - 1;
|
||||||
|
if (duration > Mathf.Epsilon && !Mathf.Approximately(trimmedKeys[lastIndex].time, duration))
|
||||||
|
{
|
||||||
|
trimmedKeys.Add(new ObjectReferenceKeyframe
|
||||||
|
{
|
||||||
|
time = duration,
|
||||||
|
value = trimmedKeys[lastIndex].value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimmedKeys.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 원본 클립의 루트 모션/루프/미러 설정을 새 클립에 유지합니다.
|
||||||
|
/// </summary>
|
||||||
|
private static void CopyClipSettings(AnimationClip source, AnimationClip target, float duration)
|
||||||
|
{
|
||||||
|
if (source == null || target == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SerializedObject sourceSerializedObject = new SerializedObject(source);
|
||||||
|
SerializedObject targetSerializedObject = new SerializedObject(target);
|
||||||
|
|
||||||
|
SerializedProperty sourceSettings = sourceSerializedObject.FindProperty("m_AnimationClipSettings");
|
||||||
|
SerializedProperty targetSettings = targetSerializedObject.FindProperty("m_AnimationClipSettings");
|
||||||
|
if (sourceSettings == null || targetSettings == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string[] relativePropertyNames =
|
||||||
|
{
|
||||||
|
"m_AdditiveReferencePoseClip",
|
||||||
|
"m_AdditiveReferencePoseTime",
|
||||||
|
"m_OrientationOffsetY",
|
||||||
|
"m_Level",
|
||||||
|
"m_CycleOffset",
|
||||||
|
"m_HasAdditiveReferencePose",
|
||||||
|
"m_LoopTime",
|
||||||
|
"m_LoopBlend",
|
||||||
|
"m_LoopBlendOrientation",
|
||||||
|
"m_LoopBlendPositionY",
|
||||||
|
"m_LoopBlendPositionXZ",
|
||||||
|
"m_KeepOriginalOrientation",
|
||||||
|
"m_KeepOriginalPositionY",
|
||||||
|
"m_KeepOriginalPositionXZ",
|
||||||
|
"m_HeightFromFeet",
|
||||||
|
"m_Mirror"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < relativePropertyNames.Length; i++)
|
||||||
|
{
|
||||||
|
SerializedProperty sourceProperty = sourceSettings.FindPropertyRelative(relativePropertyNames[i]);
|
||||||
|
SerializedProperty targetProperty = targetSettings.FindPropertyRelative(relativePropertyNames[i]);
|
||||||
|
if (sourceProperty == null || targetProperty == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
CopySerializedPropertyValue(sourceProperty, targetProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
SerializedProperty startTimeProperty = targetSettings.FindPropertyRelative("m_StartTime");
|
||||||
|
if (startTimeProperty != null)
|
||||||
|
startTimeProperty.floatValue = 0f;
|
||||||
|
|
||||||
|
SerializedProperty stopTimeProperty = targetSettings.FindPropertyRelative("m_StopTime");
|
||||||
|
if (stopTimeProperty != null)
|
||||||
|
stopTimeProperty.floatValue = Mathf.Max(0f, duration);
|
||||||
|
|
||||||
|
targetSerializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CopySerializedPropertyValue(SerializedProperty source, SerializedProperty target)
|
||||||
|
{
|
||||||
|
switch (source.propertyType)
|
||||||
|
{
|
||||||
|
case SerializedPropertyType.Integer:
|
||||||
|
target.intValue = source.intValue;
|
||||||
|
break;
|
||||||
|
case SerializedPropertyType.Boolean:
|
||||||
|
target.boolValue = source.boolValue;
|
||||||
|
break;
|
||||||
|
case SerializedPropertyType.Float:
|
||||||
|
target.floatValue = source.floatValue;
|
||||||
|
break;
|
||||||
|
case SerializedPropertyType.ObjectReference:
|
||||||
|
target.objectReferenceValue = source.objectReferenceValue;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user