Skip to content

Instantly share code, notes, and snippets.

@phi16
Last active January 11, 2024 09:56
Show Gist options
  • Save phi16/3dac1ebbeb0b199ec004b46973355d76 to your computer and use it in GitHub Desktop.
Save phi16/3dac1ebbeb0b199ec004b46973355d76 to your computer and use it in GitHub Desktop.
Make a loop animation from non-loop (but nearly looping) animation
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class AnimationLooper : MonoBehaviour {
[SerializeField] private AnimationClip clip;
[SerializeField] private float overlapTime = 0.5f;
[SerializeField] private string path;
#if UNITY_EDITOR
private Keyframe Interp(AnimationCurve curve, Keyframe a, Keyframe b, float t) {
float it = Mathf.InverseLerp(a.time, b.time, t);
float ht = 6*it-6*it*it;
float g = ht * (b.value - a.value) / (b.time - a.time) + Mathf.Lerp(a.outTangent, b.inTangent, it) - 3 * (a.outTangent + b.inTangent) * it * (1 - it);
return new Keyframe(t, curve.Evaluate(t), g, g);
}
public AnimationCurve Process(AnimationCurve curve, float length) {
var c = new AnimationCurve();
float start = curve.Evaluate(0);
float end = curve.Evaluate(length);
float d = start - end;
Keyframe lastF = new Keyframe(0, start);
foreach(Keyframe f in curve.keys) {
if(f.weightedMode != WeightedMode.None) {
Debug.LogError($"WeightedMode.None != {f.weightedMode}");
return null;
}
if(lastF.time < overlapTime && overlapTime < f.time) {
float kt = overlapTime;
c.AddKey(Interp(curve, lastF, f, kt));
}
if(lastF.time < length - overlapTime && length - overlapTime < f.time) {
float kt = length - overlapTime;
c.AddKey(Interp(curve, lastF, f, kt));
}
bool overlap = false;
bool startSide = false;
float t = 0;
if(f.time <= overlapTime) {
overlap = true;
t = (f.time / overlapTime) * 0.5f + 0.5f;
startSide = true;
}
if(f.time >= length - overlapTime) {
overlap = true;
t = (f.time - (length - overlapTime)) / overlapTime * 0.5f;
}
Keyframe f2 = f;
if(overlap) {
float v = t*t*(3-2*t);
if(startSide) v -= 1;
float g = 6*t-6*t*t;
g /= overlapTime;
f2 = new Keyframe(f.time, f.value + v*d, f.inTangent + g*d, f.outTangent + g*d);
}
c.AddKey(f2);
lastF = f;
}
return c;
}
public void Generate() {
if(clip.length <= overlapTime*2) {
Debug.LogError("overlapTime is too long");
return;
}
AnimationClip ac = new AnimationClip();
EditorCurveBinding[] allBindings = AnimationUtility.GetCurveBindings(clip);
for(int i=0;i<allBindings.Length;i++) {
EditorCurveBinding binding = allBindings[i];
AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding);
AnimationCurve looped = Process(curve, clip.length);
ac.SetCurve(binding.path, binding.type, binding.propertyName, looped);
}
AssetDatabase.CreateAsset(ac, path);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
[CustomEditor(typeof(AnimationLooper))]
public class AnimationLooperEditor : Editor {
private SerializedProperty script;
private void OnEnable() {
script = serializedObject.FindProperty("m_Script");
}
public override void OnInspectorGUI() {
using (new EditorGUI.DisabledScope(true)) EditorGUILayout.PropertyField(script);
AnimationLooper g = target as AnimationLooper;
g.clip = EditorGUILayout.ObjectField("Clip", g.clip, typeof(AnimationClip), false) as AnimationClip;
g.overlapTime = EditorGUILayout.FloatField("Overlap Time", g.overlapTime);
g.path = EditorGUILayout.TextField("Path", g.path);
if (GUILayout.Button("Generate")) {
g.Generate();
}
}
}
#endif
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment