diff --git a/Packages/src/Editor/UIParticleEditor.cs b/Packages/src/Editor/UIParticleEditor.cs index 857af90..df1d514 100644 --- a/Packages/src/Editor/UIParticleEditor.cs +++ b/Packages/src/Editor/UIParticleEditor.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using UnityEditor; -using UnityEditor.UI; using UnityEditorInternal; using UnityEngine; using UnityEngine.UI; @@ -18,6 +17,7 @@ using Object = UnityEngine.Object; #if UNITY_2021_2_OR_NEWER using UnityEditor.SceneManagement; + #elif UNITY_2018_3_OR_NEWER using UnityEditor.Experimental.SceneManagement; #endif @@ -26,7 +26,7 @@ namespace Coffee.UIExtensions { [CustomEditor(typeof(UIParticle))] [CanEditMultipleObjects] - internal class UIParticleEditor : GraphicEditor + internal class UIParticleEditor : Editor { #if UNITY_2021_2_OR_NEWER #if UNITY_2022_1_OR_NEWER @@ -146,10 +146,8 @@ namespace Coffee.UIExtensions /// /// This function is called when the object becomes enabled and active. /// - protected override void OnEnable() + private void OnEnable() { - base.OnEnable(); - _maskable = serializedObject.FindProperty("m_Maskable"); _scale3D = serializedObject.FindProperty("m_Scale3D"); _animatableProperties = serializedObject.FindProperty("m_AnimatableProperties"); @@ -529,9 +527,7 @@ namespace Coffee.UIExtensions { if (!p || (ignoreCurrent && target == p)) return; - var cr = p.canvasRenderer; DestroyImmediate(p); - DestroyImmediate(cr); #if UNITY_2018_3_OR_NEWER var stage = PrefabStageUtility.GetCurrentPrefabStage(); diff --git a/Packages/src/Runtime/UIParticle.cs b/Packages/src/Runtime/UIParticle.cs index 1381c11..4c26549 100644 --- a/Packages/src/Runtime/UIParticle.cs +++ b/Packages/src/Runtime/UIParticle.cs @@ -6,7 +6,6 @@ using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Rendering; using UnityEngine.Serialization; -using UnityEngine.UI; using Random = UnityEngine.Random; [assembly: InternalsVisibleTo("Coffee.UIParticle.Editor")] @@ -19,7 +18,7 @@ namespace Coffee.UIExtensions [ExecuteAlways] [RequireComponent(typeof(RectTransform))] [RequireComponent(typeof(CanvasRenderer))] - public class UIParticle : MaskableGraphic, ISerializationCallbackReceiver + public class UIParticle : UIBehaviour, ISerializationCallbackReceiver { public enum AutoScalingMode { @@ -45,16 +44,19 @@ namespace Coffee.UIExtensions [HideInInspector] [SerializeField] + [Obsolete] internal bool m_IsTrail; [HideInInspector] [FormerlySerializedAs("m_IgnoreParent")] [SerializeField] + [Obsolete] private bool m_IgnoreCanvasScaler; [HideInInspector] [SerializeField] - private bool m_AbsoluteMode; + [Obsolete] + internal bool m_AbsoluteMode; [Tooltip("Particle effect scale")] [SerializeField] @@ -93,25 +95,54 @@ namespace Coffee.UIExtensions [SerializeField] [Tooltip("Prevent the root-Canvas scale from affecting the hierarchy-scaled ParticleSystem.")] - private bool m_AutoScaling = true; + [Obsolete] + internal bool m_AutoScaling; [SerializeField] [Tooltip("Transform: Transform.lossyScale (=world scale) will be set to (1, 1, 1)." + "UIParticle: UIParticle.scale will be adjusted.")] private AutoScalingMode m_AutoScalingMode = AutoScalingMode.Transform; + [SerializeField] + private bool m_Maskable = true; + private readonly List _renderers = new List(); + private Canvas _canvas; private int _groupId; private Camera _orthoCamera; private DrivenRectTransformTracker _tracker; - /// - /// Should this graphic be considered a target for ray-casting? - /// - public override bool raycastTarget + public RectTransform rectTransform => transform as RectTransform; + + public Canvas canvas { - get => false; - set { } + get + { + if (_canvas) return _canvas; + + var tr = transform; + while (tr && !_canvas) + { + if (tr.TryGetComponent(out _canvas)) return _canvas; + tr = tr.parent; + } + + return null; + } + } + + /// + /// Does this graphic allow masking. + /// + public bool maskable + { + get => m_Maskable; + set + { + if (value == m_Maskable) return; + m_Maskable = value; + UpdateRendererMaterial(); + } } /// @@ -266,8 +297,6 @@ namespace Coffee.UIExtensions } } - public override Material materialForRendering => null; - /// /// Paused. /// @@ -282,8 +311,8 @@ namespace Coffee.UIExtensions ResetGroupId(); UpdateTracker(); UIParticleUpdater.Register(this); - RegisterDirtyMaterialCallback(UpdateRendererMaterial); + // if (0 < particles.Count) { RefreshParticles(particles); @@ -293,7 +322,7 @@ namespace Coffee.UIExtensions RefreshParticles(); } - base.OnEnable(); + UpdateRendererMaterial(); } /// @@ -304,9 +333,15 @@ namespace Coffee.UIExtensions UpdateTracker(); UIParticleUpdater.Unregister(this); _renderers.ForEach(r => r.Reset()); - UnregisterDirtyMaterialCallback(UpdateRendererMaterial); + _canvas = null; + } - base.OnDisable(); + /// + /// Called when the state of the parent Canvas is changed. + /// + protected override void OnCanvasHierarchyChanged() + { + _canvas = null; } /// @@ -316,11 +351,20 @@ namespace Coffee.UIExtensions { } + /// + /// This function is called when a direct or indirect parent of the transform of the GameObject has changed. + /// + protected override void OnTransformParentChanged() + { + _canvas = null; + } + #if UNITY_EDITOR protected override void OnValidate() { base.OnValidate(); UpdateTracker(); + UpdateRendererMaterial(); } #endif @@ -330,6 +374,7 @@ namespace Coffee.UIExtensions void ISerializationCallbackReceiver.OnAfterDeserialize() { +#pragma warning disable CS0612 // Type or member is obsolete if (m_IgnoreCanvasScaler || m_AutoScaling) { m_IgnoreCanvasScaler = false; @@ -342,6 +387,7 @@ namespace Coffee.UIExtensions m_AbsoluteMode = false; m_PositionMode = PositionMode.Absolute; } +#pragma warning restore CS0612 // Type or member is obsolete } public void Play() @@ -400,9 +446,10 @@ namespace Coffee.UIExtensions { if (!instance) return; - foreach (Transform child in transform) + var childCount = transform.childCount; + for (var i = 0; i < childCount; i++) { - var go = child.gameObject; + var go = transform.GetChild(i).gameObject; go.SetActive(false); if (destroyOldParticles) { @@ -433,7 +480,14 @@ namespace Coffee.UIExtensions { if (!root) return; root.GetComponentsInChildren(true, particles); - particles.RemoveAll(x => x.GetComponentInParent(true) != this); + for (var i = particles.Count - 1; 0 <= i; i--) + { + var ps = particles[i]; + if (!ps || ps.GetComponentInParent(true) != this) + { + particles.RemoveAt(i); + } + } for (var i = 0; i < particles.Count; i++) { @@ -448,31 +502,39 @@ namespace Coffee.UIExtensions RefreshParticles(particles); } - public void RefreshParticles(List particles) + /// + /// Refresh UIParticle using a list of ParticleSystems. + /// + public void RefreshParticles(List particleSystems) { + // Collect children UIParticleRenderer components. // #246: Nullptr exceptions when using nested UIParticle components in hierarchy _renderers.Clear(); - foreach (Transform child in transform) + var childCount = transform.childCount; + for (var i = 0; i < childCount; i++) { - var uiParticleRenderer = child.GetComponent(); - - if (uiParticleRenderer != null) + var child = transform.GetChild(i); + if (child.TryGetComponent(out UIParticleRenderer uiParticleRenderer)) { _renderers.Add(uiParticleRenderer); } } + // Reset the UIParticleRenderer components. for (var i = 0; i < _renderers.Count; i++) { _renderers[i].Reset(i); } + // Set the ParticleSystem to the UIParticleRenderer. If the trail is enabled, set it additionally. var j = 0; - for (var i = 0; i < particles.Count; i++) + for (var i = 0; i < particleSystems.Count; i++) { - var ps = particles[i]; + var ps = particleSystems[i]; if (!ps) continue; GetRenderer(j++).Set(this, ps, false); + + // If the trail is enabled, set it additionally. if (ps.trails.enabled) { GetRenderer(j++).Set(this, ps, true); @@ -500,11 +562,10 @@ namespace Coffee.UIExtensions for (var i = 0; i < _renderers.Count; i++) { var r = _renderers[i]; - if (!r) - { - RefreshParticles(particles); - break; - } + if (r) continue; + + RefreshParticles(particles); + break; } var bakeCamera = GetBakeCamera(); @@ -512,6 +573,7 @@ namespace Coffee.UIExtensions { var r = _renderers[i]; if (!r) continue; + r.UpdateMesh(bakeCamera); } } @@ -523,17 +585,6 @@ namespace Coffee.UIExtensions : Random.Range(m_GroupId, m_GroupMaxId + 1); } - protected override void UpdateMaterial() - { - } - - /// - /// Call to update the geometry of the Graphic onto the CanvasRenderer. - /// - protected override void UpdateGeometry() - { - } - private void UpdateRendererMaterial() { for (var i = 0; i < _renderers.Count; i++) @@ -574,11 +625,12 @@ namespace Coffee.UIExtensions // When render mode is ScreenSpaceOverlay, use ortho-camera. if (!_orthoCamera) { - // Find existing ortho-camera. - foreach (Transform child in transform) + // Find existing orthographic-camera. + var childCount = transform.childCount; + for (var i = 0; i < childCount; i++) { - var cam = child.GetComponent(); - if (cam && cam.name == "[generated] UIParticleOverlayCamera") + if (transform.GetChild(i).TryGetComponent(out var cam) + && cam.name == "[generated] UIParticleOverlayCamera") { _orthoCamera = cam; break; @@ -600,8 +652,7 @@ namespace Coffee.UIExtensions } // - var size = ((RectTransform)root.transform).rect.size; - _orthoCamera.orthographicSize = Mathf.Max(size.x, size.y) * root.scaleFactor; + _orthoCamera.orthographicSize = 10; _orthoCamera.transform.SetPositionAndRotation(new Vector3(0, 0, -1000), Quaternion.identity); _orthoCamera.orthographic = true; _orthoCamera.farClipPlane = 2000f; @@ -612,7 +663,7 @@ namespace Coffee.UIExtensions private void UpdateTracker() { #pragma warning disable CS0618 // Type or member is obsolete - if (!enabled || !autoScaling || autoScalingMode != AutoScalingMode.Transform) + if (!enabled || autoScalingMode != AutoScalingMode.Transform) #pragma warning restore CS0618 // Type or member is obsolete { _tracker.Clear(); diff --git a/Packages/src/Runtime/UIParticleRenderer.cs b/Packages/src/Runtime/UIParticleRenderer.cs index 727e85e..b3252cf 100644 --- a/Packages/src/Runtime/UIParticleRenderer.cs +++ b/Packages/src/Runtime/UIParticleRenderer.cs @@ -33,6 +33,7 @@ namespace Coffee.UIExtensions private int _index; private bool _isTrail; private Bounds _lastBounds; + private Material _materialForRendering; private Material _modifiedMaterial; private UIParticle _parent; private ParticleSystem _particleSystem; @@ -91,6 +92,19 @@ namespace Coffee.UIExtensions } } + public override Material materialForRendering + { + get + { + if (!_materialForRendering) + { + _materialForRendering = base.materialForRendering; + } + + return _materialForRendering; + } + } + public void Reset(int index = -1) { if (_renderer) @@ -110,8 +124,7 @@ namespace Coffee.UIExtensions if (this && isActiveAndEnabled) { material = null; - workerMesh.Clear(); - canvasRenderer.SetMesh(workerMesh); + canvasRenderer.Clear(); _lastBounds = new Bounds(); enabled = false; } @@ -202,10 +215,11 @@ namespace Coffee.UIExtensions ); if (!MaterialRepository.Valid(hash, _modifiedMaterial)) { - MaterialRepository.Get(hash, ref _modifiedMaterial, () => new Material(modifiedMaterial) + MaterialRepository.Get(hash, ref _modifiedMaterial, x => new Material(x.mat) { - hideFlags = HideFlags.HideAndDontSave - }); + hideFlags = HideFlags.HideAndDontSave, + mainTexture = x.texture ? x.texture : x.mat.mainTexture + }, (mat: modifiedMaterial, texture)); } return _modifiedMaterial; @@ -232,7 +246,7 @@ namespace Coffee.UIExtensions } } - _renderer = ps.GetComponent(); + ps.TryGetComponent(out _renderer); _renderer.enabled = false; //_emitter = emitter; @@ -424,66 +438,49 @@ namespace Coffee.UIExtensions Profiler.EndSample(); + // Update animatable material properties. + Profiler.BeginSample("[UIParticleRenderer] Update Animatable Material Properties"); + UpdateMaterialProperties(); + Profiler.EndSample(); // Get grouped renderers. + Profiler.BeginSample("[UIParticleRenderer] Set Mesh"); s_Renderers.Clear(); if (_parent.useMeshSharing) { UIParticleUpdater.GetGroupedRenderers(_parent.groupId, _index, s_Renderers); } - // Set mesh to the CanvasRenderer. - Profiler.BeginSample("[UIParticleRenderer] Set Mesh"); for (var i = 0; i < s_Renderers.Count; i++) { if (s_Renderers[i] == this) continue; + s_Renderers[i].canvasRenderer.SetMesh(workerMesh); s_Renderers[i]._lastBounds = _lastBounds; + s_Renderers[i].canvasRenderer.materialCount = 1; + s_Renderers[i].canvasRenderer.SetMaterial(materialForRendering, 0); } - if (!_parent.canRender) + if (_parent.canRender) + { + canvasRenderer.SetMesh(workerMesh); + } + else { workerMesh.Clear(); } - canvasRenderer.SetMesh(workerMesh); - Profiler.EndSample(); - - // Update animatable material properties. - Profiler.BeginSample("[UIParticleRenderer] Update Animatable Material Properties"); - -#if UNITY_EDITOR - if (_modifiedMaterial != material) - { - _renderer.GetSharedMaterials(s_Materials); - material = s_Materials[_isTrail ? 1 : 0]; - s_Materials.Clear(); - SetMaterialDirty(); - } -#endif - - UpdateMaterialProperties(); - if (_parent.useMeshSharing) - { - if (!_currentMaterialForRendering) - { - _currentMaterialForRendering = materialForRendering; - } - - for (var i = 0; i < s_Renderers.Count; i++) - { - if (s_Renderers[i] == this) continue; - - s_Renderers[i].canvasRenderer.materialCount = 1; - s_Renderers[i].canvasRenderer.SetMaterial(_currentMaterialForRendering, 0); - } - } - Profiler.EndSample(); s_Renderers.Clear(); } + public override void SetMaterialDirty() + { + _materialForRendering = null; + base.SetMaterialDirty(); + } + /// /// Call to update the geometry of the Graphic onto the CanvasRenderer. /// @@ -687,12 +684,12 @@ namespace Coffee.UIExtensions if (s_Mpb.isEmpty) return; // #41: Copy the value from MaterialPropertyBlock to CanvasRenderer - if (!_modifiedMaterial) return; + if (!materialForRendering) return; for (var i = 0; i < _parent.m_AnimatableProperties.Length; i++) { var ap = _parent.m_AnimatableProperties[i]; - ap.UpdateMaterialProperties(_modifiedMaterial, s_Mpb); + ap.UpdateMaterialProperties(materialForRendering, s_Mpb); } s_Mpb.Clear();