설명 없음
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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329
  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 MarkDirtyTrackAndClips()
  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. ScriptPlayable<TimeNotificationBehaviour> notificationPlayable;
  662. if (go.TryGetComponent(out PlayableDirector director))
  663. notificationPlayable = NotificationUtilities.CreateNotificationsPlayable(graph, s_BuildData.markerList, director);
  664. else
  665. notificationPlayable = NotificationUtilities.CreateNotificationsPlayable(graph, s_BuildData.markerList, timelineAsset);
  666. if (notificationPlayable.IsValid())
  667. {
  668. notificationPlayable.GetBehaviour().timeSource = timelinePlayable;
  669. if (mixerPlayable.IsValid())
  670. {
  671. notificationPlayable.SetInputCount(1);
  672. graph.Connect(mixerPlayable, 0, notificationPlayable, 0);
  673. notificationPlayable.SetInputWeight(mixerPlayable, 1);
  674. }
  675. }
  676. return notificationPlayable;
  677. }
  678. internal Playable CreatePlayableGraph(PlayableGraph graph, GameObject go, IntervalTree<RuntimeElement> tree, Playable timelinePlayable)
  679. {
  680. UpdateDuration();
  681. var mixerPlayable = Playable.Null;
  682. if (CanCreateMixerRecursive())
  683. mixerPlayable = CreateMixerPlayableGraph(graph, go, tree);
  684. Playable notificationsPlayable = CreateNotificationsPlayable(graph, mixerPlayable, go, timelinePlayable);
  685. // clear the temporary build data to avoid holding references
  686. // case 1253974
  687. s_BuildData.Clear();
  688. if (!notificationsPlayable.IsValid() && !mixerPlayable.IsValid())
  689. {
  690. Debug.LogErrorFormat("Track {0} of type {1} has no notifications and returns an invalid mixer Playable", name,
  691. GetType().FullName);
  692. return Playable.Create(graph);
  693. }
  694. return notificationsPlayable.IsValid() ? notificationsPlayable : mixerPlayable;
  695. }
  696. internal virtual Playable CompileClips(PlayableGraph graph, GameObject go, IList<TimelineClip> timelineClips, IntervalTree<RuntimeElement> tree)
  697. {
  698. var blend = CreateTrackMixer(graph, go, timelineClips.Count);
  699. for (var c = 0; c < timelineClips.Count; c++)
  700. {
  701. var source = CreatePlayable(graph, go, timelineClips[c]);
  702. if (source.IsValid())
  703. {
  704. source.SetDuration(timelineClips[c].duration);
  705. var clip = new RuntimeClip(timelineClips[c], source, blend);
  706. tree.Add(clip);
  707. graph.Connect(source, 0, blend, c);
  708. blend.SetInputWeight(c, 0.0f);
  709. }
  710. }
  711. ConfigureTrackAnimation(tree, go, blend);
  712. return blend;
  713. }
  714. void GatherCompilableTracks(IList<TrackAsset> tracks)
  715. {
  716. if (!muted && CanCreateTrackMixer())
  717. tracks.Add(this);
  718. foreach (var c in GetChildTracks())
  719. {
  720. if (c != null)
  721. c.GatherCompilableTracks(tracks);
  722. }
  723. }
  724. void GatherNotifications(List<IMarker> markers)
  725. {
  726. if (!muted && CanCompileNotifications())
  727. markers.AddRange(GetMarkers());
  728. foreach (var c in GetChildTracks())
  729. {
  730. if (c != null)
  731. c.GatherNotifications(markers);
  732. }
  733. }
  734. internal virtual Playable CreateMixerPlayableGraph(PlayableGraph graph, GameObject go, IntervalTree<RuntimeElement> tree)
  735. {
  736. if (tree == null)
  737. throw new ArgumentException("IntervalTree argument cannot be null", "tree");
  738. if (go == null)
  739. throw new ArgumentException("GameObject argument cannot be null", "go");
  740. s_BuildData.Clear();
  741. GatherCompilableTracks(s_BuildData.trackList);
  742. // nothing to compile
  743. if (s_BuildData.trackList.Count == 0)
  744. return Playable.Null;
  745. // check if layers are supported
  746. Playable layerMixer = Playable.Null;
  747. ILayerable layerable = this as ILayerable;
  748. if (layerable != null)
  749. layerMixer = layerable.CreateLayerMixer(graph, go, s_BuildData.trackList.Count);
  750. if (layerMixer.IsValid())
  751. {
  752. for (int i = 0; i < s_BuildData.trackList.Count; i++)
  753. {
  754. var mixer = s_BuildData.trackList[i].CompileClips(graph, go, s_BuildData.trackList[i].clips, tree);
  755. if (mixer.IsValid())
  756. {
  757. graph.Connect(mixer, 0, layerMixer, i);
  758. layerMixer.SetInputWeight(i, 1.0f);
  759. }
  760. }
  761. return layerMixer;
  762. }
  763. // one track compiles. Add track mixer and clips
  764. if (s_BuildData.trackList.Count == 1)
  765. return s_BuildData.trackList[0].CompileClips(graph, go, s_BuildData.trackList[0].clips, tree);
  766. // no layer mixer provided. merge down all clips.
  767. for (int i = 0; i < s_BuildData.trackList.Count; i++)
  768. s_BuildData.clipList.AddRange(s_BuildData.trackList[i].clips);
  769. #if UNITY_EDITOR
  770. bool applyWarning = false;
  771. for (int i = 0; i < s_BuildData.trackList.Count; i++)
  772. applyWarning |= i > 0 && s_BuildData.trackList[i].hasCurves;
  773. if (applyWarning)
  774. 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.");
  775. #endif
  776. // compile all the clips into a single mixer
  777. return CompileClips(graph, go, s_BuildData.clipList, tree);
  778. }
  779. internal void ConfigureTrackAnimation(IntervalTree<RuntimeElement> tree, GameObject go, Playable blend)
  780. {
  781. if (!hasCurves)
  782. return;
  783. blend.SetAnimatedProperties(m_Curves);
  784. tree.Add(new InfiniteRuntimeClip(blend));
  785. if (OnTrackAnimationPlayableCreate != null)
  786. OnTrackAnimationPlayableCreate.Invoke(this, go, blend);
  787. }
  788. // sorts clips by start time
  789. internal void SortClips()
  790. {
  791. var clipsAsArray = clips; // will alloc
  792. if (!m_CacheSorted)
  793. {
  794. Array.Sort(clips, (clip1, clip2) => clip1.start.CompareTo(clip2.start));
  795. m_CacheSorted = true;
  796. }
  797. }
  798. // clears the clips after a clone
  799. internal void ClearClipsInternal()
  800. {
  801. m_Clips = new List<TimelineClip>();
  802. m_ClipsCache = null;
  803. }
  804. internal void ClearSubTracksInternal()
  805. {
  806. m_Children = new List<ScriptableObject>();
  807. Invalidate();
  808. }
  809. // called by an owned clip when it moves
  810. internal void OnClipMove()
  811. {
  812. m_CacheSorted = false;
  813. }
  814. internal TimelineClip CreateNewClipContainerInternal()
  815. {
  816. var clipContainer = new TimelineClip(this);
  817. clipContainer.asset = null;
  818. // position clip at end of sequence
  819. var newClipStart = 0.0;
  820. for (var a = 0; a < m_Clips.Count - 1; a++)
  821. {
  822. var clipDuration = m_Clips[a].duration;
  823. if (double.IsInfinity(clipDuration))
  824. clipDuration = TimelineClip.kDefaultClipDurationInSeconds;
  825. newClipStart = Math.Max(newClipStart, m_Clips[a].start + clipDuration);
  826. }
  827. clipContainer.mixInCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
  828. clipContainer.mixOutCurve = AnimationCurve.EaseInOut(0, 1, 1, 0);
  829. clipContainer.start = newClipStart;
  830. clipContainer.duration = TimelineClip.kDefaultClipDurationInSeconds;
  831. clipContainer.displayName = "untitled";
  832. return clipContainer;
  833. }
  834. internal void AddChild(TrackAsset child)
  835. {
  836. if (child == null)
  837. return;
  838. m_Children.Add(child);
  839. child.parent = this;
  840. Invalidate();
  841. }
  842. internal void MoveLastTrackBefore(TrackAsset asset)
  843. {
  844. if (m_Children == null || m_Children.Count < 2 || asset == null)
  845. return;
  846. var lastTrack = m_Children[m_Children.Count - 1];
  847. if (lastTrack == asset)
  848. return;
  849. for (int i = 0; i < m_Children.Count - 1; i++)
  850. {
  851. if (m_Children[i] == asset)
  852. {
  853. for (int j = m_Children.Count - 1; j > i; j--)
  854. m_Children[j] = m_Children[j - 1];
  855. m_Children[i] = lastTrack;
  856. Invalidate();
  857. break;
  858. }
  859. }
  860. }
  861. internal bool RemoveSubTrack(TrackAsset child)
  862. {
  863. if (m_Children.Remove(child))
  864. {
  865. Invalidate();
  866. child.parent = null;
  867. return true;
  868. }
  869. return false;
  870. }
  871. internal void RemoveClip(TimelineClip clip)
  872. {
  873. m_Clips.Remove(clip);
  874. m_ClipsCache = null;
  875. }
  876. // Is this track compilable for the sequence
  877. // calculate the time interval that this track will be evaluated in.
  878. internal virtual void GetEvaluationTime(out double outStart, out double outDuration)
  879. {
  880. outStart = 0;
  881. outDuration = 1;
  882. outStart = double.PositiveInfinity;
  883. var outEnd = double.NegativeInfinity;
  884. if (hasCurves)
  885. {
  886. outStart = 0.0;
  887. outEnd = TimeUtility.GetAnimationClipLength(curves);
  888. }
  889. foreach (var clip in clips)
  890. {
  891. outStart = Math.Min(clip.start, outStart);
  892. outEnd = Math.Max(clip.end, outEnd);
  893. }
  894. if (HasNotifications())
  895. {
  896. var notificationDuration = GetNotificationDuration();
  897. outStart = Math.Min(notificationDuration, outStart);
  898. outEnd = Math.Max(notificationDuration, outEnd);
  899. }
  900. if (double.IsInfinity(outStart) || double.IsInfinity(outEnd))
  901. outStart = outDuration = 0.0;
  902. else
  903. outDuration = outEnd - outStart;
  904. }
  905. // calculate the time interval that the sequence will use to determine length.
  906. // by default this is the same as the evaluation, but subclasses can have different
  907. // behaviour
  908. internal virtual void GetSequenceTime(out double outStart, out double outDuration)
  909. {
  910. GetEvaluationTime(out outStart, out outDuration);
  911. }
  912. /// <summary>
  913. /// Called by the Timeline Editor to gather properties requiring preview.
  914. /// </summary>
  915. /// <param name="director">The PlayableDirector invoking the preview</param>
  916. /// <param name="driver">PropertyCollector used to gather previewable properties</param>
  917. public virtual void GatherProperties(PlayableDirector director, IPropertyCollector driver)
  918. {
  919. // only push on game objects if there is a binding. Subtracks
  920. // will use objects on the stack
  921. var gameObject = GetGameObjectBinding(director);
  922. if (gameObject != null)
  923. driver.PushActiveGameObject(gameObject);
  924. if (hasCurves)
  925. driver.AddObjectProperties(this, m_Curves);
  926. foreach (var clip in clips)
  927. {
  928. if (clip.curves != null && clip.asset != null)
  929. driver.AddObjectProperties(clip.asset, clip.curves);
  930. IPropertyPreview modifier = clip.asset as IPropertyPreview;
  931. if (modifier != null)
  932. modifier.GatherProperties(director, driver);
  933. }
  934. foreach (var subtrack in GetChildTracks())
  935. {
  936. if (subtrack != null)
  937. subtrack.GatherProperties(director, driver);
  938. }
  939. if (gameObject != null)
  940. driver.PopActiveGameObject();
  941. }
  942. internal GameObject GetGameObjectBinding(PlayableDirector director)
  943. {
  944. if (director == null)
  945. return null;
  946. var binding = director.GetGenericBinding(this);
  947. var gameObject = binding as GameObject;
  948. if (gameObject != null)
  949. return gameObject;
  950. var comp = binding as Component;
  951. if (comp != null)
  952. return comp.gameObject;
  953. return null;
  954. }
  955. internal bool ValidateClipType(Type clipType)
  956. {
  957. var attrs = GetType().GetCustomAttributes(typeof(TrackClipTypeAttribute), true);
  958. for (var c = 0; c < attrs.Length; ++c)
  959. {
  960. var attr = (TrackClipTypeAttribute)attrs[c];
  961. if (attr.inspectedType.IsAssignableFrom(clipType))
  962. return true;
  963. }
  964. // special case for playable tracks, they accept all clips (in the runtime)
  965. return typeof(PlayableTrack).IsAssignableFrom(GetType()) &&
  966. typeof(IPlayableAsset).IsAssignableFrom(clipType) &&
  967. typeof(ScriptableObject).IsAssignableFrom(clipType);
  968. }
  969. /// <summary>
  970. /// Called when a clip is created on a track.
  971. /// </summary>
  972. /// <param name="clip">The timeline clip added to this track</param>
  973. /// <remarks>Use this method to set default values on a timeline clip, or it's PlayableAsset.</remarks>
  974. protected virtual void OnCreateClip(TimelineClip clip) { }
  975. void UpdateDuration()
  976. {
  977. // check if something changed in the clips that require a re-calculation of the evaluation times.
  978. var itemsHash = CalculateItemsHash();
  979. if (itemsHash == m_ItemsHash)
  980. return;
  981. m_ItemsHash = itemsHash;
  982. double trackStart, trackDuration;
  983. GetSequenceTime(out trackStart, out trackDuration);
  984. m_Start = (DiscreteTime)trackStart;
  985. m_End = (DiscreteTime)(trackStart + trackDuration);
  986. // calculate the extrapolations time.
  987. // TODO Extrapolation time should probably be extracted from the SequenceClip so only a track is aware of it.
  988. this.CalculateExtrapolationTimes();
  989. }
  990. protected internal virtual int CalculateItemsHash()
  991. {
  992. return HashUtility.CombineHash(GetClipsHash(), GetAnimationClipHash(m_Curves), GetTimeRangeHash());
  993. }
  994. /// <summary>
  995. /// Constructs a Playable from a TimelineClip.
  996. /// </summary>
  997. /// <param name="graph">PlayableGraph that will own the playable.</param>
  998. /// <param name="gameObject">The GameObject that builds the PlayableGraph.</param>
  999. /// <param name="clip">The TimelineClip to construct a playable for.</param>
  1000. /// <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>
  1001. /// <exception cref="ArgumentException">Thrown if the specified PlayableGraph is not valid.</exception>
  1002. /// <exception cref="ArgumentNullException">Thrown if the specified TimelineClip is not valid.</exception>
  1003. /// <remarks>
  1004. /// 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.
  1005. /// </remarks>
  1006. protected virtual Playable CreatePlayable(PlayableGraph graph, GameObject gameObject, TimelineClip clip)
  1007. {
  1008. if (!graph.IsValid())
  1009. throw new ArgumentException("graph must be a valid PlayableGraph");
  1010. if (clip == null)
  1011. throw new ArgumentNullException("clip");
  1012. var asset = clip.asset as IPlayableAsset;
  1013. if (asset != null)
  1014. {
  1015. var handle = asset.CreatePlayable(graph, gameObject);
  1016. if (handle.IsValid())
  1017. {
  1018. handle.SetAnimatedProperties(clip.curves);
  1019. handle.SetSpeed(clip.timeScale);
  1020. if (OnClipPlayableCreate != null)
  1021. OnClipPlayableCreate(clip, gameObject, handle);
  1022. }
  1023. return handle;
  1024. }
  1025. return Playable.Null;
  1026. }
  1027. internal void Invalidate()
  1028. {
  1029. m_ChildTrackCache = null;
  1030. var timeline = timelineAsset;
  1031. if (timeline != null)
  1032. {
  1033. timeline.Invalidate();
  1034. }
  1035. }
  1036. internal double GetNotificationDuration()
  1037. {
  1038. if (!supportsNotifications)
  1039. {
  1040. return 0;
  1041. }
  1042. var maxTime = 0.0;
  1043. int count = m_Markers.Count;
  1044. for (int i = 0; i < count; i++)
  1045. {
  1046. var marker = m_Markers[i];
  1047. if (!(marker is INotification))
  1048. {
  1049. continue;
  1050. }
  1051. maxTime = Math.Max(maxTime, marker.time);
  1052. }
  1053. return maxTime;
  1054. }
  1055. internal virtual bool CanCompileClips()
  1056. {
  1057. return hasClips || hasCurves;
  1058. }
  1059. /// <summary>
  1060. /// Whether the track can create a mixer for its own contents.
  1061. /// </summary>
  1062. /// <returns>Returns true if the track's mixer should be included in the playable graph.</returns>
  1063. /// <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>
  1064. /// <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>
  1065. /// <remarks>Override this method to change the conditions for a track to be included in the playable graph.</remarks>
  1066. public virtual bool CanCreateTrackMixer()
  1067. {
  1068. return CanCompileClips();
  1069. }
  1070. internal bool IsCompilable()
  1071. {
  1072. bool isContainer = typeof(GroupTrack).IsAssignableFrom(GetType());
  1073. if (isContainer)
  1074. return false;
  1075. var ret = !mutedInHierarchy && (CanCreateTrackMixer() || CanCompileNotifications());
  1076. if (!ret)
  1077. {
  1078. foreach (var t in GetChildTracks())
  1079. {
  1080. if (t.IsCompilable())
  1081. return true;
  1082. }
  1083. }
  1084. return ret;
  1085. }
  1086. private void UpdateChildTrackCache()
  1087. {
  1088. if (m_ChildTrackCache == null)
  1089. {
  1090. if (m_Children == null || m_Children.Count == 0)
  1091. m_ChildTrackCache = s_EmptyCache;
  1092. else
  1093. {
  1094. var childTracks = new List<TrackAsset>(m_Children.Count);
  1095. for (int i = 0; i < m_Children.Count; i++)
  1096. {
  1097. var subTrack = m_Children[i] as TrackAsset;
  1098. if (subTrack != null)
  1099. childTracks.Add(subTrack);
  1100. }
  1101. m_ChildTrackCache = childTracks;
  1102. }
  1103. }
  1104. }
  1105. internal virtual int Hash()
  1106. {
  1107. return clips.Length + (m_Markers.Count << 16);
  1108. }
  1109. int GetClipsHash()
  1110. {
  1111. var hash = 0;
  1112. foreach (var clip in m_Clips)
  1113. {
  1114. hash = hash.CombineHash(clip.Hash());
  1115. }
  1116. return hash;
  1117. }
  1118. /// <summary>
  1119. /// Gets the hash code for an AnimationClip.
  1120. /// </summary>
  1121. /// <param name="clip">The animation clip.</param>
  1122. /// <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>
  1123. protected static int GetAnimationClipHash(AnimationClip clip)
  1124. {
  1125. var hash = 0;
  1126. if (clip != null && !clip.empty)
  1127. hash = hash.CombineHash(clip.frameRate.GetHashCode())
  1128. .CombineHash(clip.length.GetHashCode());
  1129. return hash;
  1130. }
  1131. bool HasNotifications()
  1132. {
  1133. return m_Markers.HasNotifications();
  1134. }
  1135. bool CanCompileNotifications()
  1136. {
  1137. return supportsNotifications && m_Markers.HasNotifications();
  1138. }
  1139. bool CanCreateMixerRecursive()
  1140. {
  1141. if (CanCreateTrackMixer())
  1142. return true;
  1143. foreach (var track in GetChildTracks())
  1144. {
  1145. if (track.CanCreateMixerRecursive())
  1146. return true;
  1147. }
  1148. return false;
  1149. }
  1150. }
  1151. }