效果图
用途:界面动效已经由动效人员A做完(假设k了100帧),然后UI同事又把一些节点的位置做了10px的调整
此时一帧一帧去手动改,费事费力还可能出错。
这个工具的用途就是:对相关节点的所有关键帧批量做偏移。
public class AnimClipEditWnd : EditorWindow {[MenuItem("MyTools/AnimClipEdit", false)]static void ShowWindow(){var win = GetWindow(typeof(AnimClipEditWnd), false, "AnimClipEdit");win.minSize = new Vector2(500, 300);}private Vector2 m_ScrollPos;private AnimationClip m_Clip;private EditorCurveBinding[] m_CurveBindings;private List<string> m_PathList = new List<string>();private Dictionary<string, List<string>> m_PathPropNamesDict = new Dictionary<string, List<string>>();private int m_PathOptionIndex;private GUIContent[] m_PathOptions;private GUIContent m_LabelPath = new GUIContent("path");private int m_PropNameOptionIndex;private string[] m_PropNameOptions;private AnimationCurve m_Curve;private int m_CurveBindingIndex;private float m_Delta;private void OnGUI(){m_ScrollPos = EditorGUILayout.BeginScrollView(m_ScrollPos);{OnGUI_ScrollView();}EditorGUILayout.EndScrollView();}private void OnGUI_ScrollView(){var clip = (AnimationClip)EditorGUILayout.ObjectField("Clip", m_Clip, typeof(AnimationClip), false);bool isClipChange = m_Clip != clip;m_Clip = clip;if (null != clip){if (GUILayout.Button($"Refresh"))isClipChange = true;}bool isPathOptionChange = false;if (isClipChange){isPathOptionChange = true;m_CurveBindings = null;m_PathList.Clear();m_PathPropNamesDict.Clear();m_PathOptionIndex = 0;m_PathOptions = null;m_PropNameOptionIndex = 0;m_PropNameOptions = null;if (null != clip){m_CurveBindings = AnimationUtility.GetCurveBindings(clip);foreach (var b in m_CurveBindings){if (!m_PathPropNamesDict.TryGetValue(b.path, out var list)){list = new List<string>();m_PathPropNamesDict.Add(b.path, list);m_PathList.Add(b.path);}if (!IsIgnoredPropty(b.propertyName)){list.Add(b.propertyName);}}m_PathOptions = new GUIContent[m_PathList.Count];for (int i = 0; i < m_PathList.Count; ++i)m_PathOptions[i] = new GUIContent(m_PathList[i].Replace("/", "\\"));}}if (null == m_Clip) return;bool isPropNameOptionChange = false;int pathOptionIndex = EditorGUILayout.Popup(m_LabelPath, m_PathOptionIndex, m_PathOptions);if (isPathOptionChange || m_PathOptionIndex != pathOptionIndex){Debug.Log($"PathOptionChange: {m_PathOptionIndex} -> {pathOptionIndex}");m_PathOptionIndex = pathOptionIndex;string path = m_PathList[pathOptionIndex];var propNameList = m_PathPropNamesDict[path];m_PropNameOptions = propNameList.ToArray();m_PropNameOptionIndex = 0;isPropNameOptionChange = true;}int propNameOptionIndex = EditorGUILayout.Popup("propName", m_PropNameOptionIndex, m_PropNameOptions);if (isPropNameOptionChange || m_PropNameOptionIndex != propNameOptionIndex){Debug.Log($"PropNameOptionChange: {m_PropNameOptionIndex} -> {propNameOptionIndex}");m_PropNameOptionIndex = propNameOptionIndex;string path = m_PathList[pathOptionIndex];string propName = m_PropNameOptions[m_PropNameOptionIndex];UpdateCurveAndBinding(m_Clip, path, propName);}if (null != m_Curve){GUILayout.Space(10);EditorGUILayout.CurveField(m_Curve, GUILayout.Height(100));Keyframe[] keyFrames = m_Curve.keys;EditorGUILayout.LabelField("所有关键帧:");EditorGUILayout.BeginVertical("box");for (int i = 0; i < keyFrames.Length; ++i){var kf = keyFrames[i];EditorGUILayout.LabelField($"{i} -> t: {kf.time}, v: {kf.value}");}EditorGUILayout.EndVertical();}GUILayout.Space(10);m_Delta = EditorGUILayout.FloatField("Delta", m_Delta);if (GUILayout.Button("所有关键帧的值+Delta")){if (null != m_Curve){Keyframe[] keyFrames = m_Curve.keys;for (int i = 0; i < keyFrames.Length; ++i){Keyframe kf = keyFrames[i];float old = kf.value;kf.value = TrimFloat(old + m_Delta);Debug.Log($"idx:{i}, t:{TrimFloat(kf.time)}, old:{old} -> {kf.value}");m_Curve.MoveKey(i, kf);}AnimationUtility.SetEditorCurve(m_Clip, m_CurveBindings[m_CurveBindingIndex], m_Curve);EditorUtility.SetDirty(m_Clip);AssetDatabase.SaveAssets();//AssetDatabase.Refresh();Debug.Log($"finish");}}}public static float TrimFloat(float f){int i = (int)(f * 1000);float result = i / 1000.0f;return result;}private void UpdateCurveAndBinding(AnimationClip clip, string path, string propName){m_Curve = null;int index = 0;foreach (var b in m_CurveBindings){if (b.path == path){//Debug.Log($"path:{b.path}, propName:{b.propertyName}, type:{b.type.Name}, discrete:{b.isDiscreteCurve}, pptr:{b.isPPtrCurve}");if (b.propertyName == propName){m_Curve = AnimationUtility.GetEditorCurve(clip, b);m_CurveBindingIndex = index;break;}}index++;}}private bool IsIgnoredPropty(string propertyName){switch (propertyName){case "m_Maskable":case "m_IsActive":case "m_RaycastTarget":case "m_FillCenter":case "m_FillMethod":case "m_PixelsPerUnitMultiplier":case "m_PreserveAspect"://bool类型忽略return true;}return false;}}