2022-01-12 10:06:03 +03:00
using System ;
using System.Collections.Generic ;
using UnityEngine.Playables ;
namespace UnityEngine.Timeline
{
/// <summary>
/// A PlayableAsset that represents a timeline.
/// </summary>
[ExcludeFromPreset]
[Serializable]
public partial class TimelineAsset : PlayableAsset , ISerializationCallbackReceiver , ITimelineClipAsset , IPropertyPreview
{
/// <summary>
/// How the duration of the timeline is determined.
/// </summary>
public enum DurationMode
{
/// <summary>
/// The duration of the timeline is determined based on the clips present.
/// </summary>
BasedOnClips ,
/// <summary>
/// The duration of the timeline is a fixed length.
/// </summary>
FixedLength
}
/// <summary>
/// Properties of the timeline that are used by the editor
/// </summary>
[Serializable]
public class EditorSettings
{
internal static readonly float kMinFps = ( float ) TimeUtility . kFrameRateEpsilon ;
internal static readonly float kMaxFps = 1000.0f ;
internal static readonly float kDefaultFps = 60.0f ;
[HideInInspector, SerializeField] float m_Framerate = kDefaultFps ;
2022-01-12 10:39:15 +03:00
[HideInInspector, SerializeField] bool m_ScenePreview = true ;
2022-01-12 10:06:03 +03:00
/// <summary>
/// The frames per second used for snapping and time ruler display
/// </summary>
public float fps
{
get
{
return m_Framerate ;
}
set
{
m_Framerate = GetValidFramerate ( value ) ;
}
}
2022-01-12 10:39:15 +03:00
/// <summary>
/// Set to false to ignore scene preview when this timeline is played by the Timeline window.
/// </summary>
/// <remarks>
/// When set to false, this setting will
/// - Disable scene preview when this timeline is played by the Timeline window.
/// - Disable recording for all recordable tracks.
/// - Disable play range in the Timeline window.
/// - `Stop()` is not called on the `PlayableDirector` when switching between different `TimelineAsset`s in the TimelineWindow.
///
/// `scenePreview` will only be applied if the asset is the master timeline.
/// </remarks>
/// <seealso cref="UnityEngine.Timeline.TimelineAsset"/>
public bool scenePreview
{
get = > m_ScenePreview ;
set = > m_ScenePreview = value ;
}
2022-01-12 10:06:03 +03:00
}
[HideInInspector, SerializeField] List < ScriptableObject > m_Tracks ;
[HideInInspector, SerializeField] double m_FixedDuration ; // only applied if duration mode is Fixed
[HideInInspector, NonSerialized] TrackAsset [ ] m_CacheOutputTracks ;
[HideInInspector, NonSerialized] List < TrackAsset > m_CacheRootTracks ;
[HideInInspector, NonSerialized] List < TrackAsset > m_CacheFlattenedTracks ;
[HideInInspector, SerializeField] EditorSettings m_EditorSettings = new EditorSettings ( ) ;
[SerializeField] DurationMode m_DurationMode ;
[HideInInspector, SerializeField] MarkerTrack m_MarkerTrack ;
/// <summary>
/// Settings used by timeline for editing purposes
/// </summary>
public EditorSettings editorSettings
{
get { return m_EditorSettings ; }
}
/// <summary>
/// The length, in seconds, of the timeline
/// </summary>
public override double duration
{
get
{
// @todo cache this value when rebuilt
if ( m_DurationMode = = DurationMode . BasedOnClips )
2022-01-12 10:39:15 +03:00
{
//avoid having no clip evaluated at the end by removing a tick from the total duration
var discreteDuration = CalculateItemsDuration ( ) ;
if ( discreteDuration < = 0 )
return 0.0 ;
return ( double ) discreteDuration . OneTickBefore ( ) ;
}
2022-01-12 10:06:03 +03:00
return m_FixedDuration ;
}
}
/// <summary>
/// The length of the timeline when durationMode is set to fixed length.
/// </summary>
public double fixedDuration
{
get
{
DiscreteTime discreteDuration = ( DiscreteTime ) m_FixedDuration ;
if ( discreteDuration < = 0 )
return 0.0 ;
//avoid having no clip evaluated at the end by removing a tick from the total duration
return ( double ) discreteDuration . OneTickBefore ( ) ;
}
set { m_FixedDuration = Math . Max ( 0.0 , value ) ; }
}
/// <summary>
/// The mode used to determine the duration of the Timeline
/// </summary>
public DurationMode durationMode
{
get { return m_DurationMode ; }
set { m_DurationMode = value ; }
}
/// <summary>
/// A description of the PlayableOutputs that will be created by the timeline when instantiated.
/// </summary>
/// <remarks>
/// Each track will create an PlayableOutput
/// </remarks>
public override IEnumerable < PlayableBinding > outputs
{
get
{
foreach ( var outputTracks in GetOutputTracks ( ) )
foreach ( var output in outputTracks . outputs )
yield return output ;
}
}
2022-01-12 10:39:15 +03:00
/// <summary>
/// The capabilities supported by all clips in the timeline.
/// </summary>
2022-01-12 10:06:03 +03:00
public ClipCaps clipCaps
{
get
{
var caps = ClipCaps . All ;
foreach ( var track in GetRootTracks ( ) )
{
foreach ( var clip in track . clips )
caps & = clip . clipCaps ;
}
return caps ;
}
}
/// <summary>
/// Returns the the number of output tracks in the Timeline.
/// </summary>
/// <remarks>
/// An output track is a track the generates a PlayableOutput. In general, an output track is any track that is not a GroupTrack, a subtrack, or override track.
/// </remarks>
public int outputTrackCount
{
get
{
UpdateOutputTrackCache ( ) ; // updates the cache if necessary
return m_CacheOutputTracks . Length ;
}
}
/// <summary>
/// Returns the number of tracks at the root level of the timeline.
/// </summary>
/// <remarks>
/// A root track refers to all tracks that occur at the root of the timeline. These are the outmost level GroupTracks, and output tracks that do not belong to any group
/// </remarks>
public int rootTrackCount
{
get
{
UpdateRootTrackCache ( ) ;
return m_CacheRootTracks . Count ;
}
}
void OnValidate ( )
{
editorSettings . fps = GetValidFramerate ( editorSettings . fps ) ;
}
2022-01-12 10:39:15 +03:00
internal static float GetValidFramerate ( float framerate )
2022-01-12 10:06:03 +03:00
{
return Mathf . Clamp ( framerate , EditorSettings . kMinFps , EditorSettings . kMaxFps ) ;
}
/// <summary>
/// Retrieves at root track at the specified index.
/// </summary>
/// <param name="index">Index of the root track to get. Must be between 0 and rootTrackCount</param>
/// <remarks>
/// A root track refers to all tracks that occur at the root of the timeline. These are the outmost level GroupTracks, and output tracks that do not belong to any group.
/// </remarks>
2022-01-12 10:39:15 +03:00
/// <returns>Root track at the specified index.</returns>
2022-01-12 10:06:03 +03:00
public TrackAsset GetRootTrack ( int index )
{
UpdateRootTrackCache ( ) ;
return m_CacheRootTracks [ index ] ;
}
/// <summary>
/// Get an enumerable list of all root tracks.
/// </summary>
/// <returns>An IEnumerable of all root tracks.</returns>
/// <remarks>A root track refers to all tracks that occur at the root of the timeline. These are the outmost level GroupTracks, and output tracks that do not belong to any group.</remarks>
public IEnumerable < TrackAsset > GetRootTracks ( )
{
UpdateRootTrackCache ( ) ;
return m_CacheRootTracks ;
}
/// <summary>
/// Retrives the output track from the given index.
/// </summary>
/// <param name="index">Index of the output track to retrieve. Must be between 0 and outputTrackCount</param>
/// <returns>The output track from the given index</returns>
public TrackAsset GetOutputTrack ( int index )
{
UpdateOutputTrackCache ( ) ;
return m_CacheOutputTracks [ index ] ;
}
/// <summary>
/// Gets a list of all output tracks in the Timeline.
/// </summary>
/// <returns>An IEnumerable of all output tracks</returns>
/// <remarks>
/// An output track is a track the generates a PlayableOutput. In general, an output track is any track that is not a GroupTrack or subtrack.
/// </remarks>
public IEnumerable < TrackAsset > GetOutputTracks ( )
{
UpdateOutputTrackCache ( ) ;
return m_CacheOutputTracks ;
}
void UpdateRootTrackCache ( )
{
if ( m_CacheRootTracks = = null )
{
if ( m_Tracks = = null )
m_CacheRootTracks = new List < TrackAsset > ( ) ;
else
{
m_CacheRootTracks = new List < TrackAsset > ( m_Tracks . Count ) ;
if ( markerTrack ! = null )
{
m_CacheRootTracks . Add ( markerTrack ) ;
}
foreach ( var t in m_Tracks )
{
var trackAsset = t as TrackAsset ;
if ( trackAsset ! = null )
m_CacheRootTracks . Add ( trackAsset ) ;
}
}
}
}
void UpdateOutputTrackCache ( )
{
if ( m_CacheOutputTracks = = null )
{
var outputTracks = new List < TrackAsset > ( ) ;
foreach ( var flattenedTrack in flattenedTracks )
{
if ( flattenedTrack ! = null & & flattenedTrack . GetType ( ) ! = typeof ( GroupTrack ) & & ! flattenedTrack . isSubTrack )
outputTracks . Add ( flattenedTrack ) ;
}
m_CacheOutputTracks = outputTracks . ToArray ( ) ;
}
}
internal IEnumerable < TrackAsset > flattenedTracks
{
get
{
if ( m_CacheFlattenedTracks = = null )
{
m_CacheFlattenedTracks = new List < TrackAsset > ( m_Tracks . Count * 2 ) ;
UpdateRootTrackCache ( ) ;
m_CacheFlattenedTracks . AddRange ( m_CacheRootTracks ) ;
for ( int i = 0 ; i < m_CacheRootTracks . Count ; i + + )
{
AddSubTracksRecursive ( m_CacheRootTracks [ i ] , ref m_CacheFlattenedTracks ) ;
}
}
return m_CacheFlattenedTracks ;
}
}
/// <summary>
/// Gets the marker track for this TimelineAsset.
/// </summary>
/// <returns>Returns the marker track.</returns>
/// <remarks>
/// Use <see cref="TrackAsset.GetMarkers"/> to get a list of the markers on the returned track.
/// </remarks>
public MarkerTrack markerTrack
{
get { return m_MarkerTrack ; }
}
// access to the track list as scriptable object
internal List < ScriptableObject > trackObjects
{
get { return m_Tracks ; }
}
internal void AddTrackInternal ( TrackAsset track )
{
m_Tracks . Add ( track ) ;
track . parent = this ;
Invalidate ( ) ;
}
internal void RemoveTrack ( TrackAsset track )
{
m_Tracks . Remove ( track ) ;
Invalidate ( ) ;
var parentTrack = track . parent as TrackAsset ;
if ( parentTrack ! = null )
{
parentTrack . RemoveSubTrack ( track ) ;
}
}
/// <summary>
/// Creates an instance of the timeline
/// </summary>
/// <param name="graph">PlayableGraph that will own the playable</param>
/// <param name="go">The gameobject that triggered the graph build</param>
/// <returns>The Root Playable of the Timeline</returns>
public override Playable CreatePlayable ( PlayableGraph graph , GameObject go )
{
bool autoRebalanceTree = false ;
#if UNITY_EDITOR
autoRebalanceTree = true ;
#endif
// only create outputs if we are not nested
bool createOutputs = graph . GetPlayableCount ( ) = = 0 ;
var timeline = TimelinePlayable . Create ( graph , GetOutputTracks ( ) , go , autoRebalanceTree , createOutputs ) ;
timeline . SetPropagateSetTime ( true ) ;
return timeline . IsValid ( ) ? timeline : Playable . Null ;
}
2022-01-12 10:39:15 +03:00
/// <summary>
/// Called before Unity serializes this object.
/// </summary>
2022-01-12 10:06:03 +03:00
void ISerializationCallbackReceiver . OnBeforeSerialize ( )
{
m_Version = k_LatestVersion ;
}
2022-01-12 10:39:15 +03:00
/// <summary>
/// Called after Unity deserializes this object.
/// </summary>
2022-01-12 10:06:03 +03:00
void ISerializationCallbackReceiver . OnAfterDeserialize ( )
{
2022-01-12 10:39:15 +03:00
// resets cache on an Undo
2022-01-12 10:06:03 +03:00
Invalidate ( ) ; // resets cache on an Undo
if ( m_Version < k_LatestVersion )
{
UpgradeToLatestVersion ( ) ;
}
}
void __internalAwake ( )
{
if ( m_Tracks = = null )
m_Tracks = new List < ScriptableObject > ( ) ;
2022-01-12 10:39:15 +03:00
#if UNITY_EDITOR
// case 1280331 -- embedding the timeline asset inside a prefab will create a temporary non-persistent version of an asset
// setting the track parents to this will change persistent tracks
if ( ! UnityEditor . EditorUtility . IsPersistent ( this ) )
return ;
#endif
2022-01-12 10:06:03 +03:00
// validate the array. DON'T remove Unity null objects, just actual null objects
for ( int i = m_Tracks . Count - 1 ; i > = 0 ; i - - )
{
TrackAsset asset = m_Tracks [ i ] as TrackAsset ;
if ( asset ! = null )
asset . parent = this ;
#if UNITY_EDITOR
object o = m_Tracks [ i ] ;
if ( o = = null )
{
Debug . LogWarning ( "Empty track found while loading timeline. It will be removed." ) ;
m_Tracks . RemoveAt ( i ) ;
}
#endif
}
}
/// <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 void GatherProperties ( PlayableDirector director , IPropertyCollector driver )
{
var outputTracks = GetOutputTracks ( ) ;
foreach ( var track in outputTracks )
{
2022-01-12 10:39:15 +03:00
if ( ! track . mutedInHierarchy )
track . GatherProperties ( director , driver ) ;
2022-01-12 10:06:03 +03:00
}
}
/// <summary>
/// Creates a marker track for the TimelineAsset.
/// </summary>
/// In the editor, the marker track appears under the Timeline ruler.
/// <remarks>
/// This track is always bound to the GameObject that contains the PlayableDirector component for the current timeline.
/// The marker track is created the first time this method is called. If the marker track is already created, this method does nothing.
/// </remarks>
public void CreateMarkerTrack ( )
{
if ( m_MarkerTrack = = null )
{
m_MarkerTrack = CreateInstance < MarkerTrack > ( ) ;
TimelineCreateUtilities . SaveAssetIntoObject ( m_MarkerTrack , this ) ;
m_MarkerTrack . parent = this ;
m_MarkerTrack . name = "Markers" ; // This name will show up in the bindings list if it contains signals
Invalidate ( ) ;
}
}
// Invalidates the asset, call this if changing the asset data
internal void Invalidate ( )
{
m_CacheRootTracks = null ;
m_CacheOutputTracks = null ;
m_CacheFlattenedTracks = null ;
}
2022-01-12 10:39:15 +03:00
internal void UpdateFixedDurationWithItemsDuration ( )
{
m_FixedDuration = ( double ) CalculateItemsDuration ( ) ;
}
DiscreteTime CalculateItemsDuration ( )
2022-01-12 10:06:03 +03:00
{
var discreteDuration = new DiscreteTime ( 0 ) ;
foreach ( var track in flattenedTracks )
{
if ( track . muted )
continue ;
discreteDuration = DiscreteTime . Max ( discreteDuration , ( DiscreteTime ) track . end ) ;
}
if ( discreteDuration < = 0 )
2022-01-12 10:39:15 +03:00
return new DiscreteTime ( 0 ) ;
2022-01-12 10:06:03 +03:00
2022-01-12 10:39:15 +03:00
return discreteDuration ;
2022-01-12 10:06:03 +03:00
}
static void AddSubTracksRecursive ( TrackAsset track , ref List < TrackAsset > allTracks )
{
if ( track = = null )
return ;
allTracks . AddRange ( track . GetChildTracks ( ) ) ;
foreach ( TrackAsset subTrack in track . GetChildTracks ( ) )
{
AddSubTracksRecursive ( subTrack , ref allTracks ) ;
}
}
}
}