A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using UnityEngine.Playables;
4
5namespace UnityEngine.Timeline
6{
7 /// <summary>
8 /// A PlayableAsset that represents a timeline.
9 /// </summary>
10 [ExcludeFromPreset]
11 [Serializable]
12 [TimelineHelpURL(typeof(TimelineAsset))]
13 public partial class TimelineAsset : PlayableAsset, ISerializationCallbackReceiver, ITimelineClipAsset, IPropertyPreview
14 {
15 /// <summary>
16 /// How the duration of the timeline is determined.
17 /// </summary>
18 public enum DurationMode
19 {
20 /// <summary>
21 /// The duration of the timeline is determined based on the clips present.
22 /// </summary>
23 BasedOnClips,
24 /// <summary>
25 /// The duration of the timeline is a fixed length.
26 /// </summary>
27 FixedLength
28 }
29
30 /// <summary>
31 /// Properties of the timeline that are used by the editor
32 /// </summary>
33 [Serializable]
34 public class EditorSettings
35 {
36 internal static readonly double kMinFrameRate = TimeUtility.kFrameRateEpsilon;
37 internal static readonly double kMaxFrameRate = 1000.0;
38 internal static readonly double kDefaultFrameRate = 60.0;
39 [HideInInspector, SerializeField, FrameRateField] double m_Framerate = kDefaultFrameRate;
40 [HideInInspector, SerializeField] bool m_ScenePreview = true;
41
42 /// <summary>
43 /// The frames per second used for snapping and time ruler display
44 /// </summary>
45 [Obsolete("EditorSettings.fps has been deprecated. Use editorSettings.frameRate instead.", false)]
46 public float fps
47 {
48 get
49 {
50 return (float)m_Framerate;
51 }
52 set
53 {
54 m_Framerate = Mathf.Clamp(value, (float)kMinFrameRate, (float)kMaxFrameRate);
55 }
56 }
57
58 /// <summary>
59 /// The frames per second used for framelocked preview, frame snapping and time ruler display,
60 /// </summary>
61 /// <remarks>
62 /// If frameRate is set to a non-standard custom frame rate, Timeline playback
63 /// may give incorrect results when playbackLockedToFrame is true.
64 /// </remarks>
65 /// <seealso cref="UnityEngine.Timeline.TimelineAsset"/>
66 public double frameRate
67 {
68 get { return m_Framerate; }
69 set { m_Framerate = GetValidFrameRate(value); }
70 }
71
72 /// <summary>
73 /// Sets the EditorSetting frameRate to one of the provided standard frame rates.
74 /// </summary>
75 /// <param name="enumValue"> StandardFrameRates value, used to set the current EditorSettings frameRate value.</param>
76 /// <remarks>
77 /// When specifying drop frame values, it is recommended to select one of the provided standard frame rates.
78 /// Specifying a non-standard custom frame rate may give incorrect results when playbackLockedToFrame
79 /// is enabled during Timeline playback.
80 /// </remarks>
81 /// <exception cref="ArgumentException">Thrown when the enumValue is not a valid member of StandardFrameRates.</exception>
82 /// <seealso cref="UnityEngine.Timeline.TimelineAsset"/>
83 public void SetStandardFrameRate(StandardFrameRates enumValue)
84 {
85 FrameRate rate = TimeUtility.ToFrameRate(enumValue);
86 if (rate.IsValid())
87 throw new ArgumentException(String.Format("StandardFrameRates {0}, is not defined",
88 enumValue.ToString()));
89 m_Framerate = rate.rate;
90 }
91
92 /// <summary>
93 /// Set to false to ignore scene preview when this timeline is played by the Timeline window.
94 /// </summary>
95 /// <remarks>
96 /// When set to false, this setting will
97 /// - Disable scene preview when this timeline is played by the Timeline window.
98 /// - Disable recording for all recordable tracks.
99 /// - Disable play range in the Timeline window.
100 /// - `Stop()` is not called on the `PlayableDirector` when switching between different `TimelineAsset`s in the TimelineWindow.
101 ///
102 /// `scenePreview` will only be applied if the asset is the master timeline.
103 /// </remarks>
104 /// <seealso cref="UnityEngine.Timeline.TimelineAsset"/>
105 public bool scenePreview
106 {
107 get => m_ScenePreview;
108 set => m_ScenePreview = value;
109 }
110 }
111
112 [HideInInspector, SerializeField] List<ScriptableObject> m_Tracks;
113 [HideInInspector, SerializeField] double m_FixedDuration; // only applied if duration mode is Fixed
114 [HideInInspector, NonSerialized] TrackAsset[] m_CacheOutputTracks;
115 [HideInInspector, NonSerialized] List<TrackAsset> m_CacheRootTracks;
116 [HideInInspector, NonSerialized] TrackAsset[] m_CacheFlattenedTracks;
117 [HideInInspector, SerializeField] EditorSettings m_EditorSettings = new EditorSettings();
118 [SerializeField] DurationMode m_DurationMode;
119
120 [HideInInspector, SerializeField] MarkerTrack m_MarkerTrack;
121
122 /// <summary>
123 /// Settings used by timeline for editing purposes
124 /// </summary>
125 public EditorSettings editorSettings
126 {
127 get { return m_EditorSettings; }
128 }
129
130 /// <summary>
131 /// The length, in seconds, of the timeline
132 /// </summary>
133 public override double duration
134 {
135 get
136 {
137 // @todo cache this value when rebuilt
138 if (m_DurationMode == DurationMode.BasedOnClips)
139 {
140 //avoid having no clip evaluated at the end by removing a tick from the total duration
141 var discreteDuration = CalculateItemsDuration();
142 if (discreteDuration <= 0)
143 return 0.0;
144 return (double)discreteDuration.OneTickBefore();
145 }
146
147 return m_FixedDuration;
148 }
149 }
150
151 /// <summary>
152 /// The length of the timeline when durationMode is set to fixed length.
153 /// </summary>
154 public double fixedDuration
155 {
156 get
157 {
158 DiscreteTime discreteDuration = (DiscreteTime)m_FixedDuration;
159 if (discreteDuration <= 0)
160 return 0.0;
161
162 //avoid having no clip evaluated at the end by removing a tick from the total duration
163 return (double)discreteDuration.OneTickBefore();
164 }
165 set { m_FixedDuration = Math.Max(0.0, value); }
166 }
167
168 /// <summary>
169 /// The mode used to determine the duration of the Timeline
170 /// </summary>
171 public DurationMode durationMode
172 {
173 get { return m_DurationMode; }
174 set { m_DurationMode = value; }
175 }
176
177 /// <summary>
178 /// A description of the PlayableOutputs that will be created by the timeline when instantiated.
179 /// </summary>
180 /// <remarks>
181 /// Each track will create an PlayableOutput
182 /// </remarks>
183 public override IEnumerable<PlayableBinding> outputs
184 {
185 get
186 {
187 foreach (var outputTracks in GetOutputTracks())
188 foreach (var output in outputTracks.outputs)
189 yield return output;
190 }
191 }
192
193 /// <summary>
194 /// The capabilities supported by all clips in the timeline.
195 /// </summary>
196 public ClipCaps clipCaps
197 {
198 get
199 {
200 var caps = ClipCaps.All;
201 foreach (var track in GetRootTracks())
202 {
203 foreach (var clip in track.clips)
204 caps &= clip.clipCaps;
205 }
206 return caps;
207 }
208 }
209
210 /// <summary>
211 /// Returns the the number of output tracks in the Timeline.
212 /// </summary>
213 /// <remarks>
214 /// 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.
215 /// </remarks>
216 public int outputTrackCount
217 {
218 get
219 {
220 UpdateOutputTrackCache(); // updates the cache if necessary
221 return m_CacheOutputTracks.Length;
222 }
223 }
224
225 /// <summary>
226 /// Returns the number of tracks at the root level of the timeline.
227 /// </summary>
228 /// <remarks>
229 /// 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
230 /// </remarks>
231 public int rootTrackCount
232 {
233 get
234 {
235 UpdateRootTrackCache();
236 return m_CacheRootTracks.Count;
237 }
238 }
239
240 void OnValidate()
241 {
242 editorSettings.frameRate = GetValidFrameRate(editorSettings.frameRate);
243 }
244
245 /// <summary>
246 /// Retrieves at root track at the specified index.
247 /// </summary>
248 /// <param name="index">Index of the root track to get. Must be between 0 and rootTrackCount</param>
249 /// <remarks>
250 /// 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.
251 /// </remarks>
252 /// <returns>Root track at the specified index.</returns>
253 public TrackAsset GetRootTrack(int index)
254 {
255 UpdateRootTrackCache();
256 return m_CacheRootTracks[index];
257 }
258
259 /// <summary>
260 /// Get an enumerable list of all root tracks.
261 /// </summary>
262 /// <returns>An IEnumerable of all root tracks.</returns>
263 /// <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>
264 public IEnumerable<TrackAsset> GetRootTracks()
265 {
266 UpdateRootTrackCache();
267 return m_CacheRootTracks;
268 }
269
270 /// <summary>
271 /// Retrives the output track from the given index.
272 /// </summary>
273 /// <param name="index">Index of the output track to retrieve. Must be between 0 and outputTrackCount</param>
274 /// <returns>The output track from the given index</returns>
275 public TrackAsset GetOutputTrack(int index)
276 {
277 UpdateOutputTrackCache();
278 return m_CacheOutputTracks[index];
279 }
280
281 /// <summary>
282 /// Gets a list of all output tracks in the Timeline.
283 /// </summary>
284 /// <returns>An IEnumerable of all output tracks</returns>
285 /// <remarks>
286 /// 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.
287 /// </remarks>
288 public IEnumerable<TrackAsset> GetOutputTracks()
289 {
290 UpdateOutputTrackCache();
291 return m_CacheOutputTracks;
292 }
293
294 static double GetValidFrameRate(double frameRate)
295 {
296 return Math.Min(Math.Max(frameRate, EditorSettings.kMinFrameRate), EditorSettings.kMaxFrameRate);
297 }
298
299 void UpdateRootTrackCache()
300 {
301 if (m_CacheRootTracks == null)
302 {
303 if (m_Tracks == null)
304 m_CacheRootTracks = new List<TrackAsset>();
305 else
306 {
307 m_CacheRootTracks = new List<TrackAsset>(m_Tracks.Count);
308 if (markerTrack != null)
309 {
310 m_CacheRootTracks.Add(markerTrack);
311 }
312
313 foreach (var t in m_Tracks)
314 {
315 var trackAsset = t as TrackAsset;
316 if (trackAsset != null)
317 m_CacheRootTracks.Add(trackAsset);
318 }
319 }
320 }
321 }
322
323 void UpdateOutputTrackCache()
324 {
325 if (m_CacheOutputTracks == null)
326 {
327 var outputTracks = new List<TrackAsset>();
328 foreach (var flattenedTrack in flattenedTracks)
329 {
330 if (flattenedTrack != null && flattenedTrack.GetType() != typeof(GroupTrack) && !flattenedTrack.isSubTrack)
331 outputTracks.Add(flattenedTrack);
332 }
333 m_CacheOutputTracks = outputTracks.ToArray();
334 }
335 }
336
337 internal TrackAsset[] flattenedTracks
338 {
339 get
340 {
341 if (m_CacheFlattenedTracks == null)
342 {
343 var list = new List<TrackAsset>(m_Tracks.Count * 2);
344 UpdateRootTrackCache();
345
346 list.AddRange(m_CacheRootTracks);
347 for (int i = 0; i < m_CacheRootTracks.Count; i++)
348 {
349 AddSubTracksRecursive(m_CacheRootTracks[i], ref list);
350 }
351
352 m_CacheFlattenedTracks = list.ToArray();
353 }
354 return m_CacheFlattenedTracks;
355 }
356 }
357
358 /// <summary>
359 /// Gets the marker track for this TimelineAsset.
360 /// </summary>
361 /// <returns>Returns the marker track.</returns>
362 /// <remarks>
363 /// Use <see cref="TrackAsset.GetMarkers"/> to get a list of the markers on the returned track.
364 /// </remarks>
365 public MarkerTrack markerTrack
366 {
367 get { return m_MarkerTrack; }
368 }
369
370 // access to the track list as scriptable object
371 internal List<ScriptableObject> trackObjects
372 {
373 get { return m_Tracks; }
374 }
375
376 internal void AddTrackInternal(TrackAsset track)
377 {
378 m_Tracks.Add(track);
379 track.parent = this;
380 Invalidate();
381 }
382
383 internal void RemoveTrack(TrackAsset track)
384 {
385 m_Tracks.Remove(track);
386 Invalidate();
387 var parentTrack = track.parent as TrackAsset;
388 if (parentTrack != null)
389 {
390 parentTrack.RemoveSubTrack(track);
391 }
392 }
393
394 /// <summary>
395 /// Creates an instance of the timeline
396 /// </summary>
397 /// <param name="graph">PlayableGraph that will own the playable</param>
398 /// <param name="go">The gameobject that triggered the graph build</param>
399 /// <returns>The Root Playable of the Timeline</returns>
400 public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
401 {
402 bool autoRebalanceTree = false;
403#if UNITY_EDITOR
404 autoRebalanceTree = true;
405#endif
406
407 // only create outputs if we are not nested
408 bool createOutputs = graph.GetPlayableCount() == 0;
409 var timeline = TimelinePlayable.Create(graph, GetOutputTracks(), go, autoRebalanceTree, createOutputs);
410 timeline.SetDuration(this.duration);
411 timeline.SetPropagateSetTime(true);
412 return timeline.IsValid() ? timeline : Playable.Null;
413 }
414
415 /// <summary>
416 /// Called before Unity serializes this object.
417 /// </summary>
418 void ISerializationCallbackReceiver.OnBeforeSerialize()
419 {
420 m_Version = k_LatestVersion;
421 }
422
423 /// <summary>
424 /// Called after Unity deserializes this object.
425 /// </summary>
426 void ISerializationCallbackReceiver.OnAfterDeserialize()
427 {
428 // resets cache on an Undo
429 Invalidate(); // resets cache on an Undo
430 if (m_Version < k_LatestVersion)
431 {
432 UpgradeToLatestVersion();
433 }
434 }
435
436#if UNITY_EDITOR
437 internal event Action AssetModifiedOnDisk;
438#endif
439 void __internalAwake()
440 {
441 if (m_Tracks == null)
442 m_Tracks = new List<ScriptableObject>();
443
444#if UNITY_EDITOR
445 // case 1280331 -- embedding the timeline asset inside a prefab will create a temporary non-persistent version of an asset
446 // setting the track parents to this will change persistent tracks
447 if (!UnityEditor.EditorUtility.IsPersistent(this))
448 return;
449#endif
450
451 // validate the array. DON'T remove Unity null objects, just actual null objects
452 for (int i = m_Tracks.Count - 1; i >= 0; i--)
453 {
454 TrackAsset asset = m_Tracks[i] as TrackAsset;
455 if (asset != null)
456 asset.parent = this;
457#if UNITY_EDITOR
458 object o = m_Tracks[i];
459 if (o == null)
460 {
461 Debug.LogWarning("Empty track found while loading timeline. It will be removed.");
462 m_Tracks.RemoveAt(i);
463 }
464#endif
465 }
466
467#if UNITY_EDITOR
468 AssetModifiedOnDisk?.Invoke();
469#endif
470 }
471
472 /// <summary>
473 /// Called by the Timeline Editor to gather properties requiring preview.
474 /// </summary>
475 /// <param name="director">The PlayableDirector invoking the preview</param>
476 /// <param name="driver">PropertyCollector used to gather previewable properties</param>
477 public void GatherProperties(PlayableDirector director, IPropertyCollector driver)
478 {
479 var outputTracks = GetOutputTracks();
480 foreach (var track in outputTracks)
481 {
482 if (!track.mutedInHierarchy)
483 track.GatherProperties(director, driver);
484 }
485 }
486
487 /// <summary>
488 /// Creates a marker track for the TimelineAsset.
489 /// </summary>
490 /// In the editor, the marker track appears under the Timeline ruler.
491 /// <remarks>
492 /// This track is always bound to the GameObject that contains the PlayableDirector component for the current timeline.
493 /// The marker track is created the first time this method is called. If the marker track is already created, this method does nothing.
494 /// </remarks>
495 public void CreateMarkerTrack()
496 {
497 if (m_MarkerTrack == null)
498 {
499 m_MarkerTrack = CreateInstance<MarkerTrack>();
500 TimelineCreateUtilities.SaveAssetIntoObject(m_MarkerTrack, this);
501 m_MarkerTrack.parent = this;
502 m_MarkerTrack.name = "Markers"; // This name will show up in the bindings list if it contains signals
503 Invalidate();
504 }
505 }
506
507 internal void RemoveMarkerTrack()
508 {
509 if (m_MarkerTrack != null)
510 {
511 MarkerTrack markerTrack = m_MarkerTrack;
512 m_MarkerTrack = null;
513 TimelineCreateUtilities.RemoveAssetFromObject(markerTrack, this);
514 Invalidate();
515 }
516 }
517
518 // Invalidates the asset, call this if changing the asset data
519 internal void Invalidate()
520 {
521 m_CacheRootTracks = null;
522 m_CacheOutputTracks = null;
523 m_CacheFlattenedTracks = null;
524 }
525
526 internal void UpdateFixedDurationWithItemsDuration()
527 {
528 m_FixedDuration = (double)CalculateItemsDuration();
529 }
530
531 DiscreteTime CalculateItemsDuration()
532 {
533 var discreteDuration = new DiscreteTime(0);
534 foreach (var track in flattenedTracks)
535 {
536 if (track.muted)
537 continue;
538
539 discreteDuration = DiscreteTime.Max(discreteDuration, (DiscreteTime)track.end);
540 }
541
542 if (discreteDuration <= 0)
543 return new DiscreteTime(0);
544
545 return discreteDuration;
546 }
547
548 static void AddSubTracksRecursive(TrackAsset track, ref List<TrackAsset> allTracks)
549 {
550 if (track == null)
551 return;
552
553 allTracks.AddRange(track.GetChildTracks());
554 foreach (TrackAsset subTrack in track.GetChildTracks())
555 {
556 AddSubTracksRecursive(subTrack, ref allTracks);
557 }
558 }
559 }
560}