File size: 9,891 Bytes
18a519f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 | 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 UNITY_EDITOR
if (!Application.isPlaying && UnityEditor.PrefabUtility.IsPartOfPrefabInstance(director))
UnityEditor.PrefabUtility.prefabInstanceUpdated += handle.GetBehaviour().OnPrefabUpdated;
#endif
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 UNITY_EDITOR
if (!Application.isPlaying)
UnityEditor.PrefabUtility.prefabInstanceUpdated -= OnPrefabUpdated;
#endif
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());
#if !UNITY_2021_2_OR_NEWER
SyncStop(playable.GetGraph(), playable.GetTime());
#endif
}
/// <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();
#if TIMELINE_FRAMEACCURATE
director.playableGraph.SynchronizeEvaluation(playable.GetGraph());
#endif
}
else
{
director.Evaluate();
}
}
m_SyncTime = false;
#if UNITY_2021_2_OR_NEWER
SyncStop(playable.GetGraph(), playable.GetTime());
#endif
}
#if UNITY_EDITOR
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();
}
#endif
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 TIMELINE_FRAMEACCURATE
if (graph.IsMatchFrameRateEnabled())
director.Play(graph.GetFrameRate());
else
director.Play();
#else
director.Play();
#endif
}
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))
{
#if UNITY_EDITOR
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);
#endif
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;
}
}
}
}
|