AnimationTrackRecorder.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using UnityEngine;
  4. using UnityEngine.Timeline;
  5. namespace UnityEditor.Timeline
  6. {
  7. class AnimationTrackRecorder
  8. {
  9. public static readonly string kRecordClipDefaultName = L10n.Tr("Recorded");
  10. AnimationClip m_TargetClip;
  11. int m_CurveCount = 0;
  12. double m_ClipTime;
  13. bool m_needRebuildRects;
  14. bool m_TrackHasPreviewComponents;
  15. public TimelineClip recordClip { get; private set; }
  16. public void PrepareForRecord(WindowState state)
  17. {
  18. m_CurveCount = 0;
  19. m_TargetClip = null;
  20. m_TrackHasPreviewComponents = false;
  21. }
  22. public AnimationClip PrepareTrack(TrackAsset track, WindowState state, GameObject gameObject, out double startTime)
  23. {
  24. AnimationClip animationClip = null;
  25. // if we are not in clip mode, we simply use the track clip
  26. var animationTrack = (AnimationTrack)track;
  27. // ignore recording if we are in Legacy auto mode
  28. startTime = -1;
  29. var parentTrack = TimelineUtility.GetSceneReferenceTrack(track) as AnimationTrack;
  30. if (parentTrack != null && parentTrack.trackOffset == TrackOffset.Auto)
  31. return null;
  32. if (!animationTrack.inClipMode)
  33. {
  34. var trackClip = animationTrack.GetOrCreateClip();
  35. startTime = trackClip.frameRate * state.editSequence.time;
  36. // Make the first key be at time 0 of the clip
  37. if (trackClip.empty)
  38. {
  39. animationTrack.infiniteClipTimeOffset = 0; // state.time;
  40. animationTrack.infiniteClipPreExtrapolation = TimelineClip.ClipExtrapolation.Hold;
  41. animationTrack.infiniteClipPostExtrapolation = TimelineClip.ClipExtrapolation.Hold;
  42. }
  43. animationClip = trackClip;
  44. }
  45. else
  46. {
  47. TimelineClip activeClip = null;
  48. // if it fails, but returns no clip, we can add one.
  49. if (!track.FindRecordingClipAtTime(state.editSequence.time, out activeClip) && activeClip != null)
  50. {
  51. return null;
  52. }
  53. if (activeClip == null)
  54. {
  55. activeClip = AddRecordableClip(track, state, state.editSequence.time);
  56. }
  57. var clip = activeClip.animationClip;
  58. // flags this as the clip being recorded for the track
  59. var clipTime = state.editSequence.time - activeClip.start;
  60. // if we are in the past
  61. if (clipTime < 0)
  62. {
  63. Undo.RegisterCompleteObjectUndo(clip, "Record Key");
  64. TimelineUndo.PushUndo(track, "Prepend Key");
  65. ShiftAnimationClip(clip, (float)-clipTime);
  66. activeClip.start = state.editSequence.time;
  67. activeClip.duration += -clipTime;
  68. clipTime = 0;
  69. }
  70. m_ClipTime = clipTime;
  71. recordClip = activeClip;
  72. startTime = TimeUtility.ToFrames(recordClip.ToLocalTimeUnbound(state.editSequence.time), clip.frameRate);
  73. m_needRebuildRects = clip.empty;
  74. animationClip = clip;
  75. }
  76. m_TargetClip = animationClip;
  77. m_CurveCount = GetCurveCount(animationClip);
  78. m_TrackHasPreviewComponents = animationTrack.hasPreviewComponents;
  79. return animationClip;
  80. }
  81. static int GetCurveCount(AnimationClip animationClip)
  82. {
  83. int count = 0;
  84. if (animationClip != null)
  85. {
  86. var clipCache = AnimationClipCurveCache.Instance.GetCurveInfo(animationClip);
  87. count = clipCache.curves.Length + clipCache.objectCurves.Count;
  88. }
  89. return count;
  90. }
  91. public void FinializeTrack(TrackAsset track, WindowState state)
  92. {
  93. // make sure we dirty the clip if we are in non clip mode
  94. var animTrack = track as AnimationTrack;
  95. if (!animTrack.inClipMode)
  96. {
  97. EditorUtility.SetDirty(animTrack.GetOrCreateClip());
  98. }
  99. // in clip mode we need to do some extra work
  100. if (recordClip != null)
  101. {
  102. // stretch the clip out to meet the new recording time
  103. if (m_ClipTime > recordClip.duration)
  104. {
  105. TimelineUndo.PushUndo(track, "Add Key");
  106. recordClip.duration = m_ClipTime;
  107. }
  108. track.CalculateExtrapolationTimes();
  109. }
  110. recordClip = null;
  111. m_ClipTime = 0;
  112. if (m_needRebuildRects)
  113. {
  114. state.CalculateRowRects();
  115. m_needRebuildRects = false;
  116. }
  117. }
  118. public void FinalizeRecording(WindowState state)
  119. {
  120. // rebuild the graph if we add/remove a clip. Rebuild the graph with an evaluation immediately
  121. // so previews and scene position is maintained.
  122. if (m_CurveCount != GetCurveCount(m_TargetClip))
  123. {
  124. state.rebuildGraph = true;
  125. state.GetWindow().RebuildGraphIfNecessary(true);
  126. }
  127. else if (m_TrackHasPreviewComponents)
  128. {
  129. // Track with preview components potentially has modifications impacting other properties that need
  130. // to be refreshed before inspector or scene view to not interfere with manipulation.
  131. state.EvaluateImmediate();
  132. }
  133. }
  134. // For a given track asset get a unique clip name
  135. public static string GetUniqueRecordedClipName(Object owner, string name)
  136. {
  137. // first attempt -- uniquely named in file
  138. var path = AssetDatabase.GetAssetPath(owner);
  139. if (!string.IsNullOrEmpty(path))
  140. {
  141. var names = AssetDatabase.LoadAllAssetsAtPath(path).Where(x => x != null).Select(x => x.name);
  142. return ObjectNames.GetUniqueName(names.ToArray(), name);
  143. }
  144. TrackAsset asset = owner as TrackAsset;
  145. if (asset == null || asset.clips.Length == 0)
  146. return name;
  147. // final attempt - uniquely named in track
  148. return ObjectNames.GetUniqueName(asset.clips.Select(x => x.displayName).ToArray(), name);
  149. }
  150. // Given an appropriate parent track, create a recordable clip
  151. public static TimelineClip AddRecordableClip(TrackAsset parentTrack, WindowState state, double atTime)
  152. {
  153. var sequenceAsset = state.editSequence.asset;
  154. if (sequenceAsset == null)
  155. {
  156. Debug.LogError("Parent Track needs to be bound to an asset to add a recordable");
  157. return null;
  158. }
  159. var animTrack = parentTrack as AnimationTrack;
  160. if (animTrack == null)
  161. {
  162. Debug.LogError("Recordable clips are only valid on Animation Tracks");
  163. return null;
  164. }
  165. var newClip = animTrack.CreateRecordableClip(GetUniqueRecordedClipName(parentTrack, kRecordClipDefaultName));
  166. if (newClip == null)
  167. {
  168. Debug.LogError("Could not create a recordable clip");
  169. return null;
  170. }
  171. newClip.mixInCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
  172. newClip.mixOutCurve = AnimationCurve.EaseInOut(0, 1, 1, 0);
  173. newClip.preExtrapolationMode = TimelineClip.ClipExtrapolation.Hold;
  174. newClip.postExtrapolationMode = TimelineClip.ClipExtrapolation.Hold;
  175. double startTime = 0;
  176. double endTime = 0;
  177. GetAddedRecordingClipRange(animTrack, state, atTime, out startTime, out endTime);
  178. newClip.start = startTime;
  179. newClip.duration = endTime - startTime;
  180. state.Refresh();
  181. return newClip;
  182. }
  183. // get the start and end times of what an added recording clip at a given time would be
  184. internal static void GetAddedRecordingClipRange(TrackAsset track, WindowState state, double atTime, out double start, out double end)
  185. {
  186. // size to make the clip in pixels. Reasonably big so that both handles are easily manipulated,
  187. // and the full title is normally visible
  188. double defaultDuration = state.PixelDeltaToDeltaTime(100);
  189. start = atTime;
  190. end = atTime + defaultDuration;
  191. double gapStart = 0;
  192. double gapEnd = 0;
  193. // no gap, pick are reasonable amount
  194. if (!track.GetGapAtTime(atTime, out gapStart, out gapEnd))
  195. {
  196. start = atTime;
  197. return;
  198. }
  199. if (!double.IsInfinity(gapEnd))
  200. end = gapEnd;
  201. start = state.SnapToFrameIfRequired(start);
  202. end = state.SnapToFrameIfRequired(end);
  203. }
  204. // Given a clip, shifts the keys in that clip by the given amount.
  205. internal static void ShiftAnimationClip(AnimationClip clip, float amount)
  206. {
  207. if (clip == null)
  208. return;
  209. var curveBindings = AnimationUtility.GetCurveBindings(clip);
  210. var objectCurveBindings = AnimationUtility.GetObjectReferenceCurveBindings(clip);
  211. foreach (var binding in curveBindings)
  212. {
  213. AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding);
  214. curve.keys = ShiftKeys(curve.keys, amount);
  215. AnimationUtility.SetEditorCurve(clip, binding, curve);
  216. }
  217. foreach (var binding in objectCurveBindings)
  218. {
  219. ObjectReferenceKeyframe[] keyframes = AnimationUtility.GetObjectReferenceCurve(clip, binding);
  220. keyframes = ShiftObjectKeys(keyframes, amount);
  221. AnimationUtility.SetObjectReferenceCurve(clip, binding, keyframes);
  222. }
  223. EditorUtility.SetDirty(clip);
  224. }
  225. // shift all the keys over by the given time, stretching the time 0 key
  226. static Keyframe[] ShiftKeys(Keyframe[] keys, float time)
  227. {
  228. if (keys == null || keys.Length == 0 || time == 0)
  229. return keys;
  230. for (int i = 0; i < keys.Length; i++)
  231. {
  232. keys[i].time += time;
  233. }
  234. return keys;
  235. }
  236. // Shift object keys over by the appropriate amount
  237. static ObjectReferenceKeyframe[] ShiftObjectKeys(ObjectReferenceKeyframe[] keys, float time)
  238. {
  239. if (keys == null || keys.Length == 0 || time == 0)
  240. return keys;
  241. for (int i = 0; i < keys.Length; i++)
  242. {
  243. keys[i].time += time;
  244. }
  245. return keys;
  246. }
  247. }
  248. }