117 lines
4.5 KiB
C#

using System.IO;
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
public class AnimationEditor {
[MenuItem("Assets/Scale Animation Transforms...", true)]
private static bool ValidateScaleMenu() {
// enable only when an AnimationClip is selected
return Selection.objects != null && Selection.objects.Length > 0 && System.Array.Exists(Selection.objects, o => o is AnimationClip);
}
[MenuItem("Assets/Scale Animation Transforms...")]
private static void ScaleSelectedClips() {
// default scale - you can change or prompt
float scale = 2.0f;
var clips = new List<AnimationClip>();
foreach (var o in Selection.objects)
if (o is AnimationClip clip)
clips.Add(clip);
if (clips.Count == 0) {
EditorUtility.DisplayDialog("Scale Animation", "No AnimationClip selected.", "OK");
return;
}
if (!EditorUtility.DisplayDialog("Scale Animation", $"Scale translations in {clips.Count} clip(s) by {scale}?", "Yes", "No"))
return;
foreach (AnimationClip clip in clips) {
ScaleClip(clip, scale);
}
EditorUtility.DisplayDialog("Scale Animation", "Done scaling selected clips.", "OK");
}
public static void ScaleClip(AnimationClip clip, float scale, string saveFolder) {
string origPath = AssetDatabase.GetAssetPath(clip);
string ext = Path.GetExtension(origPath);
string nameNoExt = Path.GetFileNameWithoutExtension(origPath);
string fileName = $"{nameNoExt}_scaled{ext}";
string savePath = Path.Combine(saveFolder, fileName).Replace("\\", "/");
if (AssetDatabase.LoadAssetAtPath<Object>(savePath) != null) {
if (!AssetDatabase.DeleteAsset(savePath)) {
Debug.LogError($"Failed to delete existing asset at {savePath}");
return;
}
Debug.Log($"Deleted old {savePath}");
AssetDatabase.Refresh();
}
if (!AssetDatabase.CopyAsset(origPath, savePath)) {
Debug.LogError($"Failed to duplicate asset: {origPath} -> {savePath}");
return;
}
AssetDatabase.ImportAsset(savePath);
AnimationClip newClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(savePath);
if (newClip == null) {
Debug.LogError($"Failed to load duplicated clip at: {savePath}");
return;
}
ScaleClip(newClip, scale);
}
public static void ScaleClip(AnimationClip clip, float scale) {
// make editable copy if needed (if clip is in model import, make sure to duplicate or modify import settings)
Undo.RegisterCompleteObjectUndo(clip, "Scale Animation Translations");
// Collect curve bindings for position-like properties.
EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(clip);
foreach (EditorCurveBinding binding in bindings) {
string prop = binding.propertyName;
if (string.IsNullOrEmpty(prop))
continue;
// Identify position/localPosition/property patterns to scale:
// common property names:
// "m_LocalPosition.x", "m_LocalPosition.y", "m_LocalPosition.z"
// "localPosition.x", etc. or custom paths that animate transform.localPosition
// We'll scale any curve with "position" or "LocalPosition" in propertyName (case-insensitive).
string lower = prop.ToLowerInvariant();
bool isPos =
lower.Contains("m_localposition") ||
lower.Contains("localposition") ||
lower.Contains(".position") ||
lower == "position.x" || lower == "position.y" || lower == "position.z";
if (!isPos)
continue;
AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding);
if (curve == null) continue;
// scale each keyframe value
Keyframe[] keys = curve.keys;
for (int i = 0; i < keys.Length; i++) {
Keyframe k = keys[i];
k.value *= scale;
k.inTangent *= scale;
k.outTangent *= scale;
keys[i] = k;
}
curve.keys = keys;
AnimationUtility.SetEditorCurve(clip, binding, curve);
}
EditorUtility.SetDirty(clip);
AssetDatabase.SaveAssets();
}
}