UIParticle.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. #if UNITY_2019_3_11 || UNITY_2019_3_12 || UNITY_2019_3_13 || UNITY_2019_3_14 || UNITY_2019_3_15 || UNITY_2019_4_OR_NEWER
  2. #define SERIALIZE_FIELD_MASKABLE
  3. #endif
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Runtime.CompilerServices;
  7. using Coffee.UIParticleExtensions;
  8. using UnityEngine;
  9. using UnityEngine.Rendering;
  10. using UnityEngine.Serialization;
  11. using UnityEngine.UI;
  12. [assembly: InternalsVisibleTo("Fort23.Editor")]
  13. namespace Coffee.UIExtensions
  14. {
  15. /// <summary>
  16. /// Render maskable and sortable particle effect ,without Camera, RenderTexture or Canvas.
  17. /// </summary>
  18. [ExecuteInEditMode]
  19. [RequireComponent(typeof(RectTransform))]
  20. [RequireComponent(typeof(CanvasRenderer))]
  21. public class UIParticle : MaskableGraphic
  22. #if UNITY_EDITOR
  23. , ISerializationCallbackReceiver
  24. #endif
  25. {
  26. [HideInInspector] [SerializeField] internal bool m_IsTrail = false;
  27. [Tooltip("Ignore canvas scaler")] [SerializeField] [FormerlySerializedAs("m_IgnoreParent")]
  28. bool m_IgnoreCanvasScaler = true;
  29. [Tooltip("Particle effect scale")] [SerializeField]
  30. float m_Scale = 100;
  31. [Tooltip("Particle effect scale")] [SerializeField]
  32. private Vector3 m_Scale3D;
  33. [Tooltip("Animatable material properties. If you want to change the material properties of the ParticleSystem in Animation, enable it.")] [SerializeField]
  34. internal AnimatableProperty[] m_AnimatableProperties = new AnimatableProperty[0];
  35. [Tooltip("Particles")] [SerializeField]
  36. private List<ParticleSystem> m_Particles = new List<ParticleSystem>();
  37. [Tooltip("Shrink rendering by material on refresh.\nNOTE: Performance will be improved, but in some cases the rendering is not correct.")] [SerializeField]
  38. bool m_ShrinkByMaterial = false;
  39. #if !SERIALIZE_FIELD_MASKABLE
  40. [SerializeField] private bool m_Maskable = true;
  41. #endif
  42. private bool _shouldBeRemoved;
  43. private DrivenRectTransformTracker _tracker;
  44. private Mesh _bakedMesh;
  45. private readonly List<Material> _modifiedMaterials = new List<Material>();
  46. private readonly List<Material> _maskMaterials = new List<Material>();
  47. private readonly List<bool> _activeMeshIndices = new List<bool>();
  48. private Vector3 _cachedPosition;
  49. private static readonly List<Material> s_TempMaterials = new List<Material>(2);
  50. private static MaterialPropertyBlock s_Mpb;
  51. private static readonly List<Material> s_PrevMaskMaterials = new List<Material>();
  52. private static readonly List<Material> s_PrevModifiedMaterials = new List<Material>();
  53. private static readonly List<Component> s_Components = new List<Component>();
  54. private static readonly List<ParticleSystem> s_ParticleSystems = new List<ParticleSystem>();
  55. /// <summary>
  56. /// Should this graphic be considered a target for raycasting?
  57. /// </summary>
  58. public override bool raycastTarget
  59. {
  60. get { return false; }
  61. set { }
  62. }
  63. public bool ignoreCanvasScaler
  64. {
  65. get { return m_IgnoreCanvasScaler; }
  66. set
  67. {
  68. // if (m_IgnoreCanvasScaler == value) return;
  69. m_IgnoreCanvasScaler = value;
  70. _tracker.Clear();
  71. if (isActiveAndEnabled && m_IgnoreCanvasScaler)
  72. _tracker.Add(this, rectTransform, DrivenTransformProperties.Scale);
  73. }
  74. }
  75. public bool shrinkByMaterial
  76. {
  77. get { return m_ShrinkByMaterial; }
  78. set
  79. {
  80. if (m_ShrinkByMaterial == value) return;
  81. m_ShrinkByMaterial = value;
  82. RefreshParticles();
  83. }
  84. }
  85. /// <summary>
  86. /// Particle effect scale.
  87. /// </summary>
  88. public float scale
  89. {
  90. get { return m_Scale3D.x; }
  91. set
  92. {
  93. m_Scale = Mathf.Max(0.001f, value);
  94. m_Scale3D = new Vector3(m_Scale, m_Scale, m_Scale);
  95. }
  96. }
  97. /// <summary>
  98. /// Particle effect scale.
  99. /// </summary>
  100. public Vector3 scale3D
  101. {
  102. get { return m_Scale3D; }
  103. set
  104. {
  105. if (m_Scale3D == value) return;
  106. m_Scale3D.x = Mathf.Max(0.001f, value.x);
  107. m_Scale3D.y = Mathf.Max(0.001f, value.y);
  108. m_Scale3D.z = Mathf.Max(0.001f, value.z);
  109. }
  110. }
  111. internal Mesh bakedMesh
  112. {
  113. get { return _bakedMesh; }
  114. }
  115. public List<ParticleSystem> particles
  116. {
  117. get { return m_Particles; }
  118. }
  119. public IEnumerable<Material> materials
  120. {
  121. get { return _modifiedMaterials; }
  122. }
  123. public override Material materialForRendering
  124. {
  125. get { return canvasRenderer.GetMaterial(0); }
  126. }
  127. public List<bool> activeMeshIndices
  128. {
  129. get { return _activeMeshIndices; }
  130. set
  131. {
  132. if (_activeMeshIndices.SequenceEqualFast(value)) return;
  133. _activeMeshIndices.Clear();
  134. _activeMeshIndices.AddRange(value);
  135. UpdateMaterial();
  136. }
  137. }
  138. internal Vector3 cachedPosition
  139. {
  140. get { return _cachedPosition; }
  141. set { _cachedPosition = value; }
  142. }
  143. public void Play()
  144. {
  145. particles.Exec(p => p.Play());
  146. }
  147. public void Pause()
  148. {
  149. particles.Exec(p => p.Pause());
  150. }
  151. public void Stop()
  152. {
  153. particles.Exec(p => p.Stop());
  154. }
  155. public void Clear()
  156. {
  157. particles.Exec(p => p.Clear());
  158. }
  159. public void SetParticleSystemInstance(GameObject instance)
  160. {
  161. SetParticleSystemInstance(instance, true);
  162. }
  163. public void SetParticleSystemInstance(GameObject instance, bool destroyOldParticles)
  164. {
  165. if (!instance) return;
  166. foreach (Transform child in transform)
  167. {
  168. var go = child.gameObject;
  169. go.SetActive(false);
  170. if (!destroyOldParticles) continue;
  171. #if UNITY_EDITOR
  172. if (!Application.isPlaying)
  173. DestroyImmediate(go);
  174. else
  175. #endif
  176. Destroy(go);
  177. }
  178. var tr = instance.transform;
  179. tr.SetParent(transform, false);
  180. tr.localPosition = Vector3.zero;
  181. RefreshParticles(instance);
  182. }
  183. public void SetParticleSystemPrefab(GameObject prefab)
  184. {
  185. if (!prefab) return;
  186. SetParticleSystemInstance(Instantiate(prefab.gameObject), true);
  187. }
  188. public void RefreshParticles()
  189. {
  190. RefreshParticles(gameObject);
  191. }
  192. public void RefreshParticles(GameObject root)
  193. {
  194. if (!root) return;
  195. root.GetComponentsInChildren(particles);
  196. particles.RemoveAll(x => x.GetComponentInParent<UIParticle>() != this);
  197. foreach (var ps in particles)
  198. {
  199. var tsa = ps.textureSheetAnimation;
  200. if (tsa.mode == ParticleSystemAnimationMode.Sprites && tsa.uvChannelMask == (UVChannelFlags) 0)
  201. tsa.uvChannelMask = UVChannelFlags.UV0;
  202. }
  203. particles.Exec(p => p.GetComponent<ParticleSystemRenderer>().enabled = !enabled);
  204. particles.SortForRendering(transform, m_ShrinkByMaterial);
  205. SetMaterialDirty();
  206. }
  207. protected override void UpdateMaterial()
  208. {
  209. // Clear mask materials.
  210. s_PrevMaskMaterials.AddRange(_maskMaterials);
  211. _maskMaterials.Clear();
  212. // Clear modified materials.
  213. s_PrevModifiedMaterials.AddRange(_modifiedMaterials);
  214. _modifiedMaterials.Clear();
  215. // Recalculate stencil value.
  216. if (m_ShouldRecalculateStencil)
  217. {
  218. var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
  219. m_StencilValue = maskable ? MaskUtilities.GetStencilDepth(transform, rootCanvas) : 0;
  220. m_ShouldRecalculateStencil = false;
  221. }
  222. // No mesh to render.
  223. var count = activeMeshIndices.CountFast();
  224. if (count == 0 || !isActiveAndEnabled || particles.Count == 0)
  225. {
  226. canvasRenderer.Clear();
  227. ClearPreviousMaterials();
  228. return;
  229. }
  230. //
  231. GetComponents(typeof(IMaterialModifier), s_Components);
  232. var materialCount = Mathf.Min(16, count);
  233. canvasRenderer.materialCount = materialCount;
  234. var j = 0;
  235. for (var i = 0; i < particles.Count; i++)
  236. {
  237. if (materialCount <= j) break;
  238. var ps = particles[i];
  239. if (!ps) continue;
  240. var r = ps.GetComponent<ParticleSystemRenderer>();
  241. r.GetSharedMaterials(s_TempMaterials);
  242. // Main
  243. var index = i * 2;
  244. if (activeMeshIndices.Count <= index) break;
  245. if (activeMeshIndices[index] && 0 < s_TempMaterials.Count)
  246. {
  247. var mat = GetModifiedMaterial(s_TempMaterials[0], ps.GetTextureForSprite());
  248. for (var k = 1; k < s_Components.Count; k++)
  249. mat = (s_Components[k] as IMaterialModifier).GetModifiedMaterial(mat);
  250. canvasRenderer.SetMaterial(mat, j);
  251. UpdateMaterialProperties(r, j);
  252. j++;
  253. }
  254. // Trails
  255. index++;
  256. if (activeMeshIndices.Count <= index || materialCount <= j) break;
  257. if (activeMeshIndices[index] && 1 < s_TempMaterials.Count)
  258. {
  259. var mat = GetModifiedMaterial(s_TempMaterials[1], null);
  260. for (var k = 1; k < s_Components.Count; k++)
  261. mat = (s_Components[k] as IMaterialModifier).GetModifiedMaterial(mat);
  262. canvasRenderer.SetMaterial(mat, j++);
  263. }
  264. }
  265. ClearPreviousMaterials();
  266. }
  267. private void ClearPreviousMaterials()
  268. {
  269. foreach (var m in s_PrevMaskMaterials)
  270. StencilMaterial.Remove(m);
  271. s_PrevMaskMaterials.Clear();
  272. foreach (var m in s_PrevModifiedMaterials)
  273. ModifiedMaterial.Remove(m);
  274. s_PrevModifiedMaterials.Clear();
  275. }
  276. private Material GetModifiedMaterial(Material baseMaterial, Texture2D texture)
  277. {
  278. if (0 < m_StencilValue)
  279. {
  280. baseMaterial = StencilMaterial.Add(baseMaterial, (1 << m_StencilValue) - 1, StencilOp.Keep, CompareFunction.Equal, ColorWriteMask.All, (1 << m_StencilValue) - 1, 0);
  281. _maskMaterials.Add(baseMaterial);
  282. }
  283. if (texture == null && m_AnimatableProperties.Length == 0) return baseMaterial;
  284. var id = m_AnimatableProperties.Length == 0 ? 0 : GetInstanceID();
  285. baseMaterial = ModifiedMaterial.Add(baseMaterial, texture, id);
  286. _modifiedMaterials.Add(baseMaterial);
  287. return baseMaterial;
  288. }
  289. internal void UpdateMaterialProperties()
  290. {
  291. if (m_AnimatableProperties.Length == 0) return;
  292. //
  293. var count = activeMeshIndices.CountFast();
  294. var materialCount = Mathf.Max(8, count);
  295. canvasRenderer.materialCount = materialCount;
  296. var j = 0;
  297. for (var i = 0; i < particles.Count; i++)
  298. {
  299. if (materialCount <= j) break;
  300. var ps = particles[i];
  301. if (!ps) continue;
  302. var r = ps.GetComponent<ParticleSystemRenderer>();
  303. r.GetSharedMaterials(s_TempMaterials);
  304. // Main
  305. if (activeMeshIndices[i * 2] && 0 < s_TempMaterials.Count)
  306. {
  307. UpdateMaterialProperties(r, j);
  308. j++;
  309. }
  310. }
  311. }
  312. internal void UpdateMaterialProperties(Renderer r, int index)
  313. {
  314. if (m_AnimatableProperties.Length == 0 || canvasRenderer.materialCount <= index) return;
  315. r.GetPropertyBlock(s_Mpb ?? (s_Mpb = new MaterialPropertyBlock()));
  316. if (s_Mpb.isEmpty) return;
  317. // #41: Copy the value from MaterialPropertyBlock to CanvasRenderer
  318. var mat = canvasRenderer.GetMaterial(index);
  319. if (!mat) return;
  320. foreach (var ap in m_AnimatableProperties)
  321. {
  322. ap.UpdateMaterialProperties(mat, s_Mpb);
  323. }
  324. s_Mpb.Clear();
  325. }
  326. /// <summary>
  327. /// This function is called when the object becomes enabled and active.
  328. /// </summary>
  329. protected override void OnEnable()
  330. {
  331. #if !SERIALIZE_FIELD_MASKABLE
  332. maskable = m_Maskable;
  333. #endif
  334. activeMeshIndices.Clear();
  335. UIParticleUpdater.Register(this);
  336. particles.Exec(p => p.GetComponent<ParticleSystemRenderer>().enabled = false);
  337. if (isActiveAndEnabled && m_IgnoreCanvasScaler)
  338. {
  339. _tracker.Add(this, rectTransform, DrivenTransformProperties.Scale);
  340. }
  341. // Create objects.
  342. _bakedMesh = MeshPool.Rent();
  343. base.OnEnable();
  344. InitializeIfNeeded();
  345. }
  346. private new IEnumerator Start()
  347. {
  348. // #147: ParticleSystem creates Particles in wrong position during prewarm
  349. // #148: Particle Sub Emitter not showing when start game
  350. var delayToPlay = particles.AnyFast(ps =>
  351. {
  352. ps.GetComponentsInChildren(false, s_ParticleSystems);
  353. return s_ParticleSystems.AnyFast(p => p.isPlaying && (p.subEmitters.enabled || p.main.prewarm));
  354. });
  355. s_ParticleSystems.Clear();
  356. if (!delayToPlay) yield break;
  357. Stop();
  358. Clear();
  359. yield return null;
  360. Play();
  361. }
  362. /// <summary>
  363. /// This function is called when the behaviour becomes disabled.
  364. /// </summary>
  365. protected override void OnDisable()
  366. {
  367. UIParticleUpdater.Unregister(this);
  368. if (!_shouldBeRemoved)
  369. particles.Exec(p => p.GetComponent<ParticleSystemRenderer>().enabled = true);
  370. _tracker.Clear();
  371. // Destroy object.
  372. MeshPool.Return(_bakedMesh);
  373. _bakedMesh = null;
  374. base.OnDisable();
  375. }
  376. /// <summary>
  377. /// Call to update the geometry of the Graphic onto the CanvasRenderer.
  378. /// </summary>
  379. protected override void UpdateGeometry()
  380. {
  381. }
  382. /// <summary>
  383. /// Callback for when properties have been changed by animation.
  384. /// </summary>
  385. protected override void OnDidApplyAnimationProperties()
  386. {
  387. }
  388. private void InitializeIfNeeded()
  389. {
  390. if (enabled && m_IsTrail)
  391. {
  392. UnityEngine.Debug.LogWarningFormat(this, "[UIParticle] The UIParticle component should be removed: {0}\nReason: UIParticle for trails is no longer needed.", name);
  393. gameObject.hideFlags = HideFlags.None;
  394. _shouldBeRemoved = true;
  395. enabled = false;
  396. return;
  397. }
  398. if (!this || particles.AnyFast()) return;
  399. // refresh.
  400. #if UNITY_EDITOR
  401. if (!Application.isPlaying)
  402. UnityEditor.EditorApplication.delayCall += () =>
  403. {
  404. if (this) RefreshParticles();
  405. };
  406. else
  407. #endif
  408. RefreshParticles();
  409. }
  410. #if UNITY_EDITOR
  411. protected override void OnValidate()
  412. {
  413. SetLayoutDirty();
  414. SetVerticesDirty();
  415. m_ShouldRecalculateStencil = true;
  416. RecalculateClipping();
  417. #if !SERIALIZE_FIELD_MASKABLE
  418. maskable = m_Maskable;
  419. #endif
  420. }
  421. void ISerializationCallbackReceiver.OnBeforeSerialize()
  422. {
  423. if (Application.isPlaying) return;
  424. InitializeIfNeeded();
  425. }
  426. void ISerializationCallbackReceiver.OnAfterDeserialize()
  427. {
  428. if (m_Scale3D == Vector3.zero)
  429. {
  430. scale = m_Scale;
  431. }
  432. UnityEditor.EditorApplication.delayCall += () =>
  433. {
  434. if (Application.isPlaying || !this) return;
  435. InitializeIfNeeded();
  436. };
  437. }
  438. #endif
  439. }
  440. }