123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329 |
- using System;
- using System.Collections.Generic;
- using UnityEngine.Animations;
- using UnityEngine.Playables;
-
- namespace UnityEngine.Timeline
- {
- /// <summary>
- /// A PlayableAsset representing a track inside a timeline.
- /// </summary>
- ///
- /// <remarks>
- /// Derive from TrackAsset to implement custom timeline tracks. TrackAsset derived classes support the following attributes:
- /// <seealso cref="UnityEngine.Timeline.HideInMenuAttribute"/>
- /// <seealso cref="UnityEngine.Timeline.TrackColorAttribute"/>
- /// <seealso cref="UnityEngine.Timeline.TrackClipTypeAttribute"/>
- /// <seealso cref="UnityEngine.Timeline.TrackBindingTypeAttribute"/>
- /// <seealso cref="System.ComponentModel.DisplayNameAttribute"/>
- /// </remarks>
- ///
- /// <example>
- /// <code source="../../DocCodeExamples/TrackAssetExamples.cs" region="declare-trackAssetExample" title="TrackAssetExample"/>
- /// </example>
- [Serializable]
- [IgnoreOnPlayableTrack]
- public abstract partial class TrackAsset : PlayableAsset, IPropertyPreview, ICurvesOwner
- {
- // Internal caches used to avoid memory allocation during graph construction
- 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 MarkDirtyTrackAndClips()
- {
- DirtyIndex++;
- foreach (var clip in GetClips())
- {
- if (clip != null)
- clip.MarkDirty();
- }
- }
- #endif
-
- /// <summary>
- /// The start time, in seconds, of this track
- /// </summary>
- public double start
- {
- get
- {
- UpdateDuration();
- return (double)m_Start;
- }
- }
-
- /// <summary>
- /// The end time, in seconds, of this track
- /// </summary>
- public double end
- {
- get
- {
- UpdateDuration();
- return (double)m_End;
- }
- }
-
- /// <summary>
- /// The length, in seconds, of this track
- /// </summary>
- public sealed override double duration
- {
- get
- {
- UpdateDuration();
- return (double)(m_End - m_Start);
- }
- }
-
- /// <summary>
- /// Whether the track is muted or not.
- /// </summary>
- /// <remarks>
- /// A muted track is excluded from the generated PlayableGraph
- /// </remarks>
- public bool muted
- {
- get { return m_Muted; }
- set { m_Muted = value; }
- }
-
- /// <summary>
- /// The muted state of a track.
- /// </summary>
- /// <remarks>
- /// A track is also muted when one of its parent tracks are muted.
- /// </remarks>
- 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;
- }
- }
-
- /// <summary>
- /// The TimelineAsset that this track belongs to.
- /// </summary>
- 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;
- }
- }
-
- /// <summary>
- /// The owner of this track.
- /// </summary>
- /// <remarks>
- /// If this track is a subtrack, the parent is a TrackAsset. Otherwise the parent is a TimelineAsset.
- /// </remarks>
- public PlayableAsset parent
- {
- get { return m_Parent; }
- internal set { m_Parent = value; }
- }
-
- /// <summary>
- /// A list of clips owned by this track
- /// </summary>
- /// <returns>Returns an enumerable list of clips owned by the track.</returns>
- 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;
- }
- }
-
- /// <summary>
- /// Whether this track is considered empty.
- /// </summary>
- /// <remarks>
- /// A track is considered empty when it does not contain a TimelineClip, Marker, or Curve.
- /// </remarks>
- public virtual bool isEmpty
- {
- get { return !hasClips && !hasCurves && GetMarkerCount() == 0; }
- }
-
- /// <summary>
- /// Whether this track contains any TimelineClip.
- /// </summary>
- public bool hasClips
- {
- get { return m_Clips != null && m_Clips.Count != 0; }
- }
-
- /// <summary>
- /// Whether this track contains animated properties for the attached PlayableAsset.
- /// </summary>
- /// <remarks>
- /// This property is false if the curves property is null or if it contains no information.
- /// </remarks>
- public bool hasCurves
- {
- get { return m_Curves != null && !m_Curves.empty; }
- }
-
- /// <summary>
- /// Returns whether this track is a subtrack
- /// </summary>
- public bool isSubTrack
- {
- get
- {
- var owner = parent as TrackAsset;
- return owner != null && owner.GetType() == GetType();
- }
- }
-
-
- /// <summary>
- /// Returns a description of the PlayableOutputs that will be created by this track.
- /// </summary>
- 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);
- }
- }
-
- /// <summary>
- /// The list of subtracks or child tracks attached to this track.
- /// </summary>
- /// <returns>Returns an enumerable list of child tracks owned directly by this track.</returns>
- /// <remarks>
- /// 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.
- /// </remarks>
- public IEnumerable<TrackAsset> GetChildTracks()
- {
- UpdateChildTrackCache();
- return m_ChildTrackCache;
- }
-
- internal string customPlayableTypename
- {
- get { return m_CustomPlayableFullTypename; }
- set { m_CustomPlayableFullTypename = value; }
- }
-
- /// <summary>
- /// An animation clip storing animated properties of the attached PlayableAsset
- /// </summary>
- 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; }
- }
-
- // for UI where we need to detect 'null' objects
- internal List<ScriptableObject> subTracksObjects
- {
- get { return m_Children; }
- }
-
- /// <summary>
- /// The local locked state of the track.
- /// </summary>
- /// <remarks>
- /// 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.
- ///
- /// 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.
- /// </remarks>
- public bool locked
- {
- get { return m_Locked; }
- set { m_Locked = value; }
- }
-
- /// <summary>
- /// The locked state of a track. (RO)
- /// </summary>
- /// <remarks>
- /// 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.
- ///
- /// 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.
- /// </remarks>
- 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;
- }
- }
-
- /// <summary>
- /// Indicates if a track accepts markers that implement <see cref="UnityEngine.Playables.INotification"/>.
- /// </summary>
- /// <remarks>
- /// Only tracks with a bound object of type <see cref="UnityEngine.GameObject"/> or <see cref="UnityEngine.Component"/> can accept notifications.
- /// </remarks>
- public bool supportsNotifications
- {
- get
- {
- if (!m_SupportsNotifications.HasValue)
- {
- m_SupportsNotifications = NotificationUtilities.TrackTypeSupportsNotifications(GetType());
- }
-
- return m_SupportsNotifications.Value;
- }
- }
-
- void __internalAwake() //do not use OnEnable, since users will want it to initialize their class
- {
- if (m_Clips == null)
- m_Clips = new List<TimelineClip>();
-
- m_ChildTrackCache = null;
- if (m_Children == null)
- m_Children = new List<ScriptableObject>();
- #if UNITY_EDITOR
- // validate the array. DON'T remove Unity null objects, just actual null objects
- 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
- }
-
- /// <summary>
- /// Creates an AnimationClip to store animated properties for the attached PlayableAsset.
- /// </summary>
- /// <remarks>
- /// If curves already exists for this track, this method produces no result regardless of
- /// the value specified for curvesClipName.
- /// </remarks>
- /// <remarks>
- /// When used from the editor, this method attempts to save the created curves clip to the TimelineAsset.
- /// The TimelineAsset must already exist in the AssetDatabase to save the curves clip. If the TimelineAsset
- /// does not exist, the curves clip is still created but it is not saved.
- /// </remarks>
- /// <param name="curvesClipName">
- /// The name of the AnimationClip to create.
- /// This method does not ensure unique names. If you want a unique clip name, you must provide one.
- /// See ObjectNames.GetUniqueName for information on a method that creates unique names.
- /// </param>
- public void CreateCurves(string curvesClipName)
- {
- if (m_Curves != null)
- return;
-
- m_Curves = TimelineCreateUtilities.CreateAnimationClipForTrack(string.IsNullOrEmpty(curvesClipName) ? kDefaultCurvesName : curvesClipName, this, true);
- }
-
- /// <summary>
- /// Creates a mixer used to blend playables generated by clips on the track.
- /// </summary>
- /// <param name="graph">The graph to inject playables into</param>
- /// <param name="go">The GameObject that requested the graph.</param>
- /// <param name="inputCount">The number of playables from clips that will be inputs to the returned mixer</param>
- /// <returns>A handle to the [[Playable]] representing the mixer.</returns>
- /// <remarks>
- /// Override this method to provide a custom playable for mixing clips on a graph.
- /// </remarks>
- public virtual Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
- {
- return Playable.Create(graph, inputCount);
- }
-
- /// <summary>
- /// Overrides PlayableAsset.CreatePlayable(). Not used in Timeline.
- /// </summary>
- /// <param name="graph"><inheritdoc/></param>
- /// <param name="go"><inheritdoc/></param>
- /// <returns><inheritDoc/></returns>
- public sealed override Playable CreatePlayable(PlayableGraph graph, GameObject go)
- {
- return Playable.Null;
- }
-
- /// <summary>
- /// Creates a TimelineClip on this track.
- /// </summary>
- /// <returns>Returns a new TimelineClip that is attached to the track.</returns>
- /// <remarks>
- /// The type of the playable asset attached to the clip is determined by TrackClip attributes that decorate the TrackAsset derived class
- /// </remarks>
- 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);
- }
-
- /// <summary>
- /// Creates a clip on the track with a playable asset attached, whose derived type is specified by T
- /// </summary>
- /// <typeparam name="T">A PlayableAsset derived type</typeparam>
- /// <returns>Returns a TimelineClip whose asset is of type T</returns>
- /// <remarks>
- /// Throws <exception cref="System.InvalidOperationException"/> if <typeparamref name="T"/> is not supported by the track.
- /// Supported types are determined by TrackClip attributes that decorate the TrackAsset derived class
- /// </remarks>
- public TimelineClip CreateClip<T>() where T : ScriptableObject, IPlayableAsset
- {
- return CreateClip(typeof(T));
- }
-
- /// <summary>
- /// Delete a clip from this track.
- /// </summary>
- /// <param name="clip">The clip to delete.</param>
- /// <returns>Returns true if the removal was successful</returns>
- /// <remarks>
- /// This method will delete a clip and any assets owned by the clip.
- /// </remarks>
- /// <exception>
- /// Throws <exception cref="System.InvalidOperationException"/> if <paramref name="clip"/> is not a child of the TrackAsset.
- /// </exception>
- 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);
- }
-
- /// <summary>
- /// Creates a marker of the requested type, at a specific time, and adds the marker to the current asset.
- /// </summary>
- /// <param name="type">The type of marker.</param>
- /// <param name="time">The time where the marker is created.</param>
- /// <returns>Returns the instance of the created marker.</returns>
- /// <remarks>
- /// All markers that implement IMarker and inherit from <see cref="UnityEngine.ScriptableObject"/> are supported.
- /// Markers that implement the INotification interface cannot be added to tracks that do not support notifications.
- /// CreateMarker will throw <exception cref="System.InvalidOperationException"/> with tracks that do not support notifications if <paramref name="type"/> implements the INotification interface.
- /// </remarks>
- /// <seealso cref="UnityEngine.Timeline.Marker"/>
- /// <seealso cref="UnityEngine.Timeline.TrackAsset.supportsNotifications"/>
- public IMarker CreateMarker(Type type, double time)
- {
- return m_Markers.CreateMarker(type, time, this);
- }
-
- /// <summary>
- /// Creates a marker of the requested type, at a specific time, and adds the marker to the current asset.
- /// </summary>
- /// <param name="time">The time where the marker is created.</param>
- /// <typeparam name="T">The type of marker to create.</typeparam>
- /// <returns>Returns the instance of the created marker.</returns>
- /// <remarks>
- /// All markers that implement IMarker and inherit from <see cref="UnityEngine.ScriptableObject"/> are supported.
- /// CreateMarker will throw <exception cref="System.InvalidOperationException"/> with tracks that do not support notifications if <typeparamref name="T"/> implements the INotification interface.
- /// </remarks>
- /// <seealso cref="UnityEngine.Timeline.Marker"/>
- /// <seealso cref="UnityEngine.Timeline.TrackAsset.supportsNotifications"/>
- public T CreateMarker<T>(double time) where T : ScriptableObject, IMarker
- {
- return (T)CreateMarker(typeof(T), time);
- }
-
- /// <summary>
- /// Removes a marker from the current asset.
- /// </summary>
- /// <param name="marker">The marker instance to be removed.</param>
- /// <returns>Returns true if the marker instance was successfully removed. Returns false otherwise.</returns>
- public bool DeleteMarker(IMarker marker)
- {
- return m_Markers.Remove(marker);
- }
-
- /// <summary>
- /// Returns an enumerable list of markers on the current asset.
- /// </summary>
- /// <returns>The list of markers on the asset.
- /// </returns>
- public IEnumerable<IMarker> GetMarkers()
- {
- return m_Markers.GetMarkers();
- }
-
- /// <summary>
- /// Returns the number of markers on the current asset.
- /// </summary>
- /// <returns>The number of markers.</returns>
- public int GetMarkerCount()
- {
- return m_Markers.Count;
- }
-
- /// <summary>
- /// Returns the marker at a given position, on the current asset.
- /// </summary>
- /// <param name="idx">The index of the marker to be returned.</param>
- /// <returns>The marker.</returns>
- /// <remarks>The ordering of the markers is not guaranteed.
- /// </remarks>
- 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);
- }
-
- /// <summary>
- /// Creates a timeline clip from an existing playable asset.
- /// </summary>
- /// <param name="asset"></param>
- /// <returns></returns>
- 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);
-
- ScriptPlayable<TimeNotificationBehaviour> notificationPlayable;
- if (go.TryGetComponent(out PlayableDirector director))
- notificationPlayable = NotificationUtilities.CreateNotificationsPlayable(graph, s_BuildData.markerList, director);
- else
- notificationPlayable = NotificationUtilities.CreateNotificationsPlayable(graph, s_BuildData.markerList, timelineAsset);
-
- 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);
-
- // clear the temporary build data to avoid holding references
- // case 1253974
- 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);
-
- // nothing to compile
- if (s_BuildData.trackList.Count == 0)
- return Playable.Null;
-
- // check if layers are supported
- 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;
- }
-
- // one track compiles. Add track mixer and clips
- if (s_BuildData.trackList.Count == 1)
- return s_BuildData.trackList[0].CompileClips(graph, go, s_BuildData.trackList[0].clips, tree);
-
- // no layer mixer provided. merge down all clips.
- 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
- // compile all the clips into a single mixer
- 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);
- }
-
- // sorts clips by start time
- internal void SortClips()
- {
- var clipsAsArray = clips; // will alloc
- if (!m_CacheSorted)
- {
- Array.Sort(clips, (clip1, clip2) => clip1.start.CompareTo(clip2.start));
- m_CacheSorted = true;
- }
- }
-
- // clears the clips after a clone
- internal void ClearClipsInternal()
- {
- m_Clips = new List<TimelineClip>();
- m_ClipsCache = null;
- }
-
- internal void ClearSubTracksInternal()
- {
- m_Children = new List<ScriptableObject>();
- Invalidate();
- }
-
- // called by an owned clip when it moves
- internal void OnClipMove()
- {
- m_CacheSorted = false;
- }
-
- internal TimelineClip CreateNewClipContainerInternal()
- {
- var clipContainer = new TimelineClip(this);
- clipContainer.asset = null;
-
- // position clip at end of sequence
- 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;
- }
-
- // Is this track compilable for the sequence
- // calculate the time interval that this track will be evaluated in.
- 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;
- }
-
- // calculate the time interval that the sequence will use to determine length.
- // by default this is the same as the evaluation, but subclasses can have different
- // behaviour
- internal virtual void GetSequenceTime(out double outStart, out double outDuration)
- {
- GetEvaluationTime(out outStart, out outDuration);
- }
-
- /// <summary>
- /// Called by the Timeline Editor to gather properties requiring preview.
- /// </summary>
- /// <param name="director">The PlayableDirector invoking the preview</param>
- /// <param name="driver">PropertyCollector used to gather previewable properties</param>
- public virtual void GatherProperties(PlayableDirector director, IPropertyCollector driver)
- {
- // only push on game objects if there is a binding. Subtracks
- // will use objects on the stack
- 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;
- }
-
- // special case for playable tracks, they accept all clips (in the runtime)
- return typeof(PlayableTrack).IsAssignableFrom(GetType()) &&
- typeof(IPlayableAsset).IsAssignableFrom(clipType) &&
- typeof(ScriptableObject).IsAssignableFrom(clipType);
- }
-
- /// <summary>
- /// Called when a clip is created on a track.
- /// </summary>
- /// <param name="clip">The timeline clip added to this track</param>
- /// <remarks>Use this method to set default values on a timeline clip, or it's PlayableAsset.</remarks>
- protected virtual void OnCreateClip(TimelineClip clip) { }
-
- void UpdateDuration()
- {
- // check if something changed in the clips that require a re-calculation of the evaluation times.
- 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);
-
- // calculate the extrapolations time.
- // TODO Extrapolation time should probably be extracted from the SequenceClip so only a track is aware of it.
- this.CalculateExtrapolationTimes();
- }
-
- protected internal virtual int CalculateItemsHash()
- {
- return HashUtility.CombineHash(GetClipsHash(), GetAnimationClipHash(m_Curves), GetTimeRangeHash());
- }
-
- /// <summary>
- /// Constructs a Playable from a TimelineClip.
- /// </summary>
- /// <param name="graph">PlayableGraph that will own the playable.</param>
- /// <param name="gameObject">The GameObject that builds the PlayableGraph.</param>
- /// <param name="clip">The TimelineClip to construct a playable for.</param>
- /// <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>
- /// <exception cref="ArgumentException">Thrown if the specified PlayableGraph is not valid.</exception>
- /// <exception cref="ArgumentNullException">Thrown if the specified TimelineClip is not valid.</exception>
- /// <remarks>
- /// 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.
- /// </remarks>
- 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;
- }
-
- /// <summary>
- /// Whether the track can create a mixer for its own contents.
- /// </summary>
- /// <returns>Returns true if the track's mixer should be included in the playable graph.</returns>
- /// <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>
- /// <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>
- /// <remarks>Override this method to change the conditions for a track to be included in the playable graph.</remarks>
- 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;
- }
-
- /// <summary>
- /// Gets the hash code for an AnimationClip.
- /// </summary>
- /// <param name="clip">The animation clip.</param>
- /// <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>
- 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;
- }
- }
- }
|