ParticleControlPlayable.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. using System;
  2. using UnityEngine.Playables;
  3. namespace UnityEngine.Timeline
  4. {
  5. /// <summary>
  6. /// Playable that synchronizes a particle system simulation.
  7. /// </summary>
  8. public class ParticleControlPlayable : PlayableBehaviour
  9. {
  10. const float kUnsetTime = -1;
  11. float m_LastTime = kUnsetTime;
  12. uint m_RandomSeed = 1;
  13. // particleSystem.time can not be relied on for an accurate time. It does not advance until a delta threshold is reached(fixedUpdate) and until the start delay has elapsed.
  14. float m_SystemTime;
  15. /// <summary>
  16. /// Creates a Playable with a ParticleControlPlayable behaviour attached
  17. /// </summary>
  18. /// <param name="graph">The PlayableGraph to inject the Playable into.</param>
  19. /// <param name="component">The particle systtem to control</param>
  20. /// <param name="randomSeed">A random seed to use for particle simulation</param>
  21. /// <returns>Returns the created Playable.</returns>
  22. public static ScriptPlayable<ParticleControlPlayable> Create(PlayableGraph graph, ParticleSystem component, uint randomSeed)
  23. {
  24. if (component == null)
  25. return ScriptPlayable<ParticleControlPlayable>.Null;
  26. var handle = ScriptPlayable<ParticleControlPlayable>.Create(graph);
  27. handle.GetBehaviour().Initialize(component, randomSeed);
  28. return handle;
  29. }
  30. /// <summary>
  31. /// The particle system to control
  32. /// </summary>
  33. public ParticleSystem particleSystem { get; private set; }
  34. /// <summary>
  35. /// Initializes the behaviour with a particle system and random seed.
  36. /// </summary>
  37. /// <param name="ps"></param>
  38. /// <param name="randomSeed"></param>
  39. public void Initialize(ParticleSystem ps, uint randomSeed)
  40. {
  41. m_RandomSeed = Math.Max(1, randomSeed);
  42. particleSystem = ps;
  43. m_SystemTime = 0;
  44. SetRandomSeed();
  45. #if UNITY_EDITOR
  46. if (!Application.isPlaying && UnityEditor.PrefabUtility.IsPartOfPrefabInstance(ps))
  47. UnityEditor.PrefabUtility.prefabInstanceUpdated += OnPrefabUpdated;
  48. #endif
  49. }
  50. #if UNITY_EDITOR
  51. /// <summary>
  52. /// This function is called when the Playable that owns the PlayableBehaviour is destroyed.
  53. /// </summary>
  54. /// <param name="playable">The playable this behaviour is attached to.</param>
  55. public override void OnPlayableDestroy(Playable playable)
  56. {
  57. if (!Application.isPlaying)
  58. UnityEditor.PrefabUtility.prefabInstanceUpdated -= OnPrefabUpdated;
  59. }
  60. void OnPrefabUpdated(GameObject go)
  61. {
  62. // When the instance is updated from, this will cause the next evaluate to resimulate.
  63. if (UnityEditor.PrefabUtility.GetRootGameObject(particleSystem) == go)
  64. m_LastTime = kUnsetTime;
  65. }
  66. #endif
  67. void SetRandomSeed()
  68. {
  69. particleSystem.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
  70. var systems = particleSystem.gameObject.GetComponentsInChildren<ParticleSystem>();
  71. uint seed = m_RandomSeed;
  72. foreach (var ps in systems)
  73. {
  74. // don't overwrite user set random seeds
  75. if (ps.useAutoRandomSeed)
  76. {
  77. ps.useAutoRandomSeed = false;
  78. ps.randomSeed = seed;
  79. seed++;
  80. }
  81. }
  82. }
  83. /// <summary>
  84. /// This function is called during the PrepareFrame phase of the PlayableGraph.
  85. /// </summary>
  86. /// <param name="playable">The Playable that owns the current PlayableBehaviour.</param>
  87. /// <param name="data">A FrameData structure that contains information about the current frame context.</param>
  88. public override void PrepareFrame(Playable playable, FrameData data)
  89. {
  90. if (particleSystem == null || !particleSystem.gameObject.activeInHierarchy)
  91. return;
  92. float localTime = (float)playable.GetTime();
  93. bool shouldUpdate = Mathf.Approximately(m_LastTime, kUnsetTime) ||
  94. !Mathf.Approximately(m_LastTime, localTime);
  95. if (shouldUpdate)
  96. {
  97. float epsilon = Time.fixedDeltaTime * 0.5f;
  98. float simTime = localTime;
  99. float expectedDelta = simTime - m_LastTime;
  100. // The first iteration includes the start delay. Evaluate(particleSystem.randomSeed) is how the particle system generates the random value internally.
  101. float startDelay = particleSystem.main.startDelay.Evaluate(particleSystem.randomSeed);
  102. float particleSystemDurationLoop0 = particleSystem.main.duration + startDelay;
  103. // The particle system time does not include the start delay so we need to remove this for our own system time.
  104. float expectedSystemTime = simTime > particleSystemDurationLoop0 ? m_SystemTime : m_SystemTime - startDelay;
  105. // if it's not looping, then the system time won't advance past the end of the duration
  106. if (!particleSystem.main.loop)
  107. expectedSystemTime = Math.Min(expectedSystemTime, particleSystem.main.duration);
  108. // conditions for restart
  109. bool restart = (simTime < m_LastTime) || // time went backwards
  110. (simTime < epsilon) || // time is set to 0
  111. Mathf.Approximately(m_LastTime, kUnsetTime) || // object disabled
  112. (expectedDelta > particleSystem.main.duration) || // large jump (bug workaround)
  113. !(Mathf.Abs(expectedSystemTime - particleSystem.time) < Time.maximumParticleDeltaTime); // particle system isn't where we left it
  114. if (restart)
  115. {
  116. // work around for a bug where simulate(simTime, true, true) doesn't work on loops
  117. particleSystem.Simulate(0, true, true);
  118. particleSystem.Simulate(simTime, true, false);
  119. m_SystemTime = simTime;
  120. }
  121. else
  122. {
  123. // ps.time will wrap, so we need to account for that in computing delta time
  124. float particleSystemDuration = simTime > particleSystemDurationLoop0 ? particleSystem.main.duration : particleSystemDurationLoop0;
  125. float fracTime = simTime % particleSystemDuration;
  126. float deltaTime = fracTime - m_SystemTime;
  127. if (deltaTime < -epsilon) // detect wrapping of ps.time
  128. deltaTime = fracTime + particleSystemDurationLoop0 - m_SystemTime;
  129. particleSystem.Simulate(deltaTime, true, false);
  130. m_SystemTime += deltaTime;
  131. }
  132. m_LastTime = localTime;
  133. }
  134. }
  135. /// <summary>
  136. /// This function is called when the Playable play state is changed to Playables.PlayState.Playing.
  137. /// </summary>
  138. /// <param name="playable">The Playable that owns the current PlayableBehaviour.</param>
  139. /// <param name="info">A FrameData structure that contains information about the current frame context.</param>
  140. public override void OnBehaviourPlay(Playable playable, FrameData info)
  141. {
  142. m_LastTime = kUnsetTime;
  143. }
  144. /// <summary>
  145. /// This function is called when the Playable play state is changed to PlayState.Paused.
  146. /// </summary>
  147. /// <param name="playable">The playable this behaviour is attached to.</param>
  148. /// <param name="info">A FrameData structure that contains information about the current frame context.</param>
  149. public override void OnBehaviourPause(Playable playable, FrameData info)
  150. {
  151. m_LastTime = kUnsetTime;
  152. }
  153. }
  154. }