123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- #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
- #define SERIALIZE_FIELD_MASKABLE
- #endif
- using System.Collections;
- using System.Collections.Generic;
- using System.Runtime.CompilerServices;
- using Coffee.UIParticleExtensions;
- using UnityEngine;
- using UnityEngine.Rendering;
- using UnityEngine.Serialization;
- using UnityEngine.UI;
- [assembly: InternalsVisibleTo("Fort23.Editor")]
- namespace Coffee.UIExtensions
- {
- /// <summary>
- /// Render maskable and sortable particle effect ,without Camera, RenderTexture or Canvas.
- /// </summary>
- [ExecuteInEditMode]
- [RequireComponent(typeof(RectTransform))]
- [RequireComponent(typeof(CanvasRenderer))]
- public class UIParticle : MaskableGraphic
- #if UNITY_EDITOR
- , ISerializationCallbackReceiver
- #endif
- {
- [HideInInspector] [SerializeField] internal bool m_IsTrail = false;
- [Tooltip("Ignore canvas scaler")] [SerializeField] [FormerlySerializedAs("m_IgnoreParent")]
- bool m_IgnoreCanvasScaler = true;
- [Tooltip("Particle effect scale")] [SerializeField]
- float m_Scale = 100;
- [Tooltip("Particle effect scale")] [SerializeField]
- private Vector3 m_Scale3D;
- [Tooltip("Animatable material properties. If you want to change the material properties of the ParticleSystem in Animation, enable it.")] [SerializeField]
- internal AnimatableProperty[] m_AnimatableProperties = new AnimatableProperty[0];
- [Tooltip("Particles")] [SerializeField]
- private List<ParticleSystem> m_Particles = new List<ParticleSystem>();
- [Tooltip("Shrink rendering by material on refresh.\nNOTE: Performance will be improved, but in some cases the rendering is not correct.")] [SerializeField]
- bool m_ShrinkByMaterial = false;
- #if !SERIALIZE_FIELD_MASKABLE
- [SerializeField] private bool m_Maskable = true;
- #endif
- private bool _shouldBeRemoved;
- private DrivenRectTransformTracker _tracker;
- private Mesh _bakedMesh;
- private readonly List<Material> _modifiedMaterials = new List<Material>();
- private readonly List<Material> _maskMaterials = new List<Material>();
- private readonly List<bool> _activeMeshIndices = new List<bool>();
- private Vector3 _cachedPosition;
- private static readonly List<Material> s_TempMaterials = new List<Material>(2);
- private static MaterialPropertyBlock s_Mpb;
- private static readonly List<Material> s_PrevMaskMaterials = new List<Material>();
- private static readonly List<Material> s_PrevModifiedMaterials = new List<Material>();
- private static readonly List<Component> s_Components = new List<Component>();
- private static readonly List<ParticleSystem> s_ParticleSystems = new List<ParticleSystem>();
- /// <summary>
- /// Should this graphic be considered a target for raycasting?
- /// </summary>
- public override bool raycastTarget
- {
- get { return false; }
- set { }
- }
- public bool ignoreCanvasScaler
- {
- get { return m_IgnoreCanvasScaler; }
- set
- {
- // if (m_IgnoreCanvasScaler == value) return;
- m_IgnoreCanvasScaler = value;
- _tracker.Clear();
- if (isActiveAndEnabled && m_IgnoreCanvasScaler)
- _tracker.Add(this, rectTransform, DrivenTransformProperties.Scale);
- }
- }
- public bool shrinkByMaterial
- {
- get { return m_ShrinkByMaterial; }
- set
- {
- if (m_ShrinkByMaterial == value) return;
- m_ShrinkByMaterial = value;
- RefreshParticles();
- }
- }
- /// <summary>
- /// Particle effect scale.
- /// </summary>
- public float scale
- {
- get { return m_Scale3D.x; }
- set
- {
- m_Scale = Mathf.Max(0.001f, value);
- m_Scale3D = new Vector3(m_Scale, m_Scale, m_Scale);
- }
- }
- /// <summary>
- /// Particle effect scale.
- /// </summary>
- public Vector3 scale3D
- {
- get { return m_Scale3D; }
- set
- {
- if (m_Scale3D == value) return;
- m_Scale3D.x = Mathf.Max(0.001f, value.x);
- m_Scale3D.y = Mathf.Max(0.001f, value.y);
- m_Scale3D.z = Mathf.Max(0.001f, value.z);
- }
- }
- internal Mesh bakedMesh
- {
- get { return _bakedMesh; }
- }
- public List<ParticleSystem> particles
- {
- get { return m_Particles; }
- }
- public IEnumerable<Material> materials
- {
- get { return _modifiedMaterials; }
- }
- public override Material materialForRendering
- {
- get { return canvasRenderer.GetMaterial(0); }
- }
- public List<bool> activeMeshIndices
- {
- get { return _activeMeshIndices; }
- set
- {
- if (_activeMeshIndices.SequenceEqualFast(value)) return;
- _activeMeshIndices.Clear();
- _activeMeshIndices.AddRange(value);
- UpdateMaterial();
- }
- }
- internal Vector3 cachedPosition
- {
- get { return _cachedPosition; }
- set { _cachedPosition = value; }
- }
- public void Play()
- {
- particles.Exec(p => p.Play());
- }
- public void Pause()
- {
- particles.Exec(p => p.Pause());
- }
- public void Stop()
- {
- particles.Exec(p => p.Stop());
- }
- public void Clear()
- {
- particles.Exec(p => p.Clear());
- }
- public void SetParticleSystemInstance(GameObject instance)
- {
- SetParticleSystemInstance(instance, true);
- }
- public void SetParticleSystemInstance(GameObject instance, bool destroyOldParticles)
- {
- if (!instance) return;
- foreach (Transform child in transform)
- {
- var go = child.gameObject;
- go.SetActive(false);
- if (!destroyOldParticles) continue;
- #if UNITY_EDITOR
- if (!Application.isPlaying)
- DestroyImmediate(go);
- else
- #endif
- Destroy(go);
- }
- var tr = instance.transform;
- tr.SetParent(transform, false);
- tr.localPosition = Vector3.zero;
- RefreshParticles(instance);
- }
- public void SetParticleSystemPrefab(GameObject prefab)
- {
- if (!prefab) return;
- SetParticleSystemInstance(Instantiate(prefab.gameObject), true);
- }
- public void RefreshParticles()
- {
- RefreshParticles(gameObject);
- }
- public void RefreshParticles(GameObject root)
- {
- if (!root) return;
- root.GetComponentsInChildren(particles);
- particles.RemoveAll(x => x.GetComponentInParent<UIParticle>() != this);
- foreach (var ps in particles)
- {
- var tsa = ps.textureSheetAnimation;
- if (tsa.mode == ParticleSystemAnimationMode.Sprites && tsa.uvChannelMask == (UVChannelFlags) 0)
- tsa.uvChannelMask = UVChannelFlags.UV0;
- }
- particles.Exec(p => p.GetComponent<ParticleSystemRenderer>().enabled = !enabled);
- particles.SortForRendering(transform, m_ShrinkByMaterial);
- SetMaterialDirty();
- }
- protected override void UpdateMaterial()
- {
- // Clear mask materials.
- s_PrevMaskMaterials.AddRange(_maskMaterials);
- _maskMaterials.Clear();
- // Clear modified materials.
- s_PrevModifiedMaterials.AddRange(_modifiedMaterials);
- _modifiedMaterials.Clear();
- // Recalculate stencil value.
- if (m_ShouldRecalculateStencil)
- {
- var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
- m_StencilValue = maskable ? MaskUtilities.GetStencilDepth(transform, rootCanvas) : 0;
- m_ShouldRecalculateStencil = false;
- }
- // No mesh to render.
- var count = activeMeshIndices.CountFast();
- if (count == 0 || !isActiveAndEnabled || particles.Count == 0)
- {
- canvasRenderer.Clear();
- ClearPreviousMaterials();
- return;
- }
- //
- GetComponents(typeof(IMaterialModifier), s_Components);
- var materialCount = Mathf.Min(16, count);
- canvasRenderer.materialCount = materialCount;
- var j = 0;
- for (var i = 0; i < particles.Count; i++)
- {
- if (materialCount <= j) break;
- var ps = particles[i];
- if (!ps) continue;
- var r = ps.GetComponent<ParticleSystemRenderer>();
- r.GetSharedMaterials(s_TempMaterials);
- // Main
- var index = i * 2;
- if (activeMeshIndices.Count <= index) break;
- if (activeMeshIndices[index] && 0 < s_TempMaterials.Count)
- {
- var mat = GetModifiedMaterial(s_TempMaterials[0], ps.GetTextureForSprite());
- for (var k = 1; k < s_Components.Count; k++)
- mat = (s_Components[k] as IMaterialModifier).GetModifiedMaterial(mat);
- canvasRenderer.SetMaterial(mat, j);
- UpdateMaterialProperties(r, j);
- j++;
- }
- // Trails
- index++;
- if (activeMeshIndices.Count <= index || materialCount <= j) break;
- if (activeMeshIndices[index] && 1 < s_TempMaterials.Count)
- {
- var mat = GetModifiedMaterial(s_TempMaterials[1], null);
- for (var k = 1; k < s_Components.Count; k++)
- mat = (s_Components[k] as IMaterialModifier).GetModifiedMaterial(mat);
- canvasRenderer.SetMaterial(mat, j++);
- }
- }
- ClearPreviousMaterials();
- }
- private void ClearPreviousMaterials()
- {
- foreach (var m in s_PrevMaskMaterials)
- StencilMaterial.Remove(m);
- s_PrevMaskMaterials.Clear();
- foreach (var m in s_PrevModifiedMaterials)
- ModifiedMaterial.Remove(m);
- s_PrevModifiedMaterials.Clear();
- }
- private Material GetModifiedMaterial(Material baseMaterial, Texture2D texture)
- {
- if (0 < m_StencilValue)
- {
- baseMaterial = StencilMaterial.Add(baseMaterial, (1 << m_StencilValue) - 1, StencilOp.Keep, CompareFunction.Equal, ColorWriteMask.All, (1 << m_StencilValue) - 1, 0);
- _maskMaterials.Add(baseMaterial);
- }
- if (texture == null && m_AnimatableProperties.Length == 0) return baseMaterial;
- var id = m_AnimatableProperties.Length == 0 ? 0 : GetInstanceID();
- baseMaterial = ModifiedMaterial.Add(baseMaterial, texture, id);
- _modifiedMaterials.Add(baseMaterial);
- return baseMaterial;
- }
- internal void UpdateMaterialProperties()
- {
- if (m_AnimatableProperties.Length == 0) return;
- //
- var count = activeMeshIndices.CountFast();
- var materialCount = Mathf.Max(8, count);
- canvasRenderer.materialCount = materialCount;
- var j = 0;
- for (var i = 0; i < particles.Count; i++)
- {
- if (materialCount <= j) break;
- var ps = particles[i];
- if (!ps) continue;
- var r = ps.GetComponent<ParticleSystemRenderer>();
- r.GetSharedMaterials(s_TempMaterials);
- // Main
- if (activeMeshIndices[i * 2] && 0 < s_TempMaterials.Count)
- {
- UpdateMaterialProperties(r, j);
- j++;
- }
- }
- }
- internal void UpdateMaterialProperties(Renderer r, int index)
- {
- if (m_AnimatableProperties.Length == 0 || canvasRenderer.materialCount <= index) return;
- r.GetPropertyBlock(s_Mpb ?? (s_Mpb = new MaterialPropertyBlock()));
- if (s_Mpb.isEmpty) return;
- // #41: Copy the value from MaterialPropertyBlock to CanvasRenderer
- var mat = canvasRenderer.GetMaterial(index);
- if (!mat) return;
- foreach (var ap in m_AnimatableProperties)
- {
- ap.UpdateMaterialProperties(mat, s_Mpb);
- }
- s_Mpb.Clear();
- }
- /// <summary>
- /// This function is called when the object becomes enabled and active.
- /// </summary>
- protected override void OnEnable()
- {
- #if !SERIALIZE_FIELD_MASKABLE
- maskable = m_Maskable;
- #endif
- activeMeshIndices.Clear();
- UIParticleUpdater.Register(this);
- particles.Exec(p => p.GetComponent<ParticleSystemRenderer>().enabled = false);
- if (isActiveAndEnabled && m_IgnoreCanvasScaler)
- {
- _tracker.Add(this, rectTransform, DrivenTransformProperties.Scale);
- }
- // Create objects.
- _bakedMesh = MeshPool.Rent();
- base.OnEnable();
- InitializeIfNeeded();
- }
- private new IEnumerator Start()
- {
- // #147: ParticleSystem creates Particles in wrong position during prewarm
- // #148: Particle Sub Emitter not showing when start game
- var delayToPlay = particles.AnyFast(ps =>
- {
- ps.GetComponentsInChildren(false, s_ParticleSystems);
- return s_ParticleSystems.AnyFast(p => p.isPlaying && (p.subEmitters.enabled || p.main.prewarm));
- });
- s_ParticleSystems.Clear();
- if (!delayToPlay) yield break;
- Stop();
- Clear();
- yield return null;
- Play();
- }
- /// <summary>
- /// This function is called when the behaviour becomes disabled.
- /// </summary>
- protected override void OnDisable()
- {
- UIParticleUpdater.Unregister(this);
- if (!_shouldBeRemoved)
- particles.Exec(p => p.GetComponent<ParticleSystemRenderer>().enabled = true);
- _tracker.Clear();
- // Destroy object.
- MeshPool.Return(_bakedMesh);
- _bakedMesh = null;
- base.OnDisable();
- }
- /// <summary>
- /// Call to update the geometry of the Graphic onto the CanvasRenderer.
- /// </summary>
- protected override void UpdateGeometry()
- {
- }
- /// <summary>
- /// Callback for when properties have been changed by animation.
- /// </summary>
- protected override void OnDidApplyAnimationProperties()
- {
- }
- private void InitializeIfNeeded()
- {
- if (enabled && m_IsTrail)
- {
- UnityEngine.Debug.LogWarningFormat(this, "[UIParticle] The UIParticle component should be removed: {0}\nReason: UIParticle for trails is no longer needed.", name);
- gameObject.hideFlags = HideFlags.None;
- _shouldBeRemoved = true;
- enabled = false;
- return;
- }
- if (!this || particles.AnyFast()) return;
- // refresh.
- #if UNITY_EDITOR
- if (!Application.isPlaying)
- UnityEditor.EditorApplication.delayCall += () =>
- {
- if (this) RefreshParticles();
- };
- else
- #endif
- RefreshParticles();
- }
- #if UNITY_EDITOR
- protected override void OnValidate()
- {
- SetLayoutDirty();
- SetVerticesDirty();
- m_ShouldRecalculateStencil = true;
- RecalculateClipping();
- #if !SERIALIZE_FIELD_MASKABLE
- maskable = m_Maskable;
- #endif
- }
- void ISerializationCallbackReceiver.OnBeforeSerialize()
- {
- if (Application.isPlaying) return;
- InitializeIfNeeded();
- }
- void ISerializationCallbackReceiver.OnAfterDeserialize()
- {
- if (m_Scale3D == Vector3.zero)
- {
- scale = m_Scale;
- }
- UnityEditor.EditorApplication.delayCall += () =>
- {
- if (Application.isPlaying || !this) return;
- InitializeIfNeeded();
- };
- }
- #endif
- }
- }
|