UIParticleAttractor.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. using System;
  2. using System.Collections.Generic;
  3. using Coffee.UIParticleInternal;
  4. using UnityEngine;
  5. using UnityEngine.Events;
  6. namespace Coffee.UIExtensions
  7. {
  8. [ExecuteAlways]
  9. public class UIParticleAttractor : MonoBehaviour, ISerializationCallbackReceiver
  10. {
  11. public enum Movement
  12. {
  13. Linear,
  14. Smooth,
  15. Sphere
  16. }
  17. public enum UpdateMode
  18. {
  19. Normal,
  20. UnscaledTime
  21. }
  22. [SerializeField]
  23. [HideInInspector]
  24. private ParticleSystem m_ParticleSystem;
  25. [SerializeField]
  26. private List<ParticleSystem> m_ParticleSystems = new List<ParticleSystem>();
  27. [Range(0.1f, 10f)]
  28. [SerializeField]
  29. private float m_DestinationRadius = 1;
  30. [Range(0f, 0.95f)]
  31. [SerializeField]
  32. private float m_DelayRate;
  33. [Range(0.001f, 100f)]
  34. [SerializeField]
  35. private float m_MaxSpeed = 1;
  36. [SerializeField]
  37. private Movement m_Movement;
  38. [SerializeField]
  39. private UpdateMode m_UpdateMode;
  40. [SerializeField]
  41. private UnityEvent m_OnAttracted;
  42. private List<UIParticle> _uiParticles = new List<UIParticle>();
  43. public float destinationRadius
  44. {
  45. get => m_DestinationRadius;
  46. set => m_DestinationRadius = Mathf.Clamp(value, 0.1f, 10f);
  47. }
  48. public float delay
  49. {
  50. get => m_DelayRate;
  51. set => m_DelayRate = value;
  52. }
  53. public float maxSpeed
  54. {
  55. get => m_MaxSpeed;
  56. set => m_MaxSpeed = value;
  57. }
  58. public Movement movement
  59. {
  60. get => m_Movement;
  61. set => m_Movement = value;
  62. }
  63. public UpdateMode updateMode
  64. {
  65. get => m_UpdateMode;
  66. set => m_UpdateMode = value;
  67. }
  68. public UnityEvent onAttracted
  69. {
  70. get => m_OnAttracted;
  71. set => m_OnAttracted = value;
  72. }
  73. /// <summary>
  74. /// The target ParticleSystems to attract. Use <see cref="AddParticleSystem"/> and
  75. /// <see cref="RemoveParticleSystem"/> to modify the list.
  76. /// </summary>
  77. public IReadOnlyList<ParticleSystem> particleSystems => m_ParticleSystems;
  78. public void AddParticleSystem(ParticleSystem ps)
  79. {
  80. if (m_ParticleSystems == null)
  81. {
  82. m_ParticleSystems = new List<ParticleSystem>();
  83. }
  84. var i = m_ParticleSystems.IndexOf(ps);
  85. if (0 <= i) return; // Already added: skip
  86. m_ParticleSystems.Add(ps);
  87. _uiParticles.Clear();
  88. }
  89. public void RemoveParticleSystem(ParticleSystem ps)
  90. {
  91. if (m_ParticleSystems == null)
  92. {
  93. return;
  94. }
  95. var i = m_ParticleSystems.IndexOf(ps);
  96. if (i < 0) return; // Not found. skip
  97. m_ParticleSystems.RemoveAt(i);
  98. _uiParticles.Clear();
  99. }
  100. private void Awake()
  101. {
  102. UpgradeIfNeeded();
  103. }
  104. private void OnEnable()
  105. {
  106. UIParticleUpdater.Register(this);
  107. }
  108. private void OnDisable()
  109. {
  110. UIParticleUpdater.Unregister(this);
  111. }
  112. private void OnDestroy()
  113. {
  114. _uiParticles = null;
  115. m_ParticleSystems = null;
  116. }
  117. internal void Attract()
  118. {
  119. // Collect UIParticle if needed (same size as m_ParticleSystems)
  120. CollectUIParticlesIfNeeded();
  121. for (var particleIndex = 0; particleIndex < m_ParticleSystems.Count; particleIndex++)
  122. {
  123. var particleSystem = m_ParticleSystems[particleIndex];
  124. // Skip: The ParticleSystem is not active
  125. if (particleSystem == null || !particleSystem.gameObject.activeInHierarchy) continue;
  126. // Skip: No active particles
  127. var count = particleSystem.particleCount;
  128. if (count == 0) continue;
  129. var particles = ParticleSystemExtensions.GetParticleArray(count);
  130. particleSystem.GetParticles(particles, count);
  131. var uiParticle = _uiParticles[particleIndex];
  132. var dstPos = GetDestinationPosition(uiParticle, particleSystem);
  133. for (var i = 0; i < count; i++)
  134. {
  135. // Attracted
  136. var p = particles[i];
  137. if (0f < p.remainingLifetime && Vector3.Distance(p.position, dstPos) < m_DestinationRadius)
  138. {
  139. p.remainingLifetime = 0f;
  140. particles[i] = p;
  141. if (m_OnAttracted != null)
  142. {
  143. try
  144. {
  145. m_OnAttracted.Invoke();
  146. }
  147. catch (Exception e)
  148. {
  149. Debug.LogException(e);
  150. }
  151. }
  152. continue;
  153. }
  154. // Calc attracting time
  155. var delayTime = p.startLifetime * m_DelayRate;
  156. var duration = p.startLifetime - delayTime;
  157. var time = Mathf.Max(0, p.startLifetime - p.remainingLifetime - delayTime);
  158. // Delay
  159. if (time <= 0) continue;
  160. // Attract
  161. p.position = GetAttractedPosition(p.position, dstPos, duration, time);
  162. p.velocity *= 0.5f;
  163. particles[i] = p;
  164. }
  165. particleSystem.SetParticles(particles, count);
  166. }
  167. }
  168. private Vector3 GetDestinationPosition(UIParticle uiParticle, ParticleSystem particleSystem)
  169. {
  170. var isUI = uiParticle && uiParticle.enabled;
  171. var psPos = particleSystem.transform.position;
  172. var attractorPos = transform.position;
  173. var dstPos = attractorPos;
  174. var isLocalSpace = particleSystem.IsLocalSpace();
  175. if (isLocalSpace)
  176. {
  177. dstPos = particleSystem.transform.InverseTransformPoint(dstPos);
  178. }
  179. if (isUI)
  180. {
  181. var inverseScale = uiParticle.parentScale.Inverse();
  182. var scale3d = uiParticle.scale3DForCalc;
  183. dstPos = dstPos.GetScaled(inverseScale, scale3d.Inverse());
  184. // Relative mode
  185. if (uiParticle.positionMode == UIParticle.PositionMode.Relative)
  186. {
  187. var diff = uiParticle.transform.position - psPos;
  188. diff.Scale(scale3d - inverseScale);
  189. diff.Scale(scale3d.Inverse());
  190. dstPos += diff;
  191. }
  192. #if UNITY_EDITOR
  193. if (!Application.isPlaying && !isLocalSpace)
  194. {
  195. dstPos += psPos - psPos.GetScaled(inverseScale, scale3d.Inverse());
  196. }
  197. #endif
  198. }
  199. return dstPos;
  200. }
  201. private Vector3 GetAttractedPosition(Vector3 current, Vector3 target, float duration, float time)
  202. {
  203. var speed = m_MaxSpeed;
  204. switch (m_UpdateMode)
  205. {
  206. case UpdateMode.Normal:
  207. speed *= 60 * Time.deltaTime;
  208. break;
  209. case UpdateMode.UnscaledTime:
  210. speed *= 60 * Time.unscaledDeltaTime;
  211. break;
  212. }
  213. switch (m_Movement)
  214. {
  215. case Movement.Linear:
  216. speed /= duration;
  217. break;
  218. case Movement.Smooth:
  219. target = Vector3.Lerp(current, target, time / duration);
  220. break;
  221. case Movement.Sphere:
  222. target = Vector3.Slerp(current, target, time / duration);
  223. break;
  224. }
  225. return Vector3.MoveTowards(current, target, speed);
  226. }
  227. private void CollectUIParticlesIfNeeded()
  228. {
  229. if (m_ParticleSystems.Count == 0 || _uiParticles.Count != 0) return;
  230. // Expand capacity
  231. if (_uiParticles.Capacity < m_ParticleSystems.Capacity)
  232. {
  233. _uiParticles.Capacity = m_ParticleSystems.Capacity;
  234. }
  235. // Find UIParticle that controls the ParticleSystem
  236. for (var i = 0; i < m_ParticleSystems.Count; i++)
  237. {
  238. var ps = m_ParticleSystems[i];
  239. if (ps == null)
  240. {
  241. _uiParticles.Add(null);
  242. continue;
  243. }
  244. var uiParticle = ps.GetComponentInParent<UIParticle>(true);
  245. _uiParticles.Add(uiParticle.particles.Contains(ps) ? uiParticle : null);
  246. }
  247. }
  248. #if UNITY_EDITOR
  249. private void OnValidate()
  250. {
  251. _uiParticles.Clear();
  252. }
  253. #endif
  254. void ISerializationCallbackReceiver.OnBeforeSerialize()
  255. {
  256. UpgradeIfNeeded();
  257. }
  258. void ISerializationCallbackReceiver.OnAfterDeserialize()
  259. {
  260. }
  261. private void UpgradeIfNeeded()
  262. {
  263. // Multiple ParticleSystems support: from 'm_ParticleSystem' to 'm_ParticleSystems'
  264. if (m_ParticleSystem != null)
  265. {
  266. if (!m_ParticleSystems.Contains(m_ParticleSystem))
  267. {
  268. m_ParticleSystems.Add(m_ParticleSystem);
  269. }
  270. m_ParticleSystem = null;
  271. Debug.Log($"Upgraded!");
  272. }
  273. }
  274. }
  275. }