TimelineRecording.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using UnityEngine;
  4. using UnityEngine.Timeline;
  5. using UnityEngine.Playables;
  6. namespace UnityEditor.Timeline
  7. {
  8. // Handles Undo animated properties on Monobehaviours to create track clips
  9. static partial class TimelineRecording
  10. {
  11. static readonly List<PropertyModification> s_TempPropertyModifications = new List<PropertyModification>(6);
  12. internal static UndoPropertyModification[] ProcessUndoModification(UndoPropertyModification[] modifications, WindowState state)
  13. {
  14. if (HasAnyPlayableAssetModifications(modifications))
  15. return ProcessPlayableAssetModification(modifications, state);
  16. return ProcessMonoBehaviourModification(modifications, state);
  17. }
  18. static UnityEngine.Object GetTarget(UndoPropertyModification undo)
  19. {
  20. if (undo.currentValue != null)
  21. return undo.currentValue.target;
  22. if (undo.previousValue != null)
  23. return undo.previousValue.target;
  24. return null;
  25. }
  26. // Gets the appropriate track for a given game object
  27. static TrackAsset GetTrackForGameObject(GameObject gameObject, WindowState state)
  28. {
  29. if (gameObject == null)
  30. return null;
  31. var director = state.editSequence.director;
  32. if (director == null)
  33. return null;
  34. var level = int.MaxValue;
  35. TrackAsset result = null;
  36. // search the output tracks
  37. var outputTracks = state.editSequence.asset.flattenedTracks;
  38. foreach (var track in outputTracks)
  39. {
  40. if (track.GetType() != typeof(AnimationTrack))
  41. continue;
  42. if (!state.IsTrackRecordable(track))
  43. continue;
  44. var obj = TimelineUtility.GetSceneGameObject(director, track);
  45. if (obj != null)
  46. {
  47. // checks if the effected gameobject is our child
  48. var childLevel = GetChildLevel(obj, gameObject);
  49. if (childLevel != -1 && childLevel < level)
  50. {
  51. result = track;
  52. level = childLevel;
  53. }
  54. }
  55. }
  56. // the resulting track is not armed. checking here avoids accidentally recording objects with their own
  57. // tracks
  58. if (result && !state.IsTrackRecordable(result))
  59. {
  60. result = null;
  61. }
  62. return result;
  63. }
  64. // Gets the track this property would record to.
  65. // Returns null if there is a track, but it's not currently active for recording
  66. public static TrackAsset GetRecordingTrack(SerializedProperty property, WindowState state)
  67. {
  68. var serializedObject = property.serializedObject;
  69. var component = serializedObject.targetObject as Component;
  70. if (component == null)
  71. return null;
  72. var gameObject = component.gameObject;
  73. return GetTrackForGameObject(gameObject, state);
  74. }
  75. // Given a serialized property, gathers all animatable properties
  76. static void GatherModifications(SerializedProperty property, List<PropertyModification> modifications)
  77. {
  78. // handles child properties (Vector3 is 3 recordable properties)
  79. if (property.hasChildren)
  80. {
  81. var iter = property.Copy();
  82. var end = property.GetEndProperty(false);
  83. // recurse over all children properties
  84. while (iter.Next(true) && !SerializedProperty.EqualContents(iter, end))
  85. {
  86. GatherModifications(iter, modifications);
  87. }
  88. }
  89. var isObject = property.propertyType == SerializedPropertyType.ObjectReference;
  90. var isFloat = property.propertyType == SerializedPropertyType.Float ||
  91. property.propertyType == SerializedPropertyType.Boolean ||
  92. property.propertyType == SerializedPropertyType.Integer;
  93. if (isObject || isFloat)
  94. {
  95. var serializedObject = property.serializedObject;
  96. var modification = new PropertyModification();
  97. modification.target = serializedObject.targetObject;
  98. modification.propertyPath = property.propertyPath;
  99. if (isObject)
  100. {
  101. modification.value = string.Empty;
  102. modification.objectReference = property.objectReferenceValue;
  103. }
  104. else
  105. {
  106. modification.value = TimelineUtility.PropertyToString(property);
  107. }
  108. // Path for monobehaviour based - better to grab the component to get the curvebinding to allow validation
  109. if (serializedObject.targetObject is Component)
  110. {
  111. EditorCurveBinding temp;
  112. var go = ((Component)serializedObject.targetObject).gameObject;
  113. if (AnimationUtility.PropertyModificationToEditorCurveBinding(modification, go, out temp) != null)
  114. {
  115. modifications.Add(modification);
  116. }
  117. }
  118. else
  119. {
  120. modifications.Add(modification);
  121. }
  122. }
  123. }
  124. public static bool CanRecord(SerializedProperty property, WindowState state)
  125. {
  126. if (IsPlayableAssetProperty(property))
  127. return AnimatedParameterUtility.IsTypeAnimatable(property.propertyType);
  128. if (GetRecordingTrack(property, state) == null)
  129. return false;
  130. s_TempPropertyModifications.Clear();
  131. GatherModifications(property, s_TempPropertyModifications);
  132. return s_TempPropertyModifications.Any();
  133. }
  134. public static void AddKey(SerializedProperty prop, WindowState state)
  135. {
  136. s_TempPropertyModifications.Clear();
  137. GatherModifications(prop, s_TempPropertyModifications);
  138. if (s_TempPropertyModifications.Any())
  139. {
  140. AddKey(s_TempPropertyModifications, state);
  141. }
  142. }
  143. public static void AddKey(IEnumerable<PropertyModification> modifications, WindowState state)
  144. {
  145. var undos = modifications.Select(PropertyModificationToUndoPropertyModification).ToArray();
  146. ProcessUndoModification(undos, state);
  147. }
  148. static UndoPropertyModification PropertyModificationToUndoPropertyModification(PropertyModification prop)
  149. {
  150. return new UndoPropertyModification
  151. {
  152. previousValue = prop,
  153. currentValue = new PropertyModification
  154. {
  155. objectReference = prop.objectReference,
  156. propertyPath = prop.propertyPath,
  157. target = prop.target,
  158. value = prop.value
  159. },
  160. keepPrefabOverride = true
  161. };
  162. }
  163. // Given an animation track, return the clip that we are currently recording to
  164. static AnimationClip GetRecordingClip(TrackAsset asset, WindowState state, out double startTime, out double timeScale)
  165. {
  166. startTime = 0;
  167. timeScale = 1;
  168. TimelineClip displayBackground = null;
  169. asset.FindRecordingClipAtTime(state.editSequence.time, out displayBackground);
  170. var animClip = asset.FindRecordingAnimationClipAtTime(state.editSequence.time);
  171. if (displayBackground != null)
  172. {
  173. startTime = displayBackground.start;
  174. timeScale = displayBackground.timeScale;
  175. }
  176. return animClip;
  177. }
  178. // Helper that finds the animation clip we are recording and the relative time to that clip
  179. static bool GetClipAndRelativeTime(UnityEngine.Object target, WindowState state,
  180. out AnimationClip outClip, out double keyTime, out bool keyInRange)
  181. {
  182. const float floatToDoubleError = 0.00001f;
  183. outClip = null;
  184. keyTime = 0;
  185. keyInRange = false;
  186. double startTime = 0;
  187. double timeScale = 1;
  188. AnimationClip clip = null;
  189. IPlayableAsset playableAsset = target as IPlayableAsset;
  190. Component component = target as Component;
  191. // Handle recordable playable assets
  192. if (playableAsset != null)
  193. {
  194. var curvesOwner = AnimatedParameterUtility.ToCurvesOwner(playableAsset, state.editSequence.asset);
  195. if (curvesOwner != null && state.IsTrackRecordable(curvesOwner.targetTrack))
  196. {
  197. if (curvesOwner.curves == null)
  198. curvesOwner.CreateCurves(curvesOwner.GetUniqueRecordedClipName());
  199. clip = curvesOwner.curves;
  200. var timelineClip = curvesOwner as TimelineClip;
  201. if (timelineClip != null)
  202. {
  203. startTime = timelineClip.start;
  204. timeScale = timelineClip.timeScale;
  205. }
  206. }
  207. }
  208. // Handle recording components, including infinite clip
  209. else if (component != null)
  210. {
  211. var asset = GetTrackForGameObject(component.gameObject, state);
  212. if (asset != null)
  213. {
  214. clip = GetRecordingClip(asset, state, out startTime, out timeScale);
  215. }
  216. }
  217. if (clip == null)
  218. return false;
  219. keyTime = (state.editSequence.time - startTime) * timeScale;
  220. outClip = clip;
  221. keyInRange = keyTime >= 0 && keyTime <= (clip.length * timeScale + floatToDoubleError);
  222. return true;
  223. }
  224. public static bool HasCurve(IEnumerable<PropertyModification> modifications, UnityEngine.Object target,
  225. WindowState state)
  226. {
  227. return GetKeyTimes(target, modifications, state).Any();
  228. }
  229. public static bool HasKey(IEnumerable<PropertyModification> modifications, UnityEngine.Object target,
  230. WindowState state)
  231. {
  232. AnimationClip clip;
  233. double keyTime;
  234. bool inRange;
  235. if (!GetClipAndRelativeTime(target, state, out clip, out keyTime, out inRange))
  236. return false;
  237. return GetKeyTimes(target, modifications, state).Any(t => (CurveEditUtility.KeyCompare((float)state.editSequence.time, (float)t, clip.frameRate) == 0));
  238. }
  239. // Checks if a key already exists for this property
  240. static bool HasBinding(UnityEngine.Object target, PropertyModification modification, AnimationClip clip, out EditorCurveBinding binding)
  241. {
  242. var component = target as Component;
  243. var playableAsset = target as IPlayableAsset;
  244. if (component != null)
  245. {
  246. var type = AnimationUtility.PropertyModificationToEditorCurveBinding(modification, component.gameObject, out binding);
  247. binding = RotationCurveInterpolation.RemapAnimationBindingForRotationCurves(binding, clip);
  248. return type != null;
  249. }
  250. if (playableAsset != null)
  251. {
  252. binding = EditorCurveBinding.FloatCurve(string.Empty, target.GetType(),
  253. AnimatedParameterUtility.GetAnimatedParameterBindingName(target, modification.propertyPath));
  254. }
  255. else
  256. {
  257. binding = new EditorCurveBinding();
  258. return false;
  259. }
  260. return true;
  261. }
  262. public static void RemoveKey(UnityEngine.Object target, IEnumerable<PropertyModification> modifications,
  263. WindowState state)
  264. {
  265. AnimationClip clip;
  266. double keyTime;
  267. bool inRange;
  268. if (!GetClipAndRelativeTime(target, state, out clip, out keyTime, out inRange) || !inRange)
  269. return;
  270. var refreshPreview = false;
  271. TimelineUndo.PushUndo(clip, "Remove Key");
  272. foreach (var mod in modifications)
  273. {
  274. EditorCurveBinding temp;
  275. if (HasBinding(target, mod, clip, out temp))
  276. {
  277. if (temp.isPPtrCurve)
  278. {
  279. CurveEditUtility.RemoveObjectKey(clip, temp, keyTime);
  280. if (CurveEditUtility.GetObjectKeyCount(clip, temp) == 0)
  281. {
  282. refreshPreview = true;
  283. }
  284. }
  285. else
  286. {
  287. AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, temp);
  288. if (curve != null)
  289. {
  290. CurveEditUtility.RemoveKeyFrameFromCurve(curve, (float)keyTime, clip.frameRate);
  291. AnimationUtility.SetEditorCurve(clip, temp, curve);
  292. if (curve.length == 0)
  293. {
  294. AnimationUtility.SetEditorCurve(clip, temp, null);
  295. refreshPreview = true;
  296. }
  297. }
  298. }
  299. }
  300. }
  301. if (refreshPreview)
  302. {
  303. state.ResetPreviewMode();
  304. }
  305. }
  306. static HashSet<double> GetKeyTimes(UnityEngine.Object target, IEnumerable<PropertyModification> modifications, WindowState state)
  307. {
  308. var keyTimes = new HashSet<double>();
  309. AnimationClip animationClip;
  310. double keyTime;
  311. bool inRange;
  312. GetClipAndRelativeTime(target, state, out animationClip, out keyTime, out inRange);
  313. if (animationClip == null)
  314. return keyTimes;
  315. var component = target as Component;
  316. var playableAsset = target as IPlayableAsset;
  317. var info = AnimationClipCurveCache.Instance.GetCurveInfo(animationClip);
  318. TimelineClip clip = null;
  319. if (component != null)
  320. {
  321. GetTrackForGameObject(component.gameObject, state).FindRecordingClipAtTime(state.editSequence.time, out clip);
  322. }
  323. else if (playableAsset != null)
  324. {
  325. clip = FindClipWithAsset(state.editSequence.asset, playableAsset);
  326. }
  327. foreach (var mod in modifications)
  328. {
  329. EditorCurveBinding temp;
  330. if (HasBinding(target, mod, animationClip, out temp))
  331. {
  332. IEnumerable<double> keys = new HashSet<double>();
  333. if (temp.isPPtrCurve)
  334. {
  335. var curve = info.GetObjectCurveForBinding(temp);
  336. if (curve != null)
  337. {
  338. keys = curve.Select(x => (double)x.time);
  339. }
  340. }
  341. else
  342. {
  343. var curve = info.GetCurveForBinding(temp);
  344. if (curve != null)
  345. {
  346. keys = curve.keys.Select(x => (double)x.time);
  347. }
  348. }
  349. // Transform the times in to 'global' space using the clip
  350. if (clip != null)
  351. {
  352. foreach (var k in keys)
  353. {
  354. var time = clip.FromLocalTimeUnbound(k);
  355. const double eps = 1e-5;
  356. if (time >= clip.start - eps && time <= clip.end + eps)
  357. {
  358. keyTimes.Add(time);
  359. }
  360. }
  361. }
  362. // infinite clip mode, global == local space
  363. else
  364. {
  365. keyTimes.UnionWith(keys);
  366. }
  367. }
  368. }
  369. return keyTimes;
  370. }
  371. public static void NextKey(UnityEngine.Object target, IEnumerable<PropertyModification> modifications, WindowState state)
  372. {
  373. const double eps = 1e-5;
  374. var keyTimes = GetKeyTimes(target, modifications, state);
  375. if (keyTimes.Count == 0)
  376. return;
  377. var nextKeys = keyTimes.Where(x => x > state.editSequence.time + eps);
  378. if (nextKeys.Any())
  379. {
  380. state.editSequence.time = nextKeys.Min();
  381. }
  382. }
  383. public static void PrevKey(UnityEngine.Object target, IEnumerable<PropertyModification> modifications, WindowState state)
  384. {
  385. const double eps = 1e-5;
  386. var keyTimes = GetKeyTimes(target, modifications, state);
  387. if (keyTimes.Count == 0)
  388. return;
  389. var prevKeys = keyTimes.Where(x => x < state.editSequence.time - eps);
  390. if (prevKeys.Any())
  391. {
  392. state.editSequence.time = prevKeys.Max();
  393. }
  394. }
  395. public static void RemoveCurve(UnityEngine.Object target, IEnumerable<PropertyModification> modifications, WindowState state)
  396. {
  397. AnimationClip clip = null;
  398. double keyTime = 0;
  399. var inRange = false; // not used for curves
  400. if (!GetClipAndRelativeTime(target, state, out clip, out keyTime, out inRange))
  401. return;
  402. TimelineUndo.PushUndo(clip, "Remove Curve");
  403. foreach (var mod in modifications)
  404. {
  405. EditorCurveBinding temp;
  406. if (HasBinding(target, mod, clip, out temp))
  407. {
  408. if (temp.isPPtrCurve)
  409. AnimationUtility.SetObjectReferenceCurve(clip, temp, null);
  410. else
  411. AnimationUtility.SetEditorCurve(clip, temp, null);
  412. }
  413. }
  414. state.ResetPreviewMode();
  415. }
  416. public static IEnumerable<GameObject> GetRecordableGameObjects(WindowState state)
  417. {
  418. if (state == null || state.editSequence.asset == null || state.editSequence.director == null)
  419. yield break;
  420. var outputTracks = state.editSequence.asset.GetOutputTracks();
  421. foreach (var track in outputTracks)
  422. {
  423. if (track.GetType() != typeof(AnimationTrack))
  424. continue;
  425. if (!state.IsTrackRecordable(track) && !track.GetChildTracks().Any(state.IsTrackRecordable))
  426. continue;
  427. var obj = TimelineUtility.GetSceneGameObject(state.editSequence.director, track);
  428. if (obj != null)
  429. {
  430. yield return obj;
  431. }
  432. }
  433. }
  434. }
  435. }