| using System; |
| using System.Collections.Generic; |
| using UnityEngine.Animations; |
| using UnityEngine.Playables; |
|
|
| namespace UnityEngine.Timeline |
| { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| [Serializable] |
| [IgnoreOnPlayableTrack] |
| public abstract partial class TrackAsset : PlayableAsset, IPropertyPreview, ICurvesOwner |
| { |
| |
| private struct TransientBuildData |
| { |
| public List<TrackAsset> trackList; |
| public List<TimelineClip> clipList; |
| public List<IMarker> markerList; |
|
|
| public static TransientBuildData Create() |
| { |
| return new TransientBuildData() |
| { |
| trackList = new List<TrackAsset>(20), |
| clipList = new List<TimelineClip>(500), |
| markerList = new List<IMarker>(100), |
| }; |
| } |
|
|
| public void Clear() |
| { |
| trackList.Clear(); |
| clipList.Clear(); |
| markerList.Clear(); |
| } |
| } |
|
|
| private static TransientBuildData s_BuildData = TransientBuildData.Create(); |
|
|
| internal const string kDefaultCurvesName = "Track Parameters"; |
|
|
| internal static event Action<TimelineClip, GameObject, Playable> OnClipPlayableCreate; |
| internal static event Action<TrackAsset, GameObject, Playable> OnTrackAnimationPlayableCreate; |
|
|
| [SerializeField, HideInInspector] bool m_Locked; |
| [SerializeField, HideInInspector] bool m_Muted; |
| [SerializeField, HideInInspector] string m_CustomPlayableFullTypename = string.Empty; |
| [SerializeField, HideInInspector] AnimationClip m_Curves; |
| [SerializeField, HideInInspector] PlayableAsset m_Parent; |
| [SerializeField, HideInInspector] List<ScriptableObject> m_Children; |
|
|
| [NonSerialized] int m_ItemsHash; |
| [NonSerialized] TimelineClip[] m_ClipsCache; |
|
|
| DiscreteTime m_Start; |
| DiscreteTime m_End; |
| bool m_CacheSorted; |
| bool? m_SupportsNotifications; |
|
|
| static TrackAsset[] s_EmptyCache = new TrackAsset[0]; |
| IEnumerable<TrackAsset> m_ChildTrackCache; |
|
|
| static Dictionary<Type, TrackBindingTypeAttribute> s_TrackBindingTypeAttributeCache = new Dictionary<Type, TrackBindingTypeAttribute>(); |
|
|
| [SerializeField, HideInInspector] protected internal List<TimelineClip> m_Clips = new List<TimelineClip>(); |
|
|
| [SerializeField, HideInInspector] MarkerList m_Markers = new MarkerList(0); |
|
|
| #if UNITY_EDITOR |
| internal int DirtyIndex { get; private set; } |
| internal void MarkDirty() |
| { |
| DirtyIndex++; |
| foreach (var clip in GetClips()) |
| { |
| if (clip != null) |
| clip.MarkDirty(); |
| } |
| } |
|
|
| #endif |
|
|
| |
| |
| |
| public double start |
| { |
| get |
| { |
| UpdateDuration(); |
| return (double)m_Start; |
| } |
| } |
|
|
| |
| |
| |
| public double end |
| { |
| get |
| { |
| UpdateDuration(); |
| return (double)m_End; |
| } |
| } |
|
|
| |
| |
| |
| public sealed override double duration |
| { |
| get |
| { |
| UpdateDuration(); |
| return (double)(m_End - m_Start); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public bool muted |
| { |
| get { return m_Muted; } |
| set { m_Muted = value; } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public bool mutedInHierarchy |
| { |
| get |
| { |
| if (muted) |
| return true; |
|
|
| TrackAsset p = this; |
| while (p.parent as TrackAsset != null) |
| { |
| p = (TrackAsset)p.parent; |
| if (p as GroupTrack != null) |
| return p.mutedInHierarchy; |
| } |
|
|
| return false; |
| } |
| } |
|
|
| |
| |
| |
| public TimelineAsset timelineAsset |
| { |
| get |
| { |
| var node = this; |
| while (node != null) |
| { |
| if (node.parent == null) |
| return null; |
|
|
| var seq = node.parent as TimelineAsset; |
| if (seq != null) |
| return seq; |
|
|
| node = node.parent as TrackAsset; |
| } |
| return null; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public PlayableAsset parent |
| { |
| get { return m_Parent; } |
| internal set { m_Parent = value; } |
| } |
|
|
| |
| |
| |
| |
| public IEnumerable<TimelineClip> GetClips() |
| { |
| return clips; |
| } |
|
|
| internal TimelineClip[] clips |
| { |
| get |
| { |
| if (m_Clips == null) |
| m_Clips = new List<TimelineClip>(); |
|
|
| if (m_ClipsCache == null) |
| { |
| m_CacheSorted = false; |
| m_ClipsCache = m_Clips.ToArray(); |
| } |
|
|
| return m_ClipsCache; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public virtual bool isEmpty |
| { |
| get { return !hasClips && !hasCurves && GetMarkerCount() == 0; } |
| } |
|
|
| |
| |
| |
| public bool hasClips |
| { |
| get { return m_Clips != null && m_Clips.Count != 0; } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public bool hasCurves |
| { |
| get { return m_Curves != null && !m_Curves.empty; } |
| } |
|
|
| |
| |
| |
| public bool isSubTrack |
| { |
| get |
| { |
| var owner = parent as TrackAsset; |
| return owner != null && owner.GetType() == GetType(); |
| } |
| } |
|
|
|
|
| |
| |
| |
| public override IEnumerable<PlayableBinding> outputs |
| { |
| get |
| { |
| TrackBindingTypeAttribute attribute; |
| if (!s_TrackBindingTypeAttributeCache.TryGetValue(GetType(), out attribute)) |
| { |
| attribute = (TrackBindingTypeAttribute)Attribute.GetCustomAttribute(GetType(), typeof(TrackBindingTypeAttribute)); |
| s_TrackBindingTypeAttributeCache.Add(GetType(), attribute); |
| } |
|
|
| var trackBindingType = attribute != null ? attribute.type : null; |
| yield return ScriptPlayableBinding.Create(name, this, trackBindingType); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public IEnumerable<TrackAsset> GetChildTracks() |
| { |
| UpdateChildTrackCache(); |
| return m_ChildTrackCache; |
| } |
|
|
| internal string customPlayableTypename |
| { |
| get { return m_CustomPlayableFullTypename; } |
| set { m_CustomPlayableFullTypename = value; } |
| } |
|
|
| |
| |
| |
| public AnimationClip curves |
| { |
| get { return m_Curves; } |
| internal set { m_Curves = value; } |
| } |
|
|
| string ICurvesOwner.defaultCurvesName |
| { |
| get { return kDefaultCurvesName; } |
| } |
|
|
| Object ICurvesOwner.asset |
| { |
| get { return this; } |
| } |
|
|
| Object ICurvesOwner.assetOwner |
| { |
| get { return timelineAsset; } |
| } |
|
|
| TrackAsset ICurvesOwner.targetTrack |
| { |
| get { return this; } |
| } |
|
|
| |
| internal List<ScriptableObject> subTracksObjects |
| { |
| get { return m_Children; } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| public bool locked |
| { |
| get { return m_Locked; } |
| set { m_Locked = value; } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| public bool lockedInHierarchy |
| { |
| get |
| { |
| if (locked) |
| return true; |
|
|
| TrackAsset p = this; |
| while (p.parent as TrackAsset != null) |
| { |
| p = (TrackAsset)p.parent; |
| if (p as GroupTrack != null) |
| return p.lockedInHierarchy; |
| } |
|
|
| return false; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public bool supportsNotifications |
| { |
| get |
| { |
| if (!m_SupportsNotifications.HasValue) |
| { |
| m_SupportsNotifications = NotificationUtilities.TrackTypeSupportsNotifications(GetType()); |
| } |
|
|
| return m_SupportsNotifications.Value; |
| } |
| } |
|
|
| void __internalAwake() |
| { |
| if (m_Clips == null) |
| m_Clips = new List<TimelineClip>(); |
|
|
| m_ChildTrackCache = null; |
| if (m_Children == null) |
| m_Children = new List<ScriptableObject>(); |
| #if UNITY_EDITOR |
| |
| for (int i = m_Children.Count - 1; i >= 0; i--) |
| { |
| object o = m_Children[i]; |
| if (o == null) |
| { |
| Debug.LogWarning("Empty child track found while loading timeline. It will be removed."); |
| m_Children.RemoveAt(i); |
| } |
| } |
| #endif |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public void CreateCurves(string curvesClipName) |
| { |
| if (m_Curves != null) |
| return; |
|
|
| m_Curves = TimelineCreateUtilities.CreateAnimationClipForTrack(string.IsNullOrEmpty(curvesClipName) ? kDefaultCurvesName : curvesClipName, this, true); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public virtual Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount) |
| { |
| return Playable.Create(graph, inputCount); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public sealed override Playable CreatePlayable(PlayableGraph graph, GameObject go) |
| { |
| return Playable.Null; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public TimelineClip CreateDefaultClip() |
| { |
| var trackClipTypeAttributes = GetType().GetCustomAttributes(typeof(TrackClipTypeAttribute), true); |
| Type playableAssetType = null; |
| foreach (var trackClipTypeAttribute in trackClipTypeAttributes) |
| { |
| var attribute = trackClipTypeAttribute as TrackClipTypeAttribute; |
| if (attribute != null && typeof(IPlayableAsset).IsAssignableFrom(attribute.inspectedType) && typeof(ScriptableObject).IsAssignableFrom(attribute.inspectedType)) |
| { |
| playableAssetType = attribute.inspectedType; |
| break; |
| } |
| } |
|
|
| if (playableAssetType == null) |
| { |
| Debug.LogWarning("Cannot create a default clip for type " + GetType()); |
| return null; |
| } |
| return CreateAndAddNewClipOfType(playableAssetType); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public TimelineClip CreateClip<T>() where T : ScriptableObject, IPlayableAsset |
| { |
| return CreateClip(typeof(T)); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public bool DeleteClip(TimelineClip clip) |
| { |
| if (!m_Clips.Contains(clip)) |
| throw new InvalidOperationException("Cannot delete clip since it is not a child of the TrackAsset."); |
|
|
| return timelineAsset != null && timelineAsset.DeleteClip(clip); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public IMarker CreateMarker(Type type, double time) |
| { |
| return m_Markers.CreateMarker(type, time, this); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public T CreateMarker<T>(double time) where T : ScriptableObject, IMarker |
| { |
| return (T)CreateMarker(typeof(T), time); |
| } |
|
|
| |
| |
| |
| |
| |
| public bool DeleteMarker(IMarker marker) |
| { |
| return m_Markers.Remove(marker); |
| } |
|
|
| |
| |
| |
| |
| |
| public IEnumerable<IMarker> GetMarkers() |
| { |
| return m_Markers.GetMarkers(); |
| } |
|
|
| |
| |
| |
| |
| public int GetMarkerCount() |
| { |
| return m_Markers.Count; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public IMarker GetMarker(int idx) |
| { |
| return m_Markers[idx]; |
| } |
|
|
| internal TimelineClip CreateClip(System.Type requestedType) |
| { |
| if (ValidateClipType(requestedType)) |
| return CreateAndAddNewClipOfType(requestedType); |
|
|
| throw new InvalidOperationException("Clips of type " + requestedType + " are not permitted on tracks of type " + GetType()); |
| } |
|
|
| internal TimelineClip CreateAndAddNewClipOfType(Type requestedType) |
| { |
| var newClip = CreateClipOfType(requestedType); |
| AddClip(newClip); |
| return newClip; |
| } |
|
|
| internal TimelineClip CreateClipOfType(Type requestedType) |
| { |
| if (!ValidateClipType(requestedType)) |
| throw new System.InvalidOperationException("Clips of type " + requestedType + " are not permitted on tracks of type " + GetType()); |
|
|
| var playableAsset = CreateInstance(requestedType); |
| if (playableAsset == null) |
| { |
| throw new System.InvalidOperationException("Could not create an instance of the ScriptableObject type " + requestedType.Name); |
| } |
| playableAsset.name = requestedType.Name; |
| TimelineCreateUtilities.SaveAssetIntoObject(playableAsset, this); |
| TimelineUndo.RegisterCreatedObjectUndo(playableAsset, "Create Clip"); |
|
|
| return CreateClipFromAsset(playableAsset); |
| } |
|
|
| |
| |
| |
| |
| |
| internal TimelineClip CreateClipFromPlayableAsset(IPlayableAsset asset) |
| { |
| if (asset == null) |
| throw new ArgumentNullException("asset"); |
|
|
| if ((asset as ScriptableObject) == null) |
| throw new System.ArgumentException("CreateClipFromPlayableAsset " + " only supports ScriptableObject-derived Types"); |
|
|
| if (!ValidateClipType(asset.GetType())) |
| throw new System.InvalidOperationException("Clips of type " + asset.GetType() + " are not permitted on tracks of type " + GetType()); |
|
|
| return CreateClipFromAsset(asset as ScriptableObject); |
| } |
|
|
| private TimelineClip CreateClipFromAsset(ScriptableObject playableAsset) |
| { |
| TimelineUndo.PushUndo(this, "Create Clip"); |
|
|
| var newClip = CreateNewClipContainerInternal(); |
| newClip.displayName = playableAsset.name; |
| newClip.asset = playableAsset; |
|
|
| IPlayableAsset iPlayableAsset = playableAsset as IPlayableAsset; |
| if (iPlayableAsset != null) |
| { |
| var candidateDuration = iPlayableAsset.duration; |
|
|
| if (!double.IsInfinity(candidateDuration) && candidateDuration > 0) |
| newClip.duration = Math.Min(Math.Max(candidateDuration, TimelineClip.kMinDuration), TimelineClip.kMaxTimeValue); |
| } |
|
|
| try |
| { |
| OnCreateClip(newClip); |
| } |
| catch (Exception e) |
| { |
| Debug.LogError(e.Message, playableAsset); |
| return null; |
| } |
|
|
| return newClip; |
| } |
|
|
| internal IEnumerable<ScriptableObject> GetMarkersRaw() |
| { |
| return m_Markers.GetRawMarkerList(); |
| } |
|
|
| internal void ClearMarkers() |
| { |
| m_Markers.Clear(); |
| } |
|
|
| internal void AddMarker(ScriptableObject e) |
| { |
| m_Markers.Add(e); |
| } |
|
|
| internal bool DeleteMarkerRaw(ScriptableObject marker) |
| { |
| return m_Markers.Remove(marker, timelineAsset, this); |
| } |
|
|
| int GetTimeRangeHash() |
| { |
| double start = double.MaxValue, end = double.MinValue; |
| int count = m_Markers.Count; |
| for (int i = 0; i < m_Markers.Count; i++) |
| { |
| var marker = m_Markers[i]; |
| if (!(marker is INotification)) |
| { |
| continue; |
| } |
|
|
| if (marker.time < start) |
| start = marker.time; |
| if (marker.time > end) |
| end = marker.time; |
| } |
|
|
| return start.GetHashCode().CombineHash(end.GetHashCode()); |
| } |
|
|
| internal void AddClip(TimelineClip newClip) |
| { |
| if (!m_Clips.Contains(newClip)) |
| { |
| m_Clips.Add(newClip); |
| m_ClipsCache = null; |
| } |
| } |
|
|
| Playable CreateNotificationsPlayable(PlayableGraph graph, Playable mixerPlayable, GameObject go, Playable timelinePlayable) |
| { |
| s_BuildData.markerList.Clear(); |
| GatherNotifications(s_BuildData.markerList); |
| DirectorWrapMode extrapolationMode = DirectorWrapMode.None; |
| if (go.TryGetComponent(out PlayableDirector director)) |
| { |
| extrapolationMode = director.extrapolationMode; |
| } |
|
|
| var duration = timelineAsset.duration; |
| var notificationPlayable = NotificationUtilities.CreateNotificationsPlayable(graph, s_BuildData.markerList, duration, extrapolationMode); |
| if (notificationPlayable.IsValid()) |
| { |
| notificationPlayable.GetBehaviour().timeSource = timelinePlayable; |
| if (mixerPlayable.IsValid()) |
| { |
| notificationPlayable.SetInputCount(1); |
| graph.Connect(mixerPlayable, 0, notificationPlayable, 0); |
| notificationPlayable.SetInputWeight(mixerPlayable, 1); |
| } |
| } |
|
|
| return notificationPlayable; |
| } |
|
|
| internal Playable CreatePlayableGraph(PlayableGraph graph, GameObject go, IntervalTree<RuntimeElement> tree, Playable timelinePlayable) |
| { |
| UpdateDuration(); |
| var mixerPlayable = Playable.Null; |
| if (CanCreateMixerRecursive()) |
| mixerPlayable = CreateMixerPlayableGraph(graph, go, tree); |
|
|
| Playable notificationsPlayable = CreateNotificationsPlayable(graph, mixerPlayable, go, timelinePlayable); |
|
|
| |
| |
| s_BuildData.Clear(); |
| if (!notificationsPlayable.IsValid() && !mixerPlayable.IsValid()) |
| { |
| Debug.LogErrorFormat("Track {0} of type {1} has no notifications and returns an invalid mixer Playable", name, |
| GetType().FullName); |
|
|
| return Playable.Create(graph); |
| } |
|
|
| return notificationsPlayable.IsValid() ? notificationsPlayable : mixerPlayable; |
| } |
|
|
| internal virtual Playable CompileClips(PlayableGraph graph, GameObject go, IList<TimelineClip> timelineClips, IntervalTree<RuntimeElement> tree) |
| { |
| var blend = CreateTrackMixer(graph, go, timelineClips.Count); |
| for (var c = 0; c < timelineClips.Count; c++) |
| { |
| var source = CreatePlayable(graph, go, timelineClips[c]); |
| if (source.IsValid()) |
| { |
| source.SetDuration(timelineClips[c].duration); |
| var clip = new RuntimeClip(timelineClips[c], source, blend); |
| tree.Add(clip); |
| graph.Connect(source, 0, blend, c); |
| blend.SetInputWeight(c, 0.0f); |
| } |
| } |
| ConfigureTrackAnimation(tree, go, blend); |
| return blend; |
| } |
|
|
| void GatherCompilableTracks(IList<TrackAsset> tracks) |
| { |
| if (!muted && CanCreateTrackMixer()) |
| tracks.Add(this); |
|
|
| foreach (var c in GetChildTracks()) |
| { |
| if (c != null) |
| c.GatherCompilableTracks(tracks); |
| } |
| } |
|
|
| void GatherNotifications(List<IMarker> markers) |
| { |
| if (!muted && CanCompileNotifications()) |
| markers.AddRange(GetMarkers()); |
| foreach (var c in GetChildTracks()) |
| { |
| if (c != null) |
| c.GatherNotifications(markers); |
| } |
| } |
|
|
| internal virtual Playable CreateMixerPlayableGraph(PlayableGraph graph, GameObject go, IntervalTree<RuntimeElement> tree) |
| { |
| if (tree == null) |
| throw new ArgumentException("IntervalTree argument cannot be null", "tree"); |
|
|
| if (go == null) |
| throw new ArgumentException("GameObject argument cannot be null", "go"); |
|
|
| s_BuildData.Clear(); |
| GatherCompilableTracks(s_BuildData.trackList); |
|
|
| |
| if (s_BuildData.trackList.Count == 0) |
| return Playable.Null; |
|
|
| |
| Playable layerMixer = Playable.Null; |
| ILayerable layerable = this as ILayerable; |
| if (layerable != null) |
| layerMixer = layerable.CreateLayerMixer(graph, go, s_BuildData.trackList.Count); |
|
|
| if (layerMixer.IsValid()) |
| { |
| for (int i = 0; i < s_BuildData.trackList.Count; i++) |
| { |
| var mixer = s_BuildData.trackList[i].CompileClips(graph, go, s_BuildData.trackList[i].clips, tree); |
| if (mixer.IsValid()) |
| { |
| graph.Connect(mixer, 0, layerMixer, i); |
| layerMixer.SetInputWeight(i, 1.0f); |
| } |
| } |
| return layerMixer; |
| } |
|
|
| |
| if (s_BuildData.trackList.Count == 1) |
| return s_BuildData.trackList[0].CompileClips(graph, go, s_BuildData.trackList[0].clips, tree); |
|
|
| |
| for (int i = 0; i < s_BuildData.trackList.Count; i++) |
| s_BuildData.clipList.AddRange(s_BuildData.trackList[i].clips); |
|
|
| #if UNITY_EDITOR |
| bool applyWarning = false; |
| for (int i = 0; i < s_BuildData.trackList.Count; i++) |
| applyWarning |= i > 0 && s_BuildData.trackList[i].hasCurves; |
|
|
| if (applyWarning) |
| 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."); |
| #endif |
| |
| return CompileClips(graph, go, s_BuildData.clipList, tree); |
| } |
|
|
| internal void ConfigureTrackAnimation(IntervalTree<RuntimeElement> tree, GameObject go, Playable blend) |
| { |
| if (!hasCurves) |
| return; |
|
|
| blend.SetAnimatedProperties(m_Curves); |
| tree.Add(new InfiniteRuntimeClip(blend)); |
|
|
| if (OnTrackAnimationPlayableCreate != null) |
| OnTrackAnimationPlayableCreate.Invoke(this, go, blend); |
| } |
|
|
| |
| internal void SortClips() |
| { |
| var clipsAsArray = clips; |
| if (!m_CacheSorted) |
| { |
| Array.Sort(clips, (clip1, clip2) => clip1.start.CompareTo(clip2.start)); |
| m_CacheSorted = true; |
| } |
| } |
|
|
| |
| internal void ClearClipsInternal() |
| { |
| m_Clips = new List<TimelineClip>(); |
| m_ClipsCache = null; |
| } |
|
|
| internal void ClearSubTracksInternal() |
| { |
| m_Children = new List<ScriptableObject>(); |
| Invalidate(); |
| } |
|
|
| |
| internal void OnClipMove() |
| { |
| m_CacheSorted = false; |
| } |
|
|
| internal TimelineClip CreateNewClipContainerInternal() |
| { |
| var clipContainer = new TimelineClip(this); |
| clipContainer.asset = null; |
|
|
| |
| var newClipStart = 0.0; |
| for (var a = 0; a < m_Clips.Count - 1; a++) |
| { |
| var clipDuration = m_Clips[a].duration; |
| if (double.IsInfinity(clipDuration)) |
| clipDuration = TimelineClip.kDefaultClipDurationInSeconds; |
| newClipStart = Math.Max(newClipStart, m_Clips[a].start + clipDuration); |
| } |
|
|
| clipContainer.mixInCurve = AnimationCurve.EaseInOut(0, 0, 1, 1); |
| clipContainer.mixOutCurve = AnimationCurve.EaseInOut(0, 1, 1, 0); |
| clipContainer.start = newClipStart; |
| clipContainer.duration = TimelineClip.kDefaultClipDurationInSeconds; |
| clipContainer.displayName = "untitled"; |
| return clipContainer; |
| } |
|
|
| internal void AddChild(TrackAsset child) |
| { |
| if (child == null) |
| return; |
|
|
| m_Children.Add(child); |
| child.parent = this; |
| Invalidate(); |
| } |
|
|
| internal void MoveLastTrackBefore(TrackAsset asset) |
| { |
| if (m_Children == null || m_Children.Count < 2 || asset == null) |
| return; |
|
|
| var lastTrack = m_Children[m_Children.Count - 1]; |
| if (lastTrack == asset) |
| return; |
|
|
| for (int i = 0; i < m_Children.Count - 1; i++) |
| { |
| if (m_Children[i] == asset) |
| { |
| for (int j = m_Children.Count - 1; j > i; j--) |
| m_Children[j] = m_Children[j - 1]; |
| m_Children[i] = lastTrack; |
| Invalidate(); |
| break; |
| } |
| } |
| } |
|
|
| internal bool RemoveSubTrack(TrackAsset child) |
| { |
| if (m_Children.Remove(child)) |
| { |
| Invalidate(); |
| child.parent = null; |
| return true; |
| } |
| return false; |
| } |
|
|
| internal void RemoveClip(TimelineClip clip) |
| { |
| m_Clips.Remove(clip); |
| m_ClipsCache = null; |
| } |
|
|
| |
| |
| internal virtual void GetEvaluationTime(out double outStart, out double outDuration) |
| { |
| outStart = 0; |
| outDuration = 1; |
|
|
| outStart = double.PositiveInfinity; |
| var outEnd = double.NegativeInfinity; |
|
|
| if (hasCurves) |
| { |
| outStart = 0.0; |
| outEnd = TimeUtility.GetAnimationClipLength(curves); |
| } |
|
|
| foreach (var clip in clips) |
| { |
| outStart = Math.Min(clip.start, outStart); |
| outEnd = Math.Max(clip.end, outEnd); |
| } |
|
|
| if (HasNotifications()) |
| { |
| var notificationDuration = GetNotificationDuration(); |
| outStart = Math.Min(notificationDuration, outStart); |
| outEnd = Math.Max(notificationDuration, outEnd); |
| } |
|
|
| if (double.IsInfinity(outStart) || double.IsInfinity(outEnd)) |
| outStart = outDuration = 0.0; |
| else |
| outDuration = outEnd - outStart; |
| } |
|
|
| |
| |
| |
| internal virtual void GetSequenceTime(out double outStart, out double outDuration) |
| { |
| GetEvaluationTime(out outStart, out outDuration); |
| } |
|
|
| |
| |
| |
| |
| |
| public virtual void GatherProperties(PlayableDirector director, IPropertyCollector driver) |
| { |
| |
| |
| var gameObject = GetGameObjectBinding(director); |
| if (gameObject != null) |
| driver.PushActiveGameObject(gameObject); |
|
|
| if (hasCurves) |
| driver.AddObjectProperties(this, m_Curves); |
|
|
| foreach (var clip in clips) |
| { |
| if (clip.curves != null && clip.asset != null) |
| driver.AddObjectProperties(clip.asset, clip.curves); |
|
|
| IPropertyPreview modifier = clip.asset as IPropertyPreview; |
| if (modifier != null) |
| modifier.GatherProperties(director, driver); |
| } |
|
|
| foreach (var subtrack in GetChildTracks()) |
| { |
| if (subtrack != null) |
| subtrack.GatherProperties(director, driver); |
| } |
|
|
| if (gameObject != null) |
| driver.PopActiveGameObject(); |
| } |
|
|
| internal GameObject GetGameObjectBinding(PlayableDirector director) |
| { |
| if (director == null) |
| return null; |
|
|
| var binding = director.GetGenericBinding(this); |
|
|
| var gameObject = binding as GameObject; |
| if (gameObject != null) |
| return gameObject; |
|
|
| var comp = binding as Component; |
| if (comp != null) |
| return comp.gameObject; |
|
|
| return null; |
| } |
|
|
| internal bool ValidateClipType(Type clipType) |
| { |
| var attrs = GetType().GetCustomAttributes(typeof(TrackClipTypeAttribute), true); |
| for (var c = 0; c < attrs.Length; ++c) |
| { |
| var attr = (TrackClipTypeAttribute)attrs[c]; |
| if (attr.inspectedType.IsAssignableFrom(clipType)) |
| return true; |
| } |
|
|
| |
| return typeof(PlayableTrack).IsAssignableFrom(GetType()) && |
| typeof(IPlayableAsset).IsAssignableFrom(clipType) && |
| typeof(ScriptableObject).IsAssignableFrom(clipType); |
| } |
|
|
| |
| |
| |
| |
| |
| protected virtual void OnCreateClip(TimelineClip clip) { } |
|
|
| void UpdateDuration() |
| { |
| |
| var itemsHash = CalculateItemsHash(); |
| if (itemsHash == m_ItemsHash) |
| return; |
| m_ItemsHash = itemsHash; |
|
|
| double trackStart, trackDuration; |
| GetSequenceTime(out trackStart, out trackDuration); |
|
|
| m_Start = (DiscreteTime)trackStart; |
| m_End = (DiscreteTime)(trackStart + trackDuration); |
|
|
| |
| |
| this.CalculateExtrapolationTimes(); |
| } |
|
|
| protected internal virtual int CalculateItemsHash() |
| { |
| return HashUtility.CombineHash(GetClipsHash(), GetAnimationClipHash(m_Curves), GetTimeRangeHash()); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| protected virtual Playable CreatePlayable(PlayableGraph graph, GameObject gameObject, TimelineClip clip) |
| { |
| if (!graph.IsValid()) |
| throw new ArgumentException("graph must be a valid PlayableGraph"); |
| if (clip == null) |
| throw new ArgumentNullException("clip"); |
|
|
| var asset = clip.asset as IPlayableAsset; |
| if (asset != null) |
| { |
| var handle = asset.CreatePlayable(graph, gameObject); |
| if (handle.IsValid()) |
| { |
| handle.SetAnimatedProperties(clip.curves); |
| handle.SetSpeed(clip.timeScale); |
| if (OnClipPlayableCreate != null) |
| OnClipPlayableCreate(clip, gameObject, handle); |
| } |
| return handle; |
| } |
| return Playable.Null; |
| } |
|
|
| internal void Invalidate() |
| { |
| m_ChildTrackCache = null; |
| var timeline = timelineAsset; |
| if (timeline != null) |
| { |
| timeline.Invalidate(); |
| } |
| } |
|
|
| internal double GetNotificationDuration() |
| { |
| if (!supportsNotifications) |
| { |
| return 0; |
| } |
|
|
| var maxTime = 0.0; |
| int count = m_Markers.Count; |
| for (int i = 0; i < count; i++) |
| { |
| var marker = m_Markers[i]; |
| if (!(marker is INotification)) |
| { |
| continue; |
| } |
| maxTime = Math.Max(maxTime, marker.time); |
| } |
|
|
| return maxTime; |
| } |
|
|
| internal virtual bool CanCompileClips() |
| { |
| return hasClips || hasCurves; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public virtual bool CanCreateTrackMixer() |
| { |
| return CanCompileClips(); |
| } |
|
|
| internal bool IsCompilable() |
| { |
| bool isContainer = typeof(GroupTrack).IsAssignableFrom(GetType()); |
|
|
| if (isContainer) |
| return false; |
|
|
| var ret = !mutedInHierarchy && (CanCreateTrackMixer() || CanCompileNotifications()); |
| if (!ret) |
| { |
| foreach (var t in GetChildTracks()) |
| { |
| if (t.IsCompilable()) |
| return true; |
| } |
| } |
|
|
| return ret; |
| } |
|
|
| private void UpdateChildTrackCache() |
| { |
| if (m_ChildTrackCache == null) |
| { |
| if (m_Children == null || m_Children.Count == 0) |
| m_ChildTrackCache = s_EmptyCache; |
| else |
| { |
| var childTracks = new List<TrackAsset>(m_Children.Count); |
| for (int i = 0; i < m_Children.Count; i++) |
| { |
| var subTrack = m_Children[i] as TrackAsset; |
| if (subTrack != null) |
| childTracks.Add(subTrack); |
| } |
| m_ChildTrackCache = childTracks; |
| } |
| } |
| } |
|
|
| internal virtual int Hash() |
| { |
| return clips.Length + (m_Markers.Count << 16); |
| } |
|
|
| int GetClipsHash() |
| { |
| var hash = 0; |
| foreach (var clip in m_Clips) |
| { |
| hash = hash.CombineHash(clip.Hash()); |
| } |
| return hash; |
| } |
|
|
| |
| |
| |
| |
| |
| protected static int GetAnimationClipHash(AnimationClip clip) |
| { |
| var hash = 0; |
| if (clip != null && !clip.empty) |
| hash = hash.CombineHash(clip.frameRate.GetHashCode()) |
| .CombineHash(clip.length.GetHashCode()); |
|
|
| return hash; |
| } |
|
|
| bool HasNotifications() |
| { |
| return m_Markers.HasNotifications(); |
| } |
|
|
| bool CanCompileNotifications() |
| { |
| return supportsNotifications && m_Markers.HasNotifications(); |
| } |
|
|
| bool CanCreateMixerRecursive() |
| { |
| if (CanCreateTrackMixer()) |
| return true; |
| foreach (var track in GetChildTracks()) |
| { |
| if (track.CanCreateMixerRecursive()) |
| return true; |
| } |
|
|
| return false; |
| } |
| } |
| } |
|
|