No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TrackAsset.cs 48KB

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