ControlPlayableAsset.cs 15 KB

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