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