DirectorControlPlayable.cs
8.28 KB
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
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;
}
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);
SyncPlayState(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);
director.Evaluate();
}
m_SyncTime = false;
}
#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 SyncPlayState(PlayableGraph graph, double playableTime)
{
bool expectedFinished = (playableTime >= m_AssetDuration) && director.extrapolationMode == DirectorWrapMode.None;
if (graph.IsPlaying() && !expectedFinished)
director.Play();
else
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)
{
if (director.extrapolationMode == DirectorWrapMode.None)
return false;
else if (director.extrapolationMode == DirectorWrapMode.Hold)
expectedTime = m_AssetDuration;
else if (m_AssetDuration > float.Epsilon) // loop
expectedTime = expectedTime % m_AssetDuration;
}
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 = playable.GetTime();
break;
}
}
}
}