123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450 |
- using System.Collections.Generic;
- using UnityEngine;
- namespace Chronos
- {
- // Currently bugged in Unity 5.5 due to a bug in the Simulate method at times higher than the system's duration
- // https://fogbugz.unity3d.com/default.asp?854431_5mmt5ltn2q6nuseh
- public class RewindableParticleSystemTimeline : ComponentTimeline<ParticleSystem>, IParticleSystemTimeline
- {
- #region Fields
- private float absoluteSimulationTime;
- private float loopedSimulationTime;
- private float relativeStartTime;
- #endregion
- #region Properties
- public float playbackSpeed { get; set; }
- public float time
- {
- get { return (loopedSimulationTime - relativeStartTime) % component.main.duration; }
- set { loopedSimulationTime = relativeStartTime + value; }
- }
- public bool isPlaying
- {
- get { return state == State.Playing || state == State.Stopping; }
- }
- public bool isPaused
- {
- get { return state == State.Paused; }
- }
- public bool isStopped
- {
- get { return state == State.Stopped; }
- }
- #endregion
- #region State and Emission
- private enum State
- {
- Playing,
- Paused,
- Stopping,
- Stopped
- }
- private enum EmissionAction
- {
- EnableEmission,
- DisableEmission,
- Play,
- Stop
- }
- private struct StateEvent
- {
- public State state;
- public float time;
- public StateEvent(State state, float time)
- {
- this.state = state;
- this.time = time;
- }
- }
- private struct EmissionEvent
- {
- public EmissionAction action;
- public float time;
- public EmissionEvent(EmissionAction action, float time)
- {
- this.action = action;
- this.time = time;
- }
- }
- private float stateEventsTime
- {
- get { return timeline.time; }
- }
- private float emissionEventsTime
- {
- get { return absoluteSimulationTime; }
- }
- private void RegisterState(State state)
- {
- stateEvents.Add(new StateEvent(state, stateEventsTime));
- }
- private void RegisterEmission(EmissionAction action)
- {
- emissionEvents.Add(new EmissionEvent(action, emissionEventsTime));
- }
- public RewindableParticleSystemTimeline(Timeline timeline, ParticleSystem component) : base(timeline, component)
- {
- emissionEvents = new List<EmissionEvent>();
- stateEvents = new List<StateEvent>();
- }
- private List<StateEvent> stateEvents;
- private State stateOnStart;
- private List<EmissionEvent> emissionEvents;
- private bool enableEmissionOnStart;
- private State _state;
- private State state
- {
- get { return _state; }
- set
- {
- if (!AssertForwardProperty("state", Severity.Error)) return;
- if (_state != value)
- {
- RegisterState(value);
- _state = value;
- }
- }
- }
- private bool _enableEmission;
- public bool enableEmission
- {
- get { return _enableEmission; }
- set
- {
- if (!AssertForwardProperty("enableEmission", Severity.Warn)) return;
- if (_enableEmission && !value)
- {
- RegisterEmission(EmissionAction.DisableEmission);
- }
- else if (!_enableEmission && value)
- {
- RegisterEmission(EmissionAction.EnableEmission);
- }
- _enableEmission = value;
- }
- }
- #endregion
- #region Timeline
- public override void CopyProperties(ParticleSystem source)
- {
- playbackSpeed = source.main.simulationSpeed;
- stateOnStart = state = source.main.playOnAwake ? State.Playing : State.Stopped;
- enableEmissionOnStart = _enableEmission = source.emission.enabled;
- time = 0;
- if (source.useAutoRandomSeed)
- {
- if (source.isPlaying)
- {
- source.Pause(true);
- }
- ////source.useAutoRandomSeed = false;
- ////source.randomSeed = (uint)Random.Range(1, int.MaxValue);
- }
- }
- public override void Update()
- {
- if (timeline.timeScale < 0)
- {
- // Determine state by consuming state events
- if (stateEvents.Count > 0)
- {
- StateEvent lastStateEvent = stateEvents[stateEvents.Count - 1];
- if (stateEventsTime <= lastStateEvent.time)
- {
- stateEvents.Remove(lastStateEvent);
- if (stateEvents.Count > 0)
- {
- _state = stateEvents[stateEvents.Count - 1].state;
- }
- else
- {
- _state = stateOnStart;
- }
- }
- }
- // Consume emission events
- for (int i = emissionEvents.Count - 1; i >= 0; i--)
- {
- if (emissionEvents[i].time > emissionEventsTime)
- {
- emissionEvents.RemoveAt(i);
- }
- }
- }
- // Known issue: low time scales / speed will cause stutter
- // Reported here: http://fogbugz.unity3d.com/default.asp?694191_dso514lin4rf5vbg
-
- component.Simulate(0, true, true);
- if (loopedSimulationTime > 0)
- {
- var emission = component.emission;
- emission.enabled = enableEmissionOnStart;
- float chunkStartTime = 0;
- for (int i = 0; i < emissionEvents.Count; i++)
- {
- EmissionEvent current = emissionEvents[i];
- component.Simulate(current.time - chunkStartTime, true, false);
- emission.enabled = current.action == EmissionAction.Play || current.action == EmissionAction.EnableEmission;
- chunkStartTime = current.time;
- }
- component.Simulate(loopedSimulationTime - chunkStartTime, true, false);
- if (state == State.Stopping && component.particleCount == 0 && timeline.timeScale > 0)
- {
- state = State.Stopped;
- }
- }
- if (state == State.Playing || state == State.Stopping)
- {
- absoluteSimulationTime += timeline.deltaTime * playbackSpeed;
- if (state == State.Playing && !component.main.loop && absoluteSimulationTime >= component.main.duration)
- {
- // A bit hacky to stop it here, as the real system just goes on playing,
- // just without emitting, but it shouldn't cause any problem. Unfortunately,
- // there is no check on Unity's side to see if it entered that final state.
- state = State.Stopping;
- }
- // Can be performance intensive at high times.
- // Limit it with a loop-multiple of its time (globally configurable)
- float maxLoops = Timekeeper.instance.maxParticleLoops;
- if (maxLoops > 0 && state != State.Stopping)
- {
- loopedSimulationTime = absoluteSimulationTime % (component.main.duration * maxLoops);
- }
- else
- {
- loopedSimulationTime = absoluteSimulationTime;
- }
- }
- }
- #endregion
- #region Methods
- public void Play(bool withChildren = true)
- {
- if (!AssertForwardMethod("Play", Severity.Warn)) return;
- if (state != State.Paused)
- {
- RegisterEmission(EmissionAction.Play);
- relativeStartTime = loopedSimulationTime;
- }
- state = State.Playing;
- if (withChildren)
- {
- ExecuteOnChildren(ps => ps.Play(false), ps => ps.Play(false));
- }
- }
- public void Pause(bool withChildren = true)
- {
- if (!AssertForwardMethod("Pause", Severity.Warn)) return;
- state = State.Paused;
- if (withChildren)
- {
- ExecuteOnChildren(ps => ps.Pause(false), ps => ps.Pause(false));
- }
- }
- public void Stop(bool withChildren = true)
- {
- if (!AssertForwardMethod("Stop", Severity.Warn)) return;
- state = State.Stopping;
- RegisterEmission(EmissionAction.Stop);
- if (withChildren)
- {
- ExecuteOnChildren(ps => ps.Stop(false), ps => ps.Stop(false));
- }
- }
- public bool IsAlive(bool withChildren = true)
- {
- if (state == State.Stopped)
- {
- return false;
- }
- if (withChildren)
- {
- return CheckOnChildren(ps => ps.IsAlive(false), ps => ps.IsAlive(false));
- }
- return true;
- }
- #endregion
- #region Hierarchy
- private delegate void ChildNativeAction(ParticleSystem target);
- private delegate void ChildChronosAction(IParticleSystemTimeline target);
- private delegate bool ChildNativeCheck(ParticleSystem target);
- private delegate bool ChildChronosCheck(IParticleSystemTimeline target);
- private void ExecuteOnChildren(ChildNativeAction native, ChildChronosAction chronos)
- {
- foreach (ParticleSystem childParticleSystem in timeline.GetComponentsInChildren<ParticleSystem>())
- {
- if (childParticleSystem == component)
- {
- continue;
- }
- Timeline childTimeline = childParticleSystem.GetComponent<Timeline>();
- if (childTimeline != null)
- {
- chronos(childTimeline.particleSystem);
- }
- else
- {
- native(childParticleSystem);
- }
- }
- }
- private bool CheckOnChildren(ChildNativeCheck native, ChildChronosCheck chronos)
- {
- foreach (ParticleSystem childParticleSystem in timeline.GetComponentsInChildren<ParticleSystem>())
- {
- if (childParticleSystem == component)
- {
- continue;
- }
- Timeline childTimeline = childParticleSystem.GetComponent<Timeline>();
- if (childTimeline != null)
- {
- if (!chronos(childTimeline.particleSystem))
- {
- return false;
- }
- }
- else
- {
- if (!native(childParticleSystem))
- {
- return false;
- }
- }
- }
- return true;
- }
- #endregion
- #region Utility
- private bool AssertForwardMethod(string method, Severity severity)
- {
- if (timeline.timeScale <= 0)
- {
- if (severity == Severity.Error)
- {
- throw new ChronosException("Cannot call " + method + " on the particle system while time is paused or rewinding.");
- }
- else if (severity == Severity.Warn)
- {
- Debug.LogWarning("Trying to call " + method +
- " on the particle system while time is paused or rewinding, ignoring.");
- }
- }
- return timeline.timeScale > 0;
- }
- private bool AssertForwardProperty(string property, Severity severity)
- {
- if (timeline.timeScale <= 0)
- {
- if (severity == Severity.Error)
- {
- throw new ChronosException("Cannot set " + property + " on the particle system while time is paused or rewinding.");
- }
- else if (severity == Severity.Warn)
- {
- Debug.LogWarning("Trying to set " + property +
- " on the particle system while time is paused or rewinding, ignoring.");
- }
- }
- return timeline.timeScale > 0;
- }
- #endregion
- }
- }
|