TrackAsset.cs 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using UnityEngine.Animations;
  5. using UnityEngine.Playables;
  6. namespace UnityEngine.Timeline
  7. {
  8. /// <summary>
  9. /// A PlayableAsset representing a track inside a timeline.
  10. /// </summary>
  11. [Serializable]
  12. [IgnoreOnPlayableTrack]
  13. public abstract partial class TrackAsset : PlayableAsset, IPropertyPreview, ICurvesOwner
  14. {
  15. // Internal caches used to avoid memory allocation during graph construction
  16. private struct TransientBuildData
  17. {
  18. public List<TrackAsset> trackList;
  19. public List<TimelineClip> clipList;
  20. public List<IMarker> markerList;
  21. public static TransientBuildData Create()
  22. {
  23. return new TransientBuildData()
  24. {
  25. trackList = new List<TrackAsset>(20),
  26. clipList = new List<TimelineClip>(500),
  27. markerList = new List<IMarker>(100),
  28. };
  29. }
  30. public void Clear()
  31. {
  32. trackList.Clear();
  33. clipList.Clear();
  34. markerList.Clear();
  35. }
  36. }
  37. private static TransientBuildData s_BuildData = TransientBuildData.Create();
  38. internal const string kDefaultCurvesName = "Track Parameters";
  39. internal static event Action<TimelineClip, GameObject, Playable> OnClipPlayableCreate;
  40. internal static event Action<TrackAsset, GameObject, Playable> OnTrackAnimationPlayableCreate;
  41. [SerializeField, HideInInspector] bool m_Locked;
  42. [SerializeField, HideInInspector] bool m_Muted;
  43. [SerializeField, HideInInspector] string m_CustomPlayableFullTypename = string.Empty;
  44. [SerializeField, HideInInspector] AnimationClip m_Curves;
  45. [SerializeField, HideInInspector] PlayableAsset m_Parent;
  46. [SerializeField, HideInInspector] List<ScriptableObject> m_Children;
  47. [NonSerialized] int m_ItemsHash;
  48. [NonSerialized] TimelineClip[] m_ClipsCache;
  49. DiscreteTime m_Start;
  50. DiscreteTime m_End;
  51. bool m_CacheSorted;
  52. bool? m_SupportsNotifications;
  53. static TrackAsset[] s_EmptyCache = new TrackAsset[0];
  54. IEnumerable<TrackAsset> m_ChildTrackCache;
  55. static Dictionary<Type, TrackBindingTypeAttribute> s_TrackBindingTypeAttributeCache = new Dictionary<Type, TrackBindingTypeAttribute>();
  56. [SerializeField, HideInInspector] protected internal List<TimelineClip> m_Clips = new List<TimelineClip>();
  57. [SerializeField, HideInInspector] MarkerList m_Markers = new MarkerList(0);
  58. #if UNITY_EDITOR
  59. internal int DirtyIndex { get; private set; }
  60. internal void MarkDirty()
  61. {
  62. DirtyIndex++;
  63. foreach (var clip in GetClips())
  64. {
  65. if (clip != null)
  66. clip.MarkDirty();
  67. }
  68. }
  69. #endif
  70. /// <summary>
  71. /// The start time, in seconds, of this track
  72. /// </summary>
  73. public double start
  74. {
  75. get
  76. {
  77. UpdateDuration();
  78. return (double)m_Start;
  79. }
  80. }
  81. /// <summary>
  82. /// The end time, in seconds, of this track
  83. /// </summary>
  84. public double end
  85. {
  86. get
  87. {
  88. UpdateDuration();
  89. return (double)m_End;
  90. }
  91. }
  92. /// <summary>
  93. /// The length, in seconds, of this track
  94. /// </summary>
  95. public sealed override double duration
  96. {
  97. get
  98. {
  99. UpdateDuration();
  100. return (double)(m_End - m_Start);
  101. }
  102. }
  103. /// <summary>
  104. /// Whether the track is muted or not.
  105. /// </summary>
  106. /// <remarks>
  107. /// A muted track is excluded from the generated PlayableGraph
  108. /// </remarks>
  109. public bool muted
  110. {
  111. get { return m_Muted; }
  112. set { m_Muted = value; }
  113. }
  114. /// <summary>
  115. /// The muted state of a track.
  116. /// </summary>
  117. /// <remarks>
  118. /// A track is also muted when one of its parent tracks are muted.
  119. /// </remarks>
  120. public bool mutedInHierarchy
  121. {
  122. get
  123. {
  124. if (muted)
  125. return true;
  126. TrackAsset p = this;
  127. while (p.parent as TrackAsset != null)
  128. {
  129. p = (TrackAsset)p.parent;
  130. if (p as GroupTrack != null)
  131. return p.mutedInHierarchy;
  132. }
  133. return false;
  134. }
  135. }
  136. /// <summary>
  137. /// The TimelineAsset that this track belongs to.
  138. /// </summary>
  139. public TimelineAsset timelineAsset
  140. {
  141. get
  142. {
  143. var node = this;
  144. while (node != null)
  145. {
  146. if (node.parent == null)
  147. return null;
  148. var seq = node.parent as TimelineAsset;
  149. if (seq != null)
  150. return seq;
  151. node = node.parent as TrackAsset;
  152. }
  153. return null;
  154. }
  155. }
  156. /// <summary>
  157. /// The owner of this track.
  158. /// </summary>
  159. /// <remarks>
  160. /// If this track is a subtrack, the parent is a TrackAsset. Otherwise the parent is a TimelineAsset.
  161. /// </remarks>
  162. public PlayableAsset parent
  163. {
  164. get { return m_Parent; }
  165. internal set { m_Parent = value; }
  166. }
  167. /// <summary>
  168. /// A list of clips owned by this track
  169. /// </summary>
  170. /// <returns>Returns an enumerable list of clips owned by the track.</returns>
  171. public IEnumerable<TimelineClip> GetClips()
  172. {
  173. return clips;
  174. }
  175. internal TimelineClip[] clips
  176. {
  177. get
  178. {
  179. if (m_Clips == null)
  180. m_Clips = new List<TimelineClip>();
  181. if (m_ClipsCache == null)
  182. {
  183. m_CacheSorted = false;
  184. m_ClipsCache = m_Clips.ToArray();
  185. }
  186. return m_ClipsCache;
  187. }
  188. }
  189. /// <summary>
  190. /// Whether this track is considered empty.
  191. /// </summary>
  192. /// <remarks>
  193. /// A track is considered empty when it does not contain a TimelineClip, Marker, or Curve.
  194. /// </remarks>
  195. /// <remarks>
  196. /// Empty tracks are not included in the playable graph.
  197. /// </remarks>
  198. public virtual bool isEmpty
  199. {
  200. get { return !hasClips && !hasCurves && GetMarkerCount() == 0; }
  201. }
  202. /// <summary>
  203. /// Whether this track contains any TimelineClip.
  204. /// </summary>
  205. public bool hasClips
  206. {
  207. get { return m_Clips != null && m_Clips.Count != 0; }
  208. }
  209. /// <summary>
  210. /// Whether this track contains animated properties for the attached PlayableAsset.
  211. /// </summary>
  212. /// <remarks>
  213. /// This property is false if the curves property is null or if it contains no information.
  214. /// </remarks>
  215. public bool hasCurves
  216. {
  217. get { return m_Curves != null && !m_Curves.empty; }
  218. }
  219. /// <summary>
  220. /// Returns whether this track is a subtrack
  221. /// </summary>
  222. public bool isSubTrack
  223. {
  224. get
  225. {
  226. var owner = parent as TrackAsset;
  227. return owner != null && owner.GetType() == GetType();
  228. }
  229. }
  230. /// <summary>
  231. /// Returns a description of the PlayableOutputs that will be created by this track.
  232. /// </summary>
  233. public override IEnumerable<PlayableBinding> outputs
  234. {
  235. get
  236. {
  237. TrackBindingTypeAttribute attribute;
  238. if (!s_TrackBindingTypeAttributeCache.TryGetValue(GetType(), out attribute))
  239. {
  240. attribute = (TrackBindingTypeAttribute)Attribute.GetCustomAttribute(GetType(), typeof(TrackBindingTypeAttribute));
  241. s_TrackBindingTypeAttributeCache.Add(GetType(), attribute);
  242. }
  243. var trackBindingType = attribute != null ? attribute.type : null;
  244. yield return ScriptPlayableBinding.Create(name, this, trackBindingType);
  245. }
  246. }
  247. /// <summary>
  248. /// The list of subtracks or child tracks attached to this track.
  249. /// </summary>
  250. /// <returns>Returns an enumerable list of child tracks owned directly by this track.</returns>
  251. /// <remarks>
  252. /// In the case of GroupTracks, this returns all tracks contained in the group. This will return the all subtracks or override tracks, if supported by the track.
  253. /// </remarks>
  254. public IEnumerable<TrackAsset> GetChildTracks()
  255. {
  256. UpdateChildTrackCache();
  257. return m_ChildTrackCache;
  258. }
  259. internal string customPlayableTypename
  260. {
  261. get { return m_CustomPlayableFullTypename; }
  262. set { m_CustomPlayableFullTypename = value; }
  263. }
  264. /// <summary>
  265. /// An animation clip storing animated properties of the attached PlayableAsset
  266. /// </summary>
  267. public AnimationClip curves
  268. {
  269. get { return m_Curves; }
  270. internal set { m_Curves = value; }
  271. }
  272. string ICurvesOwner.defaultCurvesName
  273. {
  274. get { return kDefaultCurvesName; }
  275. }
  276. Object ICurvesOwner.asset
  277. {
  278. get { return this; }
  279. }
  280. Object ICurvesOwner.assetOwner
  281. {
  282. get { return timelineAsset; }
  283. }
  284. TrackAsset ICurvesOwner.targetTrack
  285. {
  286. get { return this; }
  287. }
  288. // for UI where we need to detect 'null' objects
  289. internal List<ScriptableObject> subTracksObjects
  290. {
  291. get { return m_Children; }
  292. }
  293. /// <summary>
  294. /// The local locked state of the track.
  295. /// </summary>
  296. /// <remarks>
  297. /// Note that locking a track only affects operations in the Timeline Editor. It does not prevent other API calls from changing a track or it's clips.
  298. ///
  299. /// This returns or sets the local locked state of the track. A track may still be locked for editing because one or more of it's parent tracks in the hierarchy is locked. Use lockedInHierarchy to test if a track is locked because of it's own locked state or because of a parent tracks locked state.
  300. /// </remarks>
  301. public bool locked
  302. {
  303. get { return m_Locked; }
  304. set { m_Locked = value; }
  305. }
  306. /// <summary>
  307. /// The locked state of a track. (RO)
  308. /// </summary>
  309. /// <remarks>
  310. /// Note that locking a track only affects operations in the Timeline Editor. It does not prevent other API calls from changing a track or it's clips.
  311. ///
  312. /// This indicates whether a track is locked in the Timeline Editor because either it's locked property is enabled or a parent track is locked.
  313. /// </remarks>
  314. public bool lockedInHierarchy
  315. {
  316. get
  317. {
  318. if (locked)
  319. return true;
  320. TrackAsset p = this;
  321. while (p.parent as TrackAsset != null)
  322. {
  323. p = (TrackAsset)p.parent;
  324. if (p as GroupTrack != null)
  325. return p.lockedInHierarchy;
  326. }
  327. return false;
  328. }
  329. }
  330. /// <summary>
  331. /// Indicates if a track accepts markers that implement <see cref="UnityEngine.Playables.INotification"/>.
  332. /// </summary>
  333. /// <remarks>
  334. /// Only tracks with a bound object of type <see cref="UnityEngine.GameObject"/> or <see cref="UnityEngine.Component"/> can accept notifications.
  335. /// </remarks>
  336. public bool supportsNotifications
  337. {
  338. get
  339. {
  340. if (!m_SupportsNotifications.HasValue)
  341. {
  342. m_SupportsNotifications = NotificationUtilities.TrackTypeSupportsNotifications(GetType());
  343. }
  344. return m_SupportsNotifications.Value;
  345. }
  346. }
  347. void __internalAwake() //do not use OnEnable, since users will want it to initialize their class
  348. {
  349. if (m_Clips == null)
  350. m_Clips = new List<TimelineClip>();
  351. m_ChildTrackCache = null;
  352. if (m_Children == null)
  353. m_Children = new List<ScriptableObject>();
  354. #if UNITY_EDITOR
  355. // validate the array. DON'T remove Unity null objects, just actual null objects
  356. for (int i = m_Children.Count - 1; i >= 0; i--)
  357. {
  358. object o = m_Children[i];
  359. if (o == null)
  360. {
  361. Debug.LogWarning("Empty child track found while loading timeline. It will be removed.");
  362. m_Children.RemoveAt(i);
  363. }
  364. }
  365. #endif
  366. }
  367. /// <summary>
  368. /// Creates an AnimationClip to store animated properties for the attached PlayableAsset.
  369. /// </summary>
  370. /// <remarks>
  371. /// If curves already exists for this track, this method produces no result regardless of
  372. /// the value specified for curvesClipName.
  373. /// </remarks>
  374. /// <remarks>
  375. /// When used from the editor, this method attempts to save the created curves clip to the TimelineAsset.
  376. /// The TimelineAsset must already exist in the AssetDatabase to save the curves clip. If the TimelineAsset
  377. /// does not exist, the curves clip is still created but it is not saved.
  378. /// </remarks>
  379. /// <param name="curvesClipName">
  380. /// The name of the AnimationClip to create.
  381. /// This method does not ensure unique names. If you want a unique clip name, you must provide one.
  382. /// See ObjectNames.GetUniqueName for information on a method that creates unique names.
  383. /// </param>
  384. public void CreateCurves(string curvesClipName)
  385. {
  386. if (m_Curves != null)
  387. return;
  388. m_Curves = TimelineCreateUtilities.CreateAnimationClipForTrack(string.IsNullOrEmpty(curvesClipName) ? kDefaultCurvesName : curvesClipName, this, true);
  389. }
  390. /// <summary>
  391. /// Creates a mixer used to blend playables generated by clips on the track.
  392. /// </summary>
  393. /// <param name="graph">The graph to inject playables into</param>
  394. /// <param name="go">The GameObject that requested the graph.</param>
  395. /// <param name="inputCount">The number of playables from clips that will be inputs to the returned mixer</param>
  396. /// <returns>A handle to the [[Playable]] representing the mixer.</returns>
  397. /// <remarks>
  398. /// Override this method to provide a custom playable for mixing clips on a graph.
  399. /// </remarks>
  400. public virtual Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
  401. {
  402. return Playable.Create(graph, inputCount);
  403. }
  404. /// <summary>
  405. /// Overrides PlayableAsset.CreatePlayable(). Not used in Timeline.
  406. /// </summary>
  407. public sealed override Playable CreatePlayable(PlayableGraph graph, GameObject go)
  408. {
  409. return Playable.Null;
  410. }
  411. /// <summary>
  412. /// Creates a TimelineClip on this track.
  413. /// </summary>
  414. /// <returns>Returns a new TimelineClip that is attached to the track.</returns>
  415. /// <remarks>
  416. /// The type of the playable asset attached to the clip is determined by TrackClip attributes that decorate the TrackAsset derived class
  417. /// </remarks>
  418. public TimelineClip CreateDefaultClip()
  419. {
  420. var trackClipTypeAttributes = GetType().GetCustomAttributes(typeof(TrackClipTypeAttribute), true);
  421. Type playableAssetType = null;
  422. foreach (var trackClipTypeAttribute in trackClipTypeAttributes)
  423. {
  424. var attribute = trackClipTypeAttribute as TrackClipTypeAttribute;
  425. if (attribute != null && typeof(IPlayableAsset).IsAssignableFrom(attribute.inspectedType) && typeof(ScriptableObject).IsAssignableFrom(attribute.inspectedType))
  426. {
  427. playableAssetType = attribute.inspectedType;
  428. break;
  429. }
  430. }
  431. if (playableAssetType == null)
  432. {
  433. Debug.LogWarning("Cannot create a default clip for type " + GetType());
  434. return null;
  435. }
  436. return CreateAndAddNewClipOfType(playableAssetType);
  437. }
  438. /// <summary>
  439. /// Creates a clip on the track with a playable asset attached, whose derived type is specified by T
  440. /// </summary>
  441. /// <typeparam name="T">A PlayableAsset derived type</typeparam>
  442. /// <returns>Returns a TimelineClip whose asset is of type T</returns>
  443. /// <remarks>
  444. /// Throws an InvalidOperationException if the specified type is not supported by the track.
  445. /// Supported types are determined by TrackClip attributes that decorate the TrackAsset derived class
  446. /// </remarks>
  447. public TimelineClip CreateClip<T>() where T : ScriptableObject, IPlayableAsset
  448. {
  449. return CreateClip(typeof(T));
  450. }
  451. /// <summary>
  452. /// Creates a marker of the requested type, at a specific time, and adds the marker to the current asset.
  453. /// </summary>
  454. /// <param name="type">The type of marker.</param>
  455. /// <param name="time">The time where the marker is created.</param>
  456. /// <returns>Returns the instance of the created marker.</returns>
  457. /// <remarks>
  458. /// All markers that implement IMarker and inherit from <see cref="UnityEngine.ScriptableObject"/> are supported.
  459. /// Markers that implement the INotification interface cannot be added to tracks that do not support notifications.
  460. /// CreateMarker will throw an <code>InvalidOperationException</code> with tracks that do not support notifications if <code>type</code> implements the INotification interface.
  461. /// </remarks>
  462. /// <seealso cref="UnityEngine.Timeline.Marker"/>
  463. /// <seealso cref="UnityEngine.Timeline.TrackAsset.supportsNotifications"/>
  464. public IMarker CreateMarker(Type type, double time)
  465. {
  466. return m_Markers.CreateMarker(type, time, this);
  467. }
  468. /// <summary>
  469. /// Creates a marker of the requested type, at a specific time, and adds the marker to the current asset.
  470. /// </summary>
  471. /// <param name="time">The time where the marker is created.</param>
  472. /// <returns>Returns the instance of the created marker.</returns>
  473. /// <remarks>
  474. /// All markers that implement IMarker and inherit from <see cref="UnityEngine.ScriptableObject"/> are supported.
  475. /// CreateMarker will throw an <code>InvalidOperationException</code> with tracks that do not support notifications if <code>T</code> implements the INotification interface.
  476. /// </remarks>
  477. /// <seealso cref="UnityEngine.Timeline.Marker"/>
  478. /// <seealso cref="UnityEngine.Timeline.TrackAsset.supportsNotifications"/>
  479. public T CreateMarker<T>(double time) where T : ScriptableObject, IMarker
  480. {
  481. return (T)CreateMarker(typeof(T), time);
  482. }
  483. /// <summary>
  484. /// Removes a marker from the current asset.
  485. /// </summary>
  486. /// <param name="marker">The marker instance to be removed.</param>
  487. /// <returns>Returns true if the marker instance was successfully removed. Returns false otherwise.</returns>
  488. public bool DeleteMarker(IMarker marker)
  489. {
  490. return m_Markers.Remove(marker);
  491. }
  492. /// <summary>
  493. /// Returns an enumerable list of markers on the current asset.
  494. /// </summary>
  495. /// <returns>The list of markers on the asset.
  496. /// </returns>
  497. public IEnumerable<IMarker> GetMarkers()
  498. {
  499. return m_Markers.GetMarkers();
  500. }
  501. /// <summary>
  502. /// Returns the number of markers on the current asset.
  503. /// </summary>
  504. /// <returns>The number of markers.</returns>
  505. public int GetMarkerCount()
  506. {
  507. return m_Markers.Count;
  508. }
  509. /// <summary>
  510. /// Returns the marker at a given position, on the current asset.
  511. /// </summary>
  512. /// <param name="idx">The index of the marker to be returned.</param>
  513. /// <returns>The marker.</returns>
  514. /// <remarks>The ordering of the markers is not guaranteed.
  515. /// </remarks>
  516. public IMarker GetMarker(int idx)
  517. {
  518. return m_Markers[idx];
  519. }
  520. internal TimelineClip CreateClip(System.Type requestedType)
  521. {
  522. if (ValidateClipType(requestedType))
  523. return CreateAndAddNewClipOfType(requestedType);
  524. throw new InvalidOperationException("Clips of type " + requestedType + " are not permitted on tracks of type " + GetType());
  525. }
  526. internal TimelineClip CreateAndAddNewClipOfType(Type requestedType)
  527. {
  528. var newClip = CreateClipOfType(requestedType);
  529. AddClip(newClip);
  530. return newClip;
  531. }
  532. internal TimelineClip CreateClipOfType(Type requestedType)
  533. {
  534. if (!ValidateClipType(requestedType))
  535. throw new System.InvalidOperationException("Clips of type " + requestedType + " are not permitted on tracks of type " + GetType());
  536. var playableAsset = CreateInstance(requestedType);
  537. if (playableAsset == null)
  538. {
  539. throw new System.InvalidOperationException("Could not create an instance of the ScriptableObject type " + requestedType.Name);
  540. }
  541. playableAsset.name = requestedType.Name;
  542. TimelineCreateUtilities.SaveAssetIntoObject(playableAsset, this);
  543. TimelineUndo.RegisterCreatedObjectUndo(playableAsset, "Create Clip");
  544. return CreateClipFromAsset(playableAsset);
  545. }
  546. /// <summary>
  547. /// Creates a timeline clip from an existing playable asset.
  548. /// </summary>
  549. /// <param name="asset"></param>
  550. /// <returns></returns>
  551. internal TimelineClip CreateClipFromPlayableAsset(IPlayableAsset asset)
  552. {
  553. if (asset == null)
  554. throw new ArgumentNullException("asset");
  555. if ((asset as ScriptableObject) == null)
  556. throw new System.ArgumentException("CreateClipFromPlayableAsset " + " only supports ScriptableObject-derived Types");
  557. if (!ValidateClipType(asset.GetType()))
  558. throw new System.InvalidOperationException("Clips of type " + asset.GetType() + " are not permitted on tracks of type " + GetType());
  559. return CreateClipFromAsset(asset as ScriptableObject);
  560. }
  561. private TimelineClip CreateClipFromAsset(ScriptableObject playableAsset)
  562. {
  563. TimelineUndo.PushUndo(this, "Create Clip");
  564. var newClip = CreateNewClipContainerInternal();
  565. newClip.displayName = playableAsset.name;
  566. newClip.asset = playableAsset;
  567. IPlayableAsset iPlayableAsset = playableAsset as IPlayableAsset;
  568. if (iPlayableAsset != null)
  569. {
  570. var candidateDuration = iPlayableAsset.duration;
  571. if (!double.IsInfinity(candidateDuration) && candidateDuration > 0)
  572. newClip.duration = Math.Min(Math.Max(candidateDuration, TimelineClip.kMinDuration), TimelineClip.kMaxTimeValue);
  573. }
  574. try
  575. {
  576. OnCreateClip(newClip);
  577. }
  578. catch (Exception e)
  579. {
  580. Debug.LogError(e.Message, playableAsset);
  581. return null;
  582. }
  583. return newClip;
  584. }
  585. internal IEnumerable<ScriptableObject> GetMarkersRaw()
  586. {
  587. return m_Markers.GetRawMarkerList();
  588. }
  589. internal void ClearMarkers()
  590. {
  591. m_Markers.Clear();
  592. }
  593. internal void AddMarker(ScriptableObject e)
  594. {
  595. m_Markers.Add(e);
  596. }
  597. internal bool DeleteMarkerRaw(ScriptableObject marker)
  598. {
  599. return m_Markers.Remove(marker, timelineAsset, this);
  600. }
  601. int GetTimeRangeHash()
  602. {
  603. double start = double.MaxValue, end = double.MinValue;
  604. foreach (var marker in GetMarkers())
  605. {
  606. if (!(marker is INotification))
  607. {
  608. continue;
  609. }
  610. if (marker.time < start)
  611. start = marker.time;
  612. if (marker.time > end)
  613. end = marker.time;
  614. }
  615. return start.GetHashCode().CombineHash(end.GetHashCode());
  616. }
  617. internal void AddClip(TimelineClip newClip)
  618. {
  619. if (!m_Clips.Contains(newClip))
  620. {
  621. m_Clips.Add(newClip);
  622. m_ClipsCache = null;
  623. }
  624. }
  625. Playable CreateNotificationsPlayable(PlayableGraph graph, Playable mixerPlayable, GameObject go, Playable timelinePlayable)
  626. {
  627. s_BuildData.markerList.Clear();
  628. GatherNotificiations(s_BuildData.markerList);
  629. var notificationPlayable = NotificationUtilities.CreateNotificationsPlayable(graph, s_BuildData.markerList, go);
  630. if (notificationPlayable.IsValid())
  631. {
  632. notificationPlayable.GetBehaviour().timeSource = timelinePlayable;
  633. if (mixerPlayable.IsValid())
  634. {
  635. notificationPlayable.SetInputCount(1);
  636. graph.Connect(mixerPlayable, 0, notificationPlayable, 0);
  637. notificationPlayable.SetInputWeight(mixerPlayable, 1);
  638. }
  639. }
  640. return notificationPlayable;
  641. }
  642. internal Playable CreatePlayableGraph(PlayableGraph graph, GameObject go, IntervalTree<RuntimeElement> tree, Playable timelinePlayable)
  643. {
  644. UpdateDuration();
  645. var mixerPlayable = Playable.Null;
  646. if (CanCompileClipsRecursive())
  647. mixerPlayable = OnCreateClipPlayableGraph(graph, go, tree);
  648. var notificationsPlayable = CreateNotificationsPlayable(graph, mixerPlayable, go, timelinePlayable);
  649. // clear the temporary build data to avoid holding references
  650. // case 1253974
  651. s_BuildData.Clear();
  652. if (!notificationsPlayable.IsValid() && !mixerPlayable.IsValid())
  653. {
  654. Debug.LogErrorFormat("Track {0} of type {1} has no notifications and returns an invalid mixer Playable", name,
  655. GetType().FullName);
  656. return Playable.Create(graph);
  657. }
  658. return notificationsPlayable.IsValid() ? notificationsPlayable : mixerPlayable;
  659. }
  660. internal virtual Playable CompileClips(PlayableGraph graph, GameObject go, IList<TimelineClip> timelineClips, IntervalTree<RuntimeElement> tree)
  661. {
  662. var blend = CreateTrackMixer(graph, go, timelineClips.Count);
  663. for (var c = 0; c < timelineClips.Count; c++)
  664. {
  665. var source = CreatePlayable(graph, go, timelineClips[c]);
  666. if (source.IsValid())
  667. {
  668. source.SetDuration(timelineClips[c].duration);
  669. var clip = new RuntimeClip(timelineClips[c], source, blend);
  670. tree.Add(clip);
  671. graph.Connect(source, 0, blend, c);
  672. blend.SetInputWeight(c, 0.0f);
  673. }
  674. }
  675. ConfigureTrackAnimation(tree, go, blend);
  676. return blend;
  677. }
  678. void GatherCompilableTracks(IList<TrackAsset> tracks)
  679. {
  680. if (!muted && CanCompileClips())
  681. tracks.Add(this);
  682. foreach (var c in GetChildTracks())
  683. {
  684. if (c != null)
  685. c.GatherCompilableTracks(tracks);
  686. }
  687. }
  688. void GatherNotificiations(List<IMarker> markers)
  689. {
  690. if (!muted && CanCompileNotifications())
  691. markers.AddRange(GetMarkers());
  692. foreach (var c in GetChildTracks())
  693. {
  694. if (c != null)
  695. c.GatherNotificiations(markers);
  696. }
  697. }
  698. internal virtual Playable OnCreateClipPlayableGraph(PlayableGraph graph, GameObject go, IntervalTree<RuntimeElement> tree)
  699. {
  700. if (tree == null)
  701. throw new ArgumentException("IntervalTree argument cannot be null", "tree");
  702. if (go == null)
  703. throw new ArgumentException("GameObject argument cannot be null", "go");
  704. s_BuildData.Clear();
  705. GatherCompilableTracks(s_BuildData.trackList);
  706. // nothing to compile
  707. if (s_BuildData.trackList.Count == 0)
  708. return Playable.Null;
  709. // check if layers are supported
  710. Playable layerMixer = Playable.Null;
  711. ILayerable layerable = this as ILayerable;
  712. if (layerable != null)
  713. layerMixer = layerable.CreateLayerMixer(graph, go, s_BuildData.trackList.Count);
  714. if (layerMixer.IsValid())
  715. {
  716. for (int i = 0; i < s_BuildData.trackList.Count; i++)
  717. {
  718. var mixer = s_BuildData.trackList[i].CompileClips(graph, go, s_BuildData.trackList[i].clips, tree);
  719. if (mixer.IsValid())
  720. {
  721. graph.Connect(mixer, 0, layerMixer, i);
  722. layerMixer.SetInputWeight(i, 1.0f);
  723. }
  724. }
  725. return layerMixer;
  726. }
  727. // one track compiles. Add track mixer and clips
  728. if (s_BuildData.trackList.Count == 1)
  729. return s_BuildData.trackList[0].CompileClips(graph, go, s_BuildData.trackList[0].clips, tree);
  730. // no layer mixer provided. merge down all clips.
  731. for (int i = 0; i < s_BuildData.trackList.Count; i++)
  732. s_BuildData.clipList.AddRange(s_BuildData.trackList[i].clips);
  733. #if UNITY_EDITOR
  734. bool applyWarning = false;
  735. for (int i = 0; i < s_BuildData.trackList.Count; i++)
  736. applyWarning |= i > 0 && s_BuildData.trackList[i].hasCurves;
  737. if (applyWarning)
  738. Debug.LogWarning("A layered track contains animated fields, but no layer mixer has been provided. Animated fields on layers will be ignored. Override CreateLayerMixer in " + s_BuildData.trackList[0].GetType().Name + " and return a valid playable to support animated fields on layered tracks.");
  739. #endif
  740. // compile all the clips into a single mixer
  741. return CompileClips(graph, go, s_BuildData.clipList, tree);
  742. }
  743. internal void ConfigureTrackAnimation(IntervalTree<RuntimeElement> tree, GameObject go, Playable blend)
  744. {
  745. if (!hasCurves)
  746. return;
  747. blend.SetAnimatedProperties(m_Curves);
  748. tree.Add(new InfiniteRuntimeClip(blend));
  749. if (OnTrackAnimationPlayableCreate != null)
  750. OnTrackAnimationPlayableCreate.Invoke(this, go, blend);
  751. }
  752. // sorts clips by start time
  753. internal void SortClips()
  754. {
  755. var clipsAsArray = clips; // will alloc
  756. if (!m_CacheSorted)
  757. {
  758. Array.Sort(clips, (clip1, clip2) => clip1.start.CompareTo(clip2.start));
  759. m_CacheSorted = true;
  760. }
  761. }
  762. // clears the clips after a clone
  763. internal void ClearClipsInternal()
  764. {
  765. m_Clips = new List<TimelineClip>();
  766. m_ClipsCache = null;
  767. }
  768. internal void ClearSubTracksInternal()
  769. {
  770. m_Children = new List<ScriptableObject>();
  771. Invalidate();
  772. }
  773. // called by an owned clip when it moves
  774. internal void OnClipMove()
  775. {
  776. m_CacheSorted = false;
  777. }
  778. internal TimelineClip CreateNewClipContainerInternal()
  779. {
  780. var clipContainer = new TimelineClip(this);
  781. clipContainer.asset = null;
  782. // position clip at end of sequence
  783. var newClipStart = 0.0;
  784. for (var a = 0; a < m_Clips.Count - 1; a++)
  785. {
  786. var clipDuration = m_Clips[a].duration;
  787. if (double.IsInfinity(clipDuration))
  788. clipDuration = TimelineClip.kDefaultClipDurationInSeconds;
  789. newClipStart = Math.Max(newClipStart, m_Clips[a].start + clipDuration);
  790. }
  791. clipContainer.mixInCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
  792. clipContainer.mixOutCurve = AnimationCurve.EaseInOut(0, 1, 1, 0);
  793. clipContainer.start = newClipStart;
  794. clipContainer.duration = TimelineClip.kDefaultClipDurationInSeconds;
  795. clipContainer.displayName = "untitled";
  796. return clipContainer;
  797. }
  798. internal void AddChild(TrackAsset child)
  799. {
  800. if (child == null)
  801. return;
  802. m_Children.Add(child);
  803. child.parent = this;
  804. Invalidate();
  805. }
  806. internal void MoveLastTrackBefore(TrackAsset asset)
  807. {
  808. if (m_Children == null || m_Children.Count < 2 || asset == null)
  809. return;
  810. var lastTrack = m_Children[m_Children.Count - 1];
  811. if (lastTrack == asset)
  812. return;
  813. for (int i = 0; i < m_Children.Count - 1; i++)
  814. {
  815. if (m_Children[i] == asset)
  816. {
  817. for (int j = m_Children.Count - 1; j > i; j--)
  818. m_Children[j] = m_Children[j - 1];
  819. m_Children[i] = lastTrack;
  820. Invalidate();
  821. break;
  822. }
  823. }
  824. }
  825. internal bool RemoveSubTrack(TrackAsset child)
  826. {
  827. if (m_Children.Remove(child))
  828. {
  829. Invalidate();
  830. child.parent = null;
  831. return true;
  832. }
  833. return false;
  834. }
  835. internal void RemoveClip(TimelineClip clip)
  836. {
  837. m_Clips.Remove(clip);
  838. m_ClipsCache = null;
  839. }
  840. // Is this track compilable for the sequence
  841. // calculate the time interval that this track will be evaluated in.
  842. internal virtual void GetEvaluationTime(out double outStart, out double outDuration)
  843. {
  844. outStart = double.PositiveInfinity;
  845. var outEnd = double.NegativeInfinity;
  846. if (hasCurves)
  847. {
  848. outStart = 0.0;
  849. outEnd = TimeUtility.GetAnimationClipLength(curves);
  850. }
  851. foreach (var clip in clips)
  852. {
  853. outStart = Math.Min(clip.start, outStart);
  854. outEnd = Math.Max(clip.end, outEnd);
  855. }
  856. if (HasNotifications())
  857. {
  858. var notificationDuration = GetNotificationDuration();
  859. outStart = Math.Min(notificationDuration, outStart);
  860. outEnd = Math.Max(notificationDuration, outEnd);
  861. }
  862. if (double.IsInfinity(outStart) || double.IsInfinity(outEnd))
  863. outStart = outDuration = 0.0;
  864. else
  865. outDuration = outEnd - outStart;
  866. }
  867. // calculate the time interval that the sequence will use to determine length.
  868. // by default this is the same as the evaluation, but subclasses can have different
  869. // behaviour
  870. internal virtual void GetSequenceTime(out double outStart, out double outDuration)
  871. {
  872. GetEvaluationTime(out outStart, out outDuration);
  873. }
  874. /// <summary>
  875. /// Called by the Timeline Editor to gather properties requiring preview.
  876. /// </summary>
  877. /// <param name="director">The PlayableDirector invoking the preview</param>
  878. /// <param name="driver">PropertyCollector used to gather previewable properties</param>
  879. public virtual void GatherProperties(PlayableDirector director, IPropertyCollector driver)
  880. {
  881. // only push on game objects if there is a binding. Subtracks
  882. // will use objects on the stack
  883. var gameObject = GetGameObjectBinding(director);
  884. if (gameObject != null)
  885. driver.PushActiveGameObject(gameObject);
  886. if (hasCurves)
  887. driver.AddObjectProperties(this, m_Curves);
  888. foreach (var clip in clips)
  889. {
  890. if (clip.curves != null && clip.asset != null)
  891. driver.AddObjectProperties(clip.asset, clip.curves);
  892. IPropertyPreview modifier = clip.asset as IPropertyPreview;
  893. if (modifier != null)
  894. modifier.GatherProperties(director, driver);
  895. }
  896. foreach (var subtrack in GetChildTracks())
  897. {
  898. if (subtrack != null)
  899. subtrack.GatherProperties(director, driver);
  900. }
  901. if (gameObject != null)
  902. driver.PopActiveGameObject();
  903. }
  904. internal GameObject GetGameObjectBinding(PlayableDirector director)
  905. {
  906. if (director == null)
  907. return null;
  908. var binding = director.GetGenericBinding(this);
  909. var gameObject = binding as GameObject;
  910. if (gameObject != null)
  911. return gameObject;
  912. var comp = binding as Component;
  913. if (comp != null)
  914. return comp.gameObject;
  915. return null;
  916. }
  917. internal bool ValidateClipType(Type clipType)
  918. {
  919. var attrs = GetType().GetCustomAttributes(typeof(TrackClipTypeAttribute), true);
  920. for (var c = 0; c < attrs.Length; ++c)
  921. {
  922. var attr = (TrackClipTypeAttribute)attrs[c];
  923. if (attr.inspectedType.IsAssignableFrom(clipType))
  924. return true;
  925. }
  926. // special case for playable tracks, they accept all clips (in the runtime)
  927. return typeof(PlayableTrack).IsAssignableFrom(GetType()) &&
  928. typeof(IPlayableAsset).IsAssignableFrom(clipType) &&
  929. typeof(ScriptableObject).IsAssignableFrom(clipType);
  930. }
  931. /// <summary>
  932. /// Called when a clip is created on a track.
  933. /// </summary>
  934. /// <param name="clip">The timeline clip added to this track</param>
  935. /// <remarks>Use this method to set default values on a timeline clip, or it's PlayableAsset.</remarks>
  936. protected virtual void OnCreateClip(TimelineClip clip) {}
  937. void UpdateDuration()
  938. {
  939. // check if something changed in the clips that require a re-calculation of the evaluation times.
  940. var itemsHash = CalculateItemsHash();
  941. if (itemsHash == m_ItemsHash)
  942. return;
  943. m_ItemsHash = itemsHash;
  944. double trackStart, trackDuration;
  945. GetSequenceTime(out trackStart, out trackDuration);
  946. m_Start = (DiscreteTime)trackStart;
  947. m_End = (DiscreteTime)(trackStart + trackDuration);
  948. // calculate the extrapolations time.
  949. // TODO Extrapolation time should probably be extracted from the SequenceClip so only a track is aware of it.
  950. this.CalculateExtrapolationTimes();
  951. }
  952. protected internal virtual int CalculateItemsHash()
  953. {
  954. return HashUtility.CombineHash(GetClipsHash(), GetAnimationClipHash(m_Curves), GetTimeRangeHash());
  955. }
  956. /// <summary>
  957. /// Constructs a Playable from a TimelineClip.
  958. /// </summary>
  959. /// <param name="graph">PlayableGraph that will own the playable.</param>
  960. /// <param name="gameObject">The GameObject that builds the PlayableGraph.</param>
  961. /// <param name="clip">The TimelineClip to construct a playable for.</param>
  962. /// <returns>A playable that will be set as an input to the Track Mixer playable, or Playable.Null if the clip does not have a valid PlayableAsset</returns>
  963. /// <exception cref="ArgumentException">Thrown if the specified PlayableGraph is not valid.</exception>
  964. /// <exception cref="ArgumentNullException">Thrown if the specified TimelineClip is not valid.</exception>
  965. /// <remarks>
  966. /// By default, this method invokes Playable.CreatePlayable, sets animated properties, and sets the speed of the created playable. Override this method to change this default implementation.
  967. /// </remarks>
  968. protected virtual Playable CreatePlayable(PlayableGraph graph, GameObject gameObject, TimelineClip clip)
  969. {
  970. if (!graph.IsValid())
  971. throw new ArgumentException("graph must be a valid PlayableGraph");
  972. if (clip == null)
  973. throw new ArgumentNullException("clip");
  974. var asset = clip.asset as IPlayableAsset;
  975. if (asset != null)
  976. {
  977. var handle = asset.CreatePlayable(graph, gameObject);
  978. if (handle.IsValid())
  979. {
  980. handle.SetAnimatedProperties(clip.curves);
  981. handle.SetSpeed(clip.timeScale);
  982. if (OnClipPlayableCreate != null)
  983. OnClipPlayableCreate(clip, gameObject, handle);
  984. }
  985. return handle;
  986. }
  987. return Playable.Null;
  988. }
  989. internal void Invalidate()
  990. {
  991. m_ChildTrackCache = null;
  992. var timeline = timelineAsset;
  993. if (timeline != null)
  994. {
  995. timeline.Invalidate();
  996. }
  997. }
  998. internal double GetNotificationDuration()
  999. {
  1000. if (!supportsNotifications)
  1001. {
  1002. return 0;
  1003. }
  1004. var maxTime = 0.0;
  1005. foreach (var marker in GetMarkers())
  1006. {
  1007. if (!(marker is INotification))
  1008. {
  1009. continue;
  1010. }
  1011. maxTime = Math.Max(maxTime, marker.time);
  1012. }
  1013. return maxTime;
  1014. }
  1015. internal virtual bool CanCompileClips()
  1016. {
  1017. return hasClips || hasCurves;
  1018. }
  1019. internal bool IsCompilable()
  1020. {
  1021. var isContainer = typeof(GroupTrack).IsAssignableFrom(GetType());
  1022. if (isContainer)
  1023. return false;
  1024. var ret = !mutedInHierarchy && (CanCompileClips() || CanCompileNotifications());
  1025. if (!ret)
  1026. {
  1027. foreach (var t in GetChildTracks())
  1028. {
  1029. if (t.IsCompilable())
  1030. return true;
  1031. }
  1032. }
  1033. return ret;
  1034. }
  1035. private void UpdateChildTrackCache()
  1036. {
  1037. if (m_ChildTrackCache == null)
  1038. {
  1039. if (m_Children == null || m_Children.Count == 0)
  1040. m_ChildTrackCache = s_EmptyCache;
  1041. else
  1042. {
  1043. var childTracks = new List<TrackAsset>(m_Children.Count);
  1044. for (int i = 0; i < m_Children.Count; i++)
  1045. {
  1046. var subTrack = m_Children[i] as TrackAsset;
  1047. if (subTrack != null)
  1048. childTracks.Add(subTrack);
  1049. }
  1050. m_ChildTrackCache = childTracks;
  1051. }
  1052. }
  1053. }
  1054. internal virtual int Hash()
  1055. {
  1056. return clips.Length + (m_Markers.Count << 16);
  1057. }
  1058. int GetClipsHash()
  1059. {
  1060. var hash = 0;
  1061. foreach (var clip in m_Clips)
  1062. {
  1063. hash = hash.CombineHash(clip.Hash());
  1064. }
  1065. return hash;
  1066. }
  1067. protected static int GetAnimationClipHash(AnimationClip clip)
  1068. {
  1069. var hash = 0;
  1070. if (clip != null && !clip.empty)
  1071. hash = hash.CombineHash(clip.frameRate.GetHashCode())
  1072. .CombineHash(clip.length.GetHashCode());
  1073. return hash;
  1074. }
  1075. bool HasNotifications()
  1076. {
  1077. return m_Markers.HasNotifications();
  1078. }
  1079. bool CanCompileNotifications()
  1080. {
  1081. return supportsNotifications && m_Markers.HasNotifications();
  1082. }
  1083. bool CanCompileClipsRecursive()
  1084. {
  1085. if (CanCompileClips())
  1086. return true;
  1087. foreach (var track in GetChildTracks())
  1088. {
  1089. if (track.CanCompileClipsRecursive())
  1090. return true;
  1091. }
  1092. return false;
  1093. }
  1094. }
  1095. }