Maze / Library /PackageCache /com.unity.timeline@1.6.5 /Runtime /Playables /DirectorControlPlayable.cs
| using System; | |
| using UnityEngine; | |
| using UnityEngine.Playables; | |
| namespace UnityEngine.Timeline | |
| { | |
| /// <summary> | |
| /// Playable Behaviour used to control a PlayableDirector. | |
| /// </summary> | |
| /// <remarks> | |
| /// This playable is used to control other PlayableDirector components from a Timeline sequence. | |
| /// </remarks> | |
| public class DirectorControlPlayable : PlayableBehaviour | |
| { | |
| /// <summary> | |
| /// The PlayableDirector being controlled by this PlayableBehaviour | |
| /// </summary> | |
| public PlayableDirector director; | |
| private bool m_SyncTime = false; | |
| private double m_AssetDuration = double.MaxValue; | |
| /// <summary> | |
| /// Creates a Playable with a DirectorControlPlayable attached | |
| /// </summary> | |
| /// <param name="graph">The graph to inject the playable into</param> | |
| /// <param name="director">The director to control</param> | |
| /// <returns>Returns a Playable with a DirectorControlPlayable attached</returns> | |
| public static ScriptPlayable<DirectorControlPlayable> Create(PlayableGraph graph, PlayableDirector director) | |
| { | |
| if (director == null) | |
| return ScriptPlayable<DirectorControlPlayable>.Null; | |
| var handle = ScriptPlayable<DirectorControlPlayable>.Create(graph); | |
| handle.GetBehaviour().director = director; | |
| if (!Application.isPlaying && UnityEditor.PrefabUtility.IsPartOfPrefabInstance(director)) | |
| UnityEditor.PrefabUtility.prefabInstanceUpdated += handle.GetBehaviour().OnPrefabUpdated; | |
| return handle; | |
| } | |
| /// <summary> | |
| /// This function is called when this PlayableBehaviour is destroyed. | |
| /// </summary> | |
| /// <param name="playable">The Playable that owns the current PlayableBehaviour.</param> | |
| public override void OnPlayableDestroy(Playable playable) | |
| { | |
| if (!Application.isPlaying) | |
| UnityEditor.PrefabUtility.prefabInstanceUpdated -= OnPrefabUpdated; | |
| if (director != null && director.playableAsset != null) | |
| director.Stop(); | |
| } | |
| /// <summary> | |
| /// This function is called during the PrepareFrame phase of the PlayableGraph. | |
| /// </summary> | |
| /// <param name="playable">The Playable that owns the current PlayableBehaviour.</param> | |
| /// <param name="info">A FrameData structure that contains information about the current frame context.</param> | |
| public override void PrepareFrame(Playable playable, FrameData info) | |
| { | |
| if (director == null || !director.isActiveAndEnabled || director.playableAsset == null) | |
| return; | |
| // resync the time on an evaluate or a time jump (caused by loops, or some setTime calls) | |
| m_SyncTime |= (info.evaluationType == FrameData.EvaluationType.Evaluate) || | |
| DetectDiscontinuity(playable, info); | |
| SyncSpeed(info.effectiveSpeed); | |
| SyncStart(playable.GetGraph(), playable.GetTime()); | |
| SyncStop(playable.GetGraph(), playable.GetTime()); | |
| } | |
| /// <summary> | |
| /// This function is called when the Playable play state is changed to Playables.PlayState.Playing. | |
| /// </summary> | |
| /// <param name="playable">The Playable that owns the current PlayableBehaviour.</param> | |
| /// <param name="info">A FrameData structure that contains information about the current frame context.</param> | |
| public override void OnBehaviourPlay(Playable playable, FrameData info) | |
| { | |
| m_SyncTime = true; | |
| if (director != null && director.playableAsset != null) | |
| m_AssetDuration = director.playableAsset.duration; | |
| } | |
| /// <summary> | |
| /// This function is called when the Playable play state is changed to PlayState.Paused. | |
| /// </summary> | |
| /// <param name="playable">The playable this behaviour is attached to.</param> | |
| /// <param name="info">A FrameData structure that contains information about the current frame context.</param> | |
| public override void OnBehaviourPause(Playable playable, FrameData info) | |
| { | |
| if (director != null && director.playableAsset != null) | |
| { | |
| if (info.effectivePlayState == PlayState.Playing) // graph was paused | |
| director.Pause(); | |
| else | |
| director.Stop(); | |
| } | |
| } | |
| /// <summary> | |
| /// This function is called during the ProcessFrame phase of the PlayableGraph. | |
| /// </summary> | |
| /// <param name="playable">The playable this behaviour is attached to.</param> | |
| /// <param name="info">A FrameData structure that contains information about the current frame context.</param> | |
| /// <param name="playerData">unused</param> | |
| public override void ProcessFrame(Playable playable, FrameData info, object playerData) | |
| { | |
| if (director == null || !director.isActiveAndEnabled || director.playableAsset == null) | |
| return; | |
| if (m_SyncTime || DetectOutOfSync(playable)) | |
| { | |
| UpdateTime(playable); | |
| if (director.playableGraph.IsValid()) | |
| { | |
| director.playableGraph.Evaluate(); | |
| director.playableGraph.SynchronizeEvaluation(playable.GetGraph()); | |
| } | |
| else | |
| { | |
| director.Evaluate(); | |
| } | |
| } | |
| m_SyncTime = false; | |
| SyncStop(playable.GetGraph(), playable.GetTime()); | |
| } | |
| void OnPrefabUpdated(GameObject go) | |
| { | |
| // When the prefab asset is updated, we rebuild the graph to reflect the changes in editor | |
| if (UnityEditor.PrefabUtility.GetRootGameObject(director) == go) | |
| director.RebuildGraph(); | |
| } | |
| void SyncSpeed(double speed) | |
| { | |
| if (director.playableGraph.IsValid()) | |
| { | |
| int roots = director.playableGraph.GetRootPlayableCount(); | |
| for (int i = 0; i < roots; i++) | |
| { | |
| var rootPlayable = director.playableGraph.GetRootPlayable(i); | |
| if (rootPlayable.IsValid()) | |
| { | |
| rootPlayable.SetSpeed(speed); | |
| } | |
| } | |
| } | |
| } | |
| void SyncStart(PlayableGraph graph, double time) | |
| { | |
| if (director.state == PlayState.Playing | |
| || !graph.IsPlaying() | |
| || (director.extrapolationMode == DirectorWrapMode.None && time > m_AssetDuration)) | |
| return; | |
| if (graph.IsMatchFrameRateEnabled()) | |
| director.Play(graph.GetFrameRate()); | |
| else | |
| director.Play(); | |
| director.Play(); | |
| } | |
| void SyncStop(PlayableGraph graph, double time) | |
| { | |
| if (director.state == PlayState.Paused) | |
| return; | |
| bool expectedFinished = director.extrapolationMode == DirectorWrapMode.None && time > m_AssetDuration; | |
| if (expectedFinished || !graph.IsPlaying()) | |
| director.Pause(); | |
| } | |
| bool DetectDiscontinuity(Playable playable, FrameData info) | |
| { | |
| return Math.Abs(playable.GetTime() - playable.GetPreviousTime() - info.m_DeltaTime * info.m_EffectiveSpeed) > DiscreteTime.tickValue; | |
| } | |
| bool DetectOutOfSync(Playable playable) | |
| { | |
| double expectedTime = playable.GetTime(); | |
| if (playable.GetTime() >= m_AssetDuration) | |
| { | |
| switch (director.extrapolationMode) | |
| { | |
| case DirectorWrapMode.None: | |
| expectedTime = m_AssetDuration; | |
| break; | |
| case DirectorWrapMode.Hold: | |
| expectedTime = m_AssetDuration; | |
| break; | |
| case DirectorWrapMode.Loop: | |
| expectedTime %= m_AssetDuration; | |
| break; | |
| } | |
| } | |
| if (!Mathf.Approximately((float)expectedTime, (float)director.time)) | |
| { | |
| double lastDelta = playable.GetTime() - playable.GetPreviousTime(); | |
| if (UnityEditor.Unsupported.IsDeveloperBuild()) | |
| Debug.LogWarningFormat("Internal Warning - Control track desync detected on {2} ({0:F10} vs {1:F10} with delta {3:F10}). Time will be resynchronized. Known to happen with nested control tracks", playable.GetTime(), director.time, director.name, lastDelta); | |
| return true; | |
| } | |
| return false; | |
| } | |
| // We need to handle loop modes explicitly since we are setting the time directly | |
| void UpdateTime(Playable playable) | |
| { | |
| double duration = Math.Max(0.1, director.playableAsset.duration); | |
| switch (director.extrapolationMode) | |
| { | |
| case DirectorWrapMode.Hold: | |
| director.time = Math.Min(duration, Math.Max(0, playable.GetTime())); | |
| break; | |
| case DirectorWrapMode.Loop: | |
| director.time = Math.Max(0, playable.GetTime() % duration); | |
| break; | |
| case DirectorWrapMode.None: | |
| director.time = Math.Min(duration, Math.Max(0, playable.GetTime())); | |
| break; | |
| } | |
| } | |
| } | |
| } | |