A game about forced loneliness, made by TACStudios
at master 450 lines 18 kB view raw
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using UnityEngine.Playables; 5 6namespace UnityEngine.Timeline 7{ 8 /// <summary> 9 /// Playable Asset that generates playables for controlling time-related elements on a GameObject. 10 /// </summary> 11 [Serializable] 12 [NotKeyable] 13 public class ControlPlayableAsset : PlayableAsset, IPropertyPreview, ITimelineClipAsset 14 { 15 const int k_MaxRandInt = 10000; 16 static readonly List<PlayableDirector> k_EmptyDirectorsList = new List<PlayableDirector>(0); 17 static readonly List<ParticleSystem> k_EmptyParticlesList = new List<ParticleSystem>(0); 18 static readonly HashSet<ParticleSystem> s_SubEmitterCollector = new HashSet<ParticleSystem>(); 19 20 /// <summary> 21 /// GameObject in the scene to control, or the parent of the instantiated prefab. 22 /// </summary> 23 [SerializeField] public ExposedReference<GameObject> sourceGameObject; 24 25 /// <summary> 26 /// Prefab object that will be instantiated. 27 /// </summary> 28 [SerializeField] public GameObject prefabGameObject; 29 30 /// <summary> 31 /// Indicates whether Particle Systems will be controlled. 32 /// </summary> 33 [SerializeField] public bool updateParticle = true; 34 35 /// <summary> 36 /// Random seed to supply particle systems that are set to use autoRandomSeed 37 /// </summary> 38 /// <remarks> 39 /// This is used to maintain determinism when playing back in timeline. Sub emitters will be assigned incrementing random seeds to maintain determinism and distinction. 40 /// </remarks> 41 [SerializeField] public uint particleRandomSeed; 42 43 /// <summary> 44 /// Indicates whether playableDirectors are controlled. 45 /// </summary> 46 [SerializeField] public bool updateDirector = true; 47 48 /// <summary> 49 /// Indicates whether Monobehaviours implementing ITimeControl will be controlled. 50 /// </summary> 51 [SerializeField] public bool updateITimeControl = true; 52 53 /// <summary> 54 /// Indicates whether to search the entire hierarchy for controllable components. 55 /// </summary> 56 [SerializeField] public bool searchHierarchy; 57 58 /// <summary> 59 /// Indicate whether GameObject activation is controlled 60 /// </summary> 61 [SerializeField] public bool active = true; 62 63 /// <summary> 64 /// Indicates the active state of the GameObject when Timeline is stopped. 65 /// </summary> 66 [SerializeField] 67 public ActivationControlPlayable.PostPlaybackState postPlayback = ActivationControlPlayable.PostPlaybackState.Revert; 68 69 /// <summary> 70 /// Which action to apply to the <see cref="PlayableDirector"/> at the end of the control clip. <seealso cref="DirectorControlPlayable.PauseAction"/> 71 /// </summary> 72 [SerializeField] 73 public DirectorControlPlayable.PauseAction directorOnClipEnd; 74 75 PlayableAsset m_ControlDirectorAsset; 76 double m_Duration = PlayableBinding.DefaultDuration; 77 bool m_SupportLoop; 78 79 private static HashSet<PlayableDirector> s_ProcessedDirectors = new HashSet<PlayableDirector>(); 80 private static HashSet<GameObject> s_CreatedPrefabs = new HashSet<GameObject>(); 81 82 // does the last instance created control directors and/or particles 83 internal bool controllingDirectors { get; private set; } 84 internal bool controllingParticles { get; private set; } 85 86 /// <summary> 87 /// This function is called when the object is loaded. 88 /// </summary> 89 public void OnEnable() 90 { 91 // can't be set in a constructor 92 if (particleRandomSeed == 0) 93 particleRandomSeed = (uint)Random.Range(1, k_MaxRandInt); 94 } 95 96 /// <summary> 97 /// Returns the duration in seconds needed to play the underlying director or particle system exactly once. 98 /// </summary> 99 public override double duration { get { return m_Duration; } } 100 101 /// <summary> 102 /// Returns the capabilities of TimelineClips that contain a ControlPlayableAsset 103 /// </summary> 104 public ClipCaps clipCaps 105 { 106 get { return ClipCaps.ClipIn | ClipCaps.SpeedMultiplier | (m_SupportLoop ? ClipCaps.Looping : ClipCaps.None); } 107 } 108 109 /// <summary> 110 /// Creates the root of a Playable subgraph to control the contents of the game object. 111 /// </summary> 112 /// <param name="graph">PlayableGraph that will own the playable</param> 113 /// <param name="go">The GameObject that triggered the graph build</param> 114 /// <returns>The root playable of the subgraph</returns> 115 public override Playable CreatePlayable(PlayableGraph graph, GameObject go) 116 { 117 // case 989856 118 if (prefabGameObject != null) 119 { 120 if (s_CreatedPrefabs.Contains(prefabGameObject)) 121 { 122 Debug.LogWarningFormat("Control Track Clip ({0}) is causing a prefab to instantiate itself recursively. Aborting further instances.", name); 123 return Playable.Create(graph); 124 } 125 s_CreatedPrefabs.Add(prefabGameObject); 126 } 127 128 Playable root = Playable.Null; 129 var playables = new List<Playable>(); 130 131 GameObject sourceObject = sourceGameObject.Resolve(graph.GetResolver()); 132 if (prefabGameObject != null) 133 { 134 Transform parenTransform = sourceObject != null ? sourceObject.transform : null; 135 var controlPlayable = PrefabControlPlayable.Create(graph, prefabGameObject, parenTransform); 136 137 sourceObject = controlPlayable.GetBehaviour().prefabInstance; 138 playables.Add(controlPlayable); 139 } 140 141 m_Duration = PlayableBinding.DefaultDuration; 142 m_SupportLoop = false; 143 144 controllingParticles = false; 145 controllingDirectors = false; 146 147 if (sourceObject != null) 148 { 149 var directors = updateDirector ? GetComponent<PlayableDirector>(sourceObject) : k_EmptyDirectorsList; 150 var particleSystems = updateParticle ? GetControllableParticleSystems(sourceObject) : k_EmptyParticlesList; 151 152 // update the duration and loop values (used for UI purposes) here 153 // so they are tied to the latest gameObject bound 154 UpdateDurationAndLoopFlag(directors, particleSystems); 155 156 var director = go.GetComponent<PlayableDirector>(); 157 if (director != null) 158 m_ControlDirectorAsset = director.playableAsset; 159 160 if (go == sourceObject && prefabGameObject == null) 161 { 162 Debug.LogWarningFormat("Control Playable ({0}) is referencing the same PlayableDirector component than the one in which it is playing.", name); 163 active = false; 164 if (!searchHierarchy) 165 updateDirector = false; 166 } 167 168 if (active) 169 CreateActivationPlayable(sourceObject, graph, playables); 170 171 if (updateDirector) 172 SearchHierarchyAndConnectDirector(directors, graph, playables, prefabGameObject != null); 173 174 if (updateParticle) 175 SearchHierarchyAndConnectParticleSystem(particleSystems, graph, playables); 176 177 if (updateITimeControl) 178 SearchHierarchyAndConnectControlableScripts(GetControlableScripts(sourceObject), graph, playables); 179 180 // Connect Playables to Generic to Mixer 181 root = ConnectPlayablesToMixer(graph, playables); 182 } 183 184 if (prefabGameObject != null) 185 s_CreatedPrefabs.Remove(prefabGameObject); 186 187 if (!root.IsValid()) 188 root = Playable.Create(graph); 189 190 return root; 191 } 192 193 static Playable ConnectPlayablesToMixer(PlayableGraph graph, List<Playable> playables) 194 { 195 var mixer = Playable.Create(graph, playables.Count); 196 197 for (int i = 0; i != playables.Count; ++i) 198 { 199 ConnectMixerAndPlayable(graph, mixer, playables[i], i); 200 } 201 202 mixer.SetPropagateSetTime(true); 203 204 return mixer; 205 } 206 207 void CreateActivationPlayable(GameObject root, PlayableGraph graph, 208 List<Playable> outplayables) 209 { 210 var activation = ActivationControlPlayable.Create(graph, root, postPlayback); 211 if (activation.IsValid()) 212 outplayables.Add(activation); 213 } 214 215 void SearchHierarchyAndConnectParticleSystem(IEnumerable<ParticleSystem> particleSystems, PlayableGraph graph, 216 List<Playable> outplayables) 217 { 218 foreach (var particleSystem in particleSystems) 219 { 220 if (particleSystem != null) 221 { 222 controllingParticles = true; 223 outplayables.Add(ParticleControlPlayable.Create(graph, particleSystem, particleRandomSeed)); 224 } 225 } 226 } 227 228 void SearchHierarchyAndConnectDirector(IEnumerable<PlayableDirector> directors, PlayableGraph graph, 229 List<Playable> outplayables, bool disableSelfReferences) 230 { 231 foreach (var director in directors) 232 { 233 if (director != null) 234 { 235 if (director.playableAsset != m_ControlDirectorAsset) 236 { 237 ScriptPlayable<DirectorControlPlayable> directorControlPlayable = DirectorControlPlayable.Create(graph, director); 238 directorControlPlayable.GetBehaviour().pauseAction = directorOnClipEnd; 239 outplayables.Add(directorControlPlayable); 240 241 controllingDirectors = true; 242 } 243 // if this self references, disable the director. 244 else if (disableSelfReferences) 245 { 246 director.enabled = false; 247 } 248 } 249 } 250 } 251 252 static void SearchHierarchyAndConnectControlableScripts(IEnumerable<MonoBehaviour> controlableScripts, PlayableGraph graph, List<Playable> outplayables) 253 { 254 foreach (var script in controlableScripts) 255 { 256 outplayables.Add(TimeControlPlayable.Create(graph, (ITimeControl)script)); 257 } 258 } 259 260 static void ConnectMixerAndPlayable(PlayableGraph graph, Playable mixer, Playable playable, 261 int portIndex) 262 { 263 graph.Connect(playable, 0, mixer, portIndex); 264 mixer.SetInputWeight(playable, 1.0f); 265 } 266 267 internal IList<T> GetComponent<T>(GameObject gameObject) 268 { 269 var components = new List<T>(); 270 if (gameObject != null) 271 { 272 if (searchHierarchy) 273 { 274 gameObject.GetComponentsInChildren<T>(true, components); 275 } 276 else 277 { 278 gameObject.GetComponents<T>(components); 279 } 280 } 281 return components; 282 } 283 284 internal static IEnumerable<MonoBehaviour> GetControlableScripts(GameObject root) 285 { 286 if (root == null) 287 yield break; 288 289 foreach (var script in root.GetComponentsInChildren<MonoBehaviour>()) 290 { 291 if (script is ITimeControl) 292 yield return script; 293 } 294 } 295 296 internal void UpdateDurationAndLoopFlag(IList<PlayableDirector> directors, IList<ParticleSystem> particleSystems) 297 { 298 if (directors.Count == 0 && particleSystems.Count == 0) 299 return; 300 301 const double invalidDuration = double.NegativeInfinity; 302 303 var maxDuration = invalidDuration; 304 var supportsLoop = false; 305 306 foreach (var director in directors) 307 { 308 if (director.playableAsset != null) 309 { 310 var assetDuration = director.playableAsset.duration; 311 312 if (director.playableAsset is TimelineAsset && assetDuration > 0.0) 313 // Timeline assets report being one tick shorter than they actually are, unless they are empty 314 assetDuration = (double)((DiscreteTime)assetDuration).OneTickAfter(); 315 316 maxDuration = Math.Max(maxDuration, assetDuration); 317 supportsLoop = supportsLoop || director.extrapolationMode == DirectorWrapMode.Loop; 318 } 319 } 320 321 foreach (var particleSystem in particleSystems) 322 { 323 maxDuration = Math.Max(maxDuration, particleSystem.main.duration); 324 supportsLoop = supportsLoop || particleSystem.main.loop; 325 } 326 327 m_Duration = double.IsNegativeInfinity(maxDuration) ? PlayableBinding.DefaultDuration : maxDuration; 328 m_SupportLoop = supportsLoop; 329 } 330 331 IList<ParticleSystem> GetControllableParticleSystems(GameObject go) 332 { 333 var roots = new List<ParticleSystem>(); 334 335 // searchHierarchy will look for particle systems on child objects. 336 // once a particle system is found, all child particle systems are controlled with playables 337 // unless they are subemitters 338 339 if (searchHierarchy || go.GetComponent<ParticleSystem>() != null) 340 { 341 GetControllableParticleSystems(go.transform, roots, s_SubEmitterCollector); 342 s_SubEmitterCollector.Clear(); 343 } 344 345 return roots; 346 } 347 348 static void GetControllableParticleSystems(Transform t, ICollection<ParticleSystem> roots, HashSet<ParticleSystem> subEmitters) 349 { 350 var ps = t.GetComponent<ParticleSystem>(); 351 if (ps != null) 352 { 353 if (!subEmitters.Contains(ps)) 354 { 355 roots.Add(ps); 356 CacheSubEmitters(ps, subEmitters); 357 } 358 } 359 360 for (int i = 0; i < t.childCount; ++i) 361 { 362 GetControllableParticleSystems(t.GetChild(i), roots, subEmitters); 363 } 364 } 365 366 static void CacheSubEmitters(ParticleSystem ps, HashSet<ParticleSystem> subEmitters) 367 { 368 if (ps == null) 369 return; 370 371 for (int i = 0; i < ps.subEmitters.subEmittersCount; i++) 372 { 373 subEmitters.Add(ps.subEmitters.GetSubEmitterSystem(i)); 374 // don't call this recursively. subEmitters are only simulated one level deep. 375 } 376 } 377 378 /// <inheritdoc/> 379 public void GatherProperties(PlayableDirector director, IPropertyCollector driver) 380 { 381 // This method is no longer called by Control Tracks. 382 if (director == null) 383 return; 384 385 // prevent infinite recursion 386 if (s_ProcessedDirectors.Contains(director)) 387 return; 388 s_ProcessedDirectors.Add(director); 389 390 var gameObject = sourceGameObject.Resolve(director); 391 if (gameObject != null) 392 { 393 if (updateParticle)// case 1076850 -- drive all emitters, not just roots. 394 PreviewParticles(driver, gameObject.GetComponentsInChildren<ParticleSystem>(true)); 395 396 if (active) 397 PreviewActivation(driver, new[] { gameObject }); 398 399 if (updateITimeControl) 400 PreviewTimeControl(driver, director, GetControlableScripts(gameObject)); 401 402 if (updateDirector) 403 PreviewDirectors(driver, GetComponent<PlayableDirector>(gameObject)); 404 } 405 s_ProcessedDirectors.Remove(director); 406 } 407 408 internal static void PreviewParticles(IPropertyCollector driver, IEnumerable<ParticleSystem> particles) 409 { 410 foreach (var ps in particles) 411 { 412 driver.AddFromName<ParticleSystem>(ps.gameObject, "randomSeed"); 413 driver.AddFromName<ParticleSystem>(ps.gameObject, "autoRandomSeed"); 414 } 415 } 416 417 internal static void PreviewActivation(IPropertyCollector driver, IEnumerable<GameObject> objects) 418 { 419 foreach (var gameObject in objects) 420 driver.AddFromName(gameObject, "m_IsActive"); 421 } 422 423 internal static void PreviewTimeControl(IPropertyCollector driver, PlayableDirector director, IEnumerable<MonoBehaviour> scripts) 424 { 425 foreach (var script in scripts) 426 { 427 var propertyPreview = script as IPropertyPreview; 428 if (propertyPreview != null) 429 propertyPreview.GatherProperties(director, driver); 430 else 431 driver.AddFromComponent(script.gameObject, script); 432 } 433 } 434 435 internal static void PreviewDirectors(IPropertyCollector driver, IEnumerable<PlayableDirector> directors) 436 { 437 foreach (var childDirector in directors) 438 { 439 if (childDirector == null) 440 continue; 441 442 var timeline = childDirector.playableAsset as TimelineAsset; 443 if (timeline == null) 444 continue; 445 446 timeline.GatherProperties(childDirector, driver); 447 } 448 } 449 } 450}