ParticleControlPlayable.cs
7.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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;
}
}
}