123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- using System;
- using UnityEngine.Playables;
- namespace UnityEngine.Timeline
- {
- /// <summary>
- /// Playable that synchronizes a particle system simulation.
- /// </summary>
- public class ParticleControlPlayable : PlayableBehaviour
- {
- const float kUnsetTime = -1;
- float m_LastTime = kUnsetTime;
- uint m_RandomSeed = 1;
- // 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.
- float m_SystemTime;
- /// <summary>
- /// Creates a Playable with a ParticleControlPlayable behaviour attached
- /// </summary>
- /// <param name="graph">The PlayableGraph to inject the Playable into.</param>
- /// <param name="component">The particle systtem to control</param>
- /// <param name="randomSeed">A random seed to use for particle simulation</param>
- /// <returns>Returns the created Playable.</returns>
- public static ScriptPlayable<ParticleControlPlayable> Create(PlayableGraph graph, ParticleSystem component, uint randomSeed)
- {
- if (component == null)
- return ScriptPlayable<ParticleControlPlayable>.Null;
- var handle = ScriptPlayable<ParticleControlPlayable>.Create(graph);
- handle.GetBehaviour().Initialize(component, randomSeed);
- return handle;
- }
- /// <summary>
- /// The particle system to control
- /// </summary>
- public ParticleSystem particleSystem { get; private set; }
- /// <summary>
- /// Initializes the behaviour with a particle system and random seed.
- /// </summary>
- /// <param name="ps"></param>
- /// <param name="randomSeed"></param>
- public void Initialize(ParticleSystem ps, uint randomSeed)
- {
- m_RandomSeed = Math.Max(1, randomSeed);
- particleSystem = ps;
- m_SystemTime = 0;
- SetRandomSeed();
- #if UNITY_EDITOR
- if (!Application.isPlaying && UnityEditor.PrefabUtility.IsPartOfPrefabInstance(ps))
- UnityEditor.PrefabUtility.prefabInstanceUpdated += OnPrefabUpdated;
- #endif
- }
- #if UNITY_EDITOR
- /// <summary>
- /// This function is called when the Playable that owns the PlayableBehaviour is destroyed.
- /// </summary>
- /// <param name="playable">The playable this behaviour is attached to.</param>
- public override void OnPlayableDestroy(Playable playable)
- {
- if (!Application.isPlaying)
- UnityEditor.PrefabUtility.prefabInstanceUpdated -= OnPrefabUpdated;
- }
- void OnPrefabUpdated(GameObject go)
- {
- // When the instance is updated from, this will cause the next evaluate to resimulate.
- if (UnityEditor.PrefabUtility.GetRootGameObject(particleSystem) == go)
- m_LastTime = kUnsetTime;
- }
- #endif
- void SetRandomSeed()
- {
- particleSystem.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
- var systems = particleSystem.gameObject.GetComponentsInChildren<ParticleSystem>();
- uint seed = m_RandomSeed;
- foreach (var ps in systems)
- {
- // don't overwrite user set random seeds
- if (ps.useAutoRandomSeed)
- {
- ps.useAutoRandomSeed = false;
- ps.randomSeed = seed;
- seed++;
- }
- }
- }
- /// <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="data">A FrameData structure that contains information about the current frame context.</param>
- public override void PrepareFrame(Playable playable, FrameData data)
- {
- if (particleSystem == null || !particleSystem.gameObject.activeInHierarchy)
- return;
- float localTime = (float)playable.GetTime();
- bool shouldUpdate = Mathf.Approximately(m_LastTime, kUnsetTime) ||
- !Mathf.Approximately(m_LastTime, localTime);
- if (shouldUpdate)
- {
- float epsilon = Time.fixedDeltaTime * 0.5f;
- float simTime = localTime;
- float expectedDelta = simTime - m_LastTime;
- // The first iteration includes the start delay. Evaluate(particleSystem.randomSeed) is how the particle system generates the random value internally.
- float startDelay = particleSystem.main.startDelay.Evaluate(particleSystem.randomSeed);
- float particleSystemDurationLoop0 = particleSystem.main.duration + startDelay;
- // The particle system time does not include the start delay so we need to remove this for our own system time.
- float expectedSystemTime = simTime > particleSystemDurationLoop0 ? m_SystemTime : m_SystemTime - startDelay;
- // if it's not looping, then the system time won't advance past the end of the duration
- if (!particleSystem.main.loop)
- expectedSystemTime = Math.Min(expectedSystemTime, particleSystem.main.duration);
- // conditions for restart
- bool restart = (simTime < m_LastTime) || // time went backwards
- (simTime < epsilon) || // time is set to 0
- Mathf.Approximately(m_LastTime, kUnsetTime) || // object disabled
- (expectedDelta > particleSystem.main.duration) || // large jump (bug workaround)
- !(Mathf.Abs(expectedSystemTime - particleSystem.time) < Time.maximumParticleDeltaTime); // particle system isn't where we left it
- if (restart)
- {
- // work around for a bug where simulate(simTime, true, true) doesn't work on loops
- particleSystem.Simulate(0, true, true);
- particleSystem.Simulate(simTime, true, false);
- m_SystemTime = simTime;
- }
- else
- {
- // ps.time will wrap, so we need to account for that in computing delta time
- float particleSystemDuration = simTime > particleSystemDurationLoop0 ? particleSystem.main.duration : particleSystemDurationLoop0;
- float fracTime = simTime % particleSystemDuration;
- float deltaTime = fracTime - m_SystemTime;
- if (deltaTime < -epsilon) // detect wrapping of ps.time
- deltaTime = fracTime + particleSystemDurationLoop0 - m_SystemTime;
- particleSystem.Simulate(deltaTime, true, false);
- m_SystemTime += deltaTime;
- }
- m_LastTime = localTime;
- }
- }
- /// <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_LastTime = kUnsetTime;
- }
- /// <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)
- {
- m_LastTime = kUnsetTime;
- }
- }
- }
|