A game about forced loneliness, made by TACStudios
1using System;
2using UnityEngine.Playables;
3
4namespace UnityEngine.Timeline
5{
6 /// <summary>
7 /// Playable that synchronizes a particle system simulation.
8 /// </summary>
9 public class ParticleControlPlayable : PlayableBehaviour
10 {
11 const float kUnsetTime = float.MaxValue;
12 float m_LastPlayableTime = kUnsetTime;
13 float m_LastParticleTime = kUnsetTime;
14 uint m_RandomSeed = 1;
15
16
17 /// <summary>
18 /// Creates a Playable with a ParticleControlPlayable behaviour attached
19 /// </summary>
20 /// <param name="graph">The PlayableGraph to inject the Playable into.</param>
21 /// <param name="component">The particle systtem to control</param>
22 /// <param name="randomSeed">A random seed to use for particle simulation</param>
23 /// <returns>Returns the created Playable.</returns>
24 public static ScriptPlayable<ParticleControlPlayable> Create(PlayableGraph graph, ParticleSystem component, uint randomSeed)
25 {
26 if (component == null)
27 return ScriptPlayable<ParticleControlPlayable>.Null;
28
29 var handle = ScriptPlayable<ParticleControlPlayable>.Create(graph);
30 handle.GetBehaviour().Initialize(component, randomSeed);
31 return handle;
32 }
33
34 /// <summary>
35 /// The particle system to control
36 /// </summary>
37 public ParticleSystem particleSystem { get; private set; }
38
39 /// <summary>
40 /// Initializes the behaviour with a particle system and random seed.
41 /// </summary>
42 /// <param name="ps"></param>
43 /// <param name="randomSeed"></param>
44 public void Initialize(ParticleSystem ps, uint randomSeed)
45 {
46 m_RandomSeed = Math.Max(1, randomSeed);
47 particleSystem = ps;
48 SetRandomSeed(particleSystem, m_RandomSeed);
49
50#if UNITY_EDITOR
51 if (!Application.isPlaying && UnityEditor.PrefabUtility.IsPartOfPrefabInstance(ps))
52 UnityEditor.PrefabUtility.prefabInstanceUpdated += OnPrefabUpdated;
53#endif
54 }
55
56#if UNITY_EDITOR
57 /// <summary>
58 /// This function is called when the Playable that owns the PlayableBehaviour is destroyed.
59 /// </summary>
60 /// <param name="playable">The playable this behaviour is attached to.</param>
61 public override void OnPlayableDestroy(Playable playable)
62 {
63 if (!Application.isPlaying)
64 UnityEditor.PrefabUtility.prefabInstanceUpdated -= OnPrefabUpdated;
65 }
66
67 void OnPrefabUpdated(GameObject go)
68 {
69 // When the instance is updated from, this will cause the next evaluate to resimulate.
70 if (UnityEditor.PrefabUtility.GetRootGameObject(particleSystem) == go)
71 m_LastPlayableTime = kUnsetTime;
72 }
73
74#endif
75
76 static void SetRandomSeed(ParticleSystem particleSystem, uint randomSeed)
77 {
78 if (particleSystem == null)
79 return;
80
81 particleSystem.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
82 if (particleSystem.useAutoRandomSeed)
83 {
84 particleSystem.useAutoRandomSeed = false;
85 particleSystem.randomSeed = randomSeed;
86 }
87
88 for (int i = 0; i < particleSystem.subEmitters.subEmittersCount; i++)
89 {
90 SetRandomSeed(particleSystem.subEmitters.GetSubEmitterSystem(i), ++randomSeed);
91 }
92 }
93
94 /// <summary>
95 /// This function is called during the PrepareFrame phase of the PlayableGraph.
96 /// </summary>
97 /// <param name="playable">The Playable that owns the current PlayableBehaviour.</param>
98 /// <param name="data">A FrameData structure that contains information about the current frame context.</param>
99 public override void PrepareFrame(Playable playable, FrameData data)
100 {
101 if (particleSystem == null || !particleSystem.gameObject.activeInHierarchy)
102 {
103 // case 1212943
104 m_LastPlayableTime = kUnsetTime;
105 return;
106 }
107
108 var time = (float)playable.GetTime();
109 var particleTime = particleSystem.time;
110
111 // if particle system time has changed externally, a re-sync is needed
112 if (m_LastPlayableTime > time || !Mathf.Approximately(particleTime, m_LastParticleTime))
113 Simulate(time, true);
114 else if (m_LastPlayableTime < time)
115 Simulate(time - m_LastPlayableTime, false);
116
117 m_LastPlayableTime = time;
118 m_LastParticleTime = particleSystem.time;
119 }
120
121 /// <summary>
122 /// This function is called when the Playable play state is changed to Playables.PlayState.Playing.
123 /// </summary>
124 /// <param name="playable">The Playable that owns the current PlayableBehaviour.</param>
125 /// <param name="info">A FrameData structure that contains information about the current frame context.</param>
126 public override void OnBehaviourPlay(Playable playable, FrameData info)
127 {
128 m_LastPlayableTime = kUnsetTime;
129 }
130
131 /// <summary>
132 /// This function is called when the Playable play state is changed to PlayState.Paused.
133 /// </summary>
134 /// <param name="playable">The playable this behaviour is attached to.</param>
135 /// <param name="info">A FrameData structure that contains information about the current frame context.</param>
136 public override void OnBehaviourPause(Playable playable, FrameData info)
137 {
138 m_LastPlayableTime = kUnsetTime;
139 }
140
141 private void Simulate(float time, bool restart)
142 {
143 const bool withChildren = false;
144 const bool fixedTimeStep = false;
145 float maxTime = Time.maximumDeltaTime;
146
147 if (restart)
148 particleSystem.Simulate(0, withChildren, true, fixedTimeStep);
149
150 // simulating by too large a time-step causes sub-emitters not to work, and loops not to
151 // simulate correctly
152 while (time > maxTime)
153 {
154 particleSystem.Simulate(maxTime, withChildren, false, fixedTimeStep);
155 time -= maxTime;
156 }
157
158 if (time > 0)
159 particleSystem.Simulate(time, withChildren, false, fixedTimeStep);
160 }
161 }
162}