From 4d0cfd3510b4007ddaf0bcc6164e9e294291d107 Mon Sep 17 00:00:00 2001 From: mob-sakai <12690315+mob-sakai@users.noreply.github.com> Date: Thu, 27 Jun 2024 11:38:28 +0900 Subject: [PATCH] refactor --- .releaserc.json | 2 +- Editor/AnimatablePropertyEditor.cs | 41 ++++--- Editor/UIParticleEditor.cs | 47 +++++--- README.md | 152 +++++++++++++----------- Runtime/UIParticle.cs | 183 ++++++++++++++++++++--------- Runtime/UIParticleRenderer.cs | 29 ++--- Runtime/UIParticleUpdater.cs | 3 +- 7 files changed, 275 insertions(+), 182 deletions(-) diff --git a/.releaserc.json b/.releaserc.json index 2d38f8e..7ea04ae 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -4,7 +4,7 @@ "release-4.x", { "name": "release-preview", - "prerelease": true + "prerelease": "preview" } ], "plugins": [ diff --git a/Editor/AnimatablePropertyEditor.cs b/Editor/AnimatablePropertyEditor.cs index 036e308..9e28284 100644 --- a/Editor/AnimatablePropertyEditor.cs +++ b/Editor/AnimatablePropertyEditor.cs @@ -31,35 +31,35 @@ namespace Coffee.UIExtensions } else { - result.Aggregate(s_Sb, (a, b) => s_Sb.AppendFormat("{0}, ", b)); + result.Aggregate(s_Sb, (a, b) => + { + s_Sb.Append(b); + return s_Sb.Append(", "); + }); s_Sb.Length -= 2; } return s_Sb.ToString(); } - public static void Draw(SerializedProperty sp, Material[] mats) + public static void Draw(SerializedProperty sp, List mats) { - bool isClicked; - using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(false))) - { - var pos = EditorGUILayout.GetControlRect(true); - var label = new GUIContent(sp.displayName, sp.tooltip); - var rect = EditorGUI.PrefixLabel(pos, label); - var text = sp.hasMultipleDifferentValues - ? "-" - : CollectActiveNames(sp, s_ActiveNames); - isClicked = GUI.Button(rect, text, EditorStyles.popup); - } + var pos = EditorGUILayout.GetControlRect(true); + var label = new GUIContent(sp.displayName, sp.tooltip); + var rect = EditorGUI.PrefixLabel(pos, label); + var text = sp.hasMultipleDifferentValues + ? "-" + : CollectActiveNames(sp, s_ActiveNames); - if (!isClicked) return; + if (!GUI.Button(rect, text, EditorStyles.popup)) return; var gm = new GenericMenu(); - gm.AddItem(s_ContentNothing, s_ActiveNames.Count == 0, () => + gm.AddItem(s_ContentNothing, s_ActiveNames.Count == 0, x => { - sp.ClearArray(); - sp.serializedObject.ApplyModifiedProperties(); - }); + var current = (SerializedProperty)x; + current.ClearArray(); + current.serializedObject.ApplyModifiedProperties(); + }, sp); if (!sp.hasMultipleDifferentValues) { @@ -73,7 +73,7 @@ namespace Coffee.UIExtensions } s_Names.Clear(); - for (var j = 0; j < mats.Length; j++) + for (var j = 0; j < mats.Count; j++) { var mat = mats[j]; if (!mat || !mat.shader) continue; @@ -82,8 +82,7 @@ namespace Coffee.UIExtensions { var name = ShaderUtil.GetPropertyName(mat.shader, i); var type = (AnimatableProperty.ShaderPropertyType)ShaderUtil.GetPropertyType(mat.shader, i); - if (s_Names.Contains(name)) continue; - s_Names.Add(name); + if (!s_Names.Add(name)) continue; AddMenu(gm, sp, new ShaderProperty(name, type), true); diff --git a/Editor/UIParticleEditor.cs b/Editor/UIParticleEditor.cs index f64c3e9..e26db43 100644 --- a/Editor/UIParticleEditor.cs +++ b/Editor/UIParticleEditor.cs @@ -5,6 +5,7 @@ using UnityEditor; using UnityEditor.UI; using UnityEditorInternal; using UnityEngine; +using UnityEngine.Profiling; using UnityEngine.UI; using Coffee.UIParticleExtensions; #if UNITY_2021_2_OR_NEWER @@ -42,6 +43,8 @@ namespace Coffee.UIExtensions private static readonly GUIContent s_ContentRandom = new GUIContent("Random"); private static readonly GUIContent s_ContentScale = new GUIContent("Scale"); private static readonly GUIContent s_ContentPrimary = new GUIContent("Primary"); + private static readonly Regex s_RegexBuiltInGuid = new Regex(@"^0{16}.0{15}$", RegexOptions.Compiled); + private static readonly List s_TempMaterials = new List(); private static bool s_XYZMode; private SerializedProperty _maskable; @@ -169,6 +172,7 @@ namespace Coffee.UIExtensions var current = target as UIParticle; if (!current) return; + Profiler.BeginSample("(UIP:E) OnInspectorGUI"); serializedObject.Update(); // Maskable @@ -180,13 +184,8 @@ namespace Coffee.UIExtensions EditorGUI.EndDisabledGroup(); // AnimatableProperties - var mats = current.particles - .Where(x => x) - .Select(x => x.GetComponent().sharedMaterial) - .Where(x => x) - .ToArray(); - - AnimatablePropertyEditor.Draw(_animatableProperties, mats); + current.GetMaterials(s_TempMaterials); + AnimatablePropertyEditor.Draw(_animatableProperties, s_TempMaterials); // Mesh sharing EditorGUI.BeginChangeCheck(); @@ -194,9 +193,12 @@ namespace Coffee.UIExtensions if (EditorGUI.EndChangeCheck()) { serializedObject.ApplyModifiedProperties(); - foreach (var uip in targets.OfType()) + foreach (var t in targets) { - uip.ResetGroupId(); + if (t is UIParticle uip) + { + uip.ResetGroupId(); + } } } @@ -204,7 +206,7 @@ namespace Coffee.UIExtensions EditorGUILayout.PropertyField(_positionMode); // Auto Scaling - DrawAutoScaling(_autoScalingMode, targets.OfType()); + EditorGUILayout.PropertyField(_autoScalingMode); // Custom View Size EditorGUILayout.PropertyField(_useCustomView); @@ -221,9 +223,7 @@ namespace Coffee.UIExtensions // Target ParticleSystems. EditorGUI.BeginChangeCheck(); - EditorGUI.BeginDisabledGroup(targets.OfType().Any(x => !x.canvas)); _ro.DoLayoutList(); - EditorGUI.EndDisabledGroup(); serializedObject.ApplyModifiedProperties(); if (EditorGUI.EndChangeCheck()) @@ -236,7 +236,8 @@ namespace Coffee.UIExtensions } // Non-UI built-in shader is not supported. - foreach (var mat in current.materials) + Profiler.BeginSample("(UIP:E) Non-UI built-in shader is not supported."); + foreach (var mat in s_TempMaterials) { if (!mat || !mat.shader) continue; var shader = mat.shader; @@ -249,15 +250,18 @@ namespace Coffee.UIExtensions } } + Profiler.EndSample(); + // Does the shader support UI masks? + Profiler.BeginSample("(UIP:E) Does the shader support UI masks?"); if (current.maskable && current.GetComponentInParent(false)) { - foreach (var mat in current.materials) + foreach (var mat in s_TempMaterials) { if (!mat || !mat.shader) continue; var shader = mat.shader; - if (s_Shaders.Contains(shader)) continue; - s_Shaders.Add(shader); + if (!s_Shaders.Add(shader)) continue; + foreach (var propName in s_MaskablePropertyNames) { if (mat.HasProperty(propName)) continue; @@ -271,7 +275,9 @@ namespace Coffee.UIExtensions } } + s_TempMaterials.Clear(); s_Shaders.Clear(); + Profiler.EndSample(); // UIParticle for trail should be removed. var label = "This UIParticle component should be removed. The UIParticle for trails is no longer needed."; @@ -310,12 +316,15 @@ namespace Coffee.UIExtensions } } #endif + Profiler.EndSample(); + EditorApplication.delayCall += () => Profiler.enabled = false; } private static bool IsBuiltInObject(Object obj) { - return AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long _) - && Regex.IsMatch(guid, "^0{16}.0{15}$", RegexOptions.Compiled); + return AssetDatabase.IsMainAsset(obj) + && AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long _) + && s_RegexBuiltInGuid.IsMatch(guid); } #if UNITY_2018 || UNITY_2019 @@ -419,7 +428,7 @@ namespace Coffee.UIExtensions return showMax; } - private static void DrawAutoScaling(SerializedProperty prop, IEnumerable uiParticles) + private static void DrawAutoScaling(SerializedProperty prop) { EditorGUILayout.PropertyField(prop); } diff --git a/README.md b/README.md index cfaae1a..17b5980 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Particle Effect For UGUI (UI Particle) +# UIParticleIcon Particle Effect For UGUI (UI Particle) This package provides a component to render particle effects for uGUI in Unity 2018.2 or later. The particle rendering is maskable and sortable, without the need for an extra Camera, RenderTexture, or Canvas. @@ -17,45 +17,36 @@ The particle rendering is maskable and sortable, without the need for an extra C ## 📝 Description -![](https://user-images.githubusercontent.com/12690315/41771577-8da4b968-7650-11e8-9524-cd162c422d9d.gif) +![Demo](https://user-images.githubusercontent.com/12690315/41771577-8da4b968-7650-11e8-9524-cd162c422d9d.gif) -This package utilizes the new APIs `MeshBake/MashTrailBake` (introduced with Unity 2018.2) to render particles through -CanvasRenderer. -You can render, mask, and sort your ParticleSystems for UI without the necessity of an additional Camera, RenderTexture, -or Canvas. +This package uses the new APIs `MeshBake/MeshTrailBake` (introduced in Unity 2018.2) to render particles through CanvasRenderer. +You can render, mask, and sort your ParticleSystems for UI without the need for an additional Camera, RenderTexture, or Canvas. -### Features +### Key Features -* Easy to use: The package is ready to use out of the box. -* Sort particle effects and other UI by sibling index. -* No extra Camera, RenderTexture, or Canvas required. -* Masking options for Mask or RectMask2D. -* Support for the Trail module. -* Support for CanvasGroup alpha. -* No allocations needed to render particles. -* Compatibility with overlay, camera space, and world space. -* Support for Universal Render Pipeline (URP) and High Definition Render Pipeline (HDRP). -* Support for disabling `Enter Play Mode Options > Reload Domain`. -* Support for changing material property with AnimationClip (AnimatableProperty). - ![AnimatableProperty.gif][AnimatableProperty.gif] -* [4.0.0+] Support for 8+ materials. -* [4.0.0+] Correct world space particle position adjustment when changing window size for standalone platforms (Windows, - MacOSX, and Linux). -* [4.0.0+] Adaptive scaling for UI. -* [4.0.0+] Mesh sharing group to improve performance. - ![MeshSharing.gif][MeshSharing.gif] -* [4.0.0+] Particle attractor component. - ![ParticleAttractor.gif][ParticleAttractor.gif] -* [4.1.0+] Relative/Absolute particle position mode. - ![AbsolutePosition.gif][AbsolutePosition.gif] - -[AnimatableProperty.gif]: https://user-images.githubusercontent.com/12690315/53286323-2d94a980-37b0-11e9-8afb-c4a207805ff2.gif - -[MeshSharing.gif]: https://user-images.githubusercontent.com/12690315/174311048-c882df81-6c34-4eba-b0aa-5645457692f1.gif - -[ParticleAttractor.gif]: https://user-images.githubusercontent.com/12690315/174311027-462929a4-13f0-4ec4-86ea-9c832f2eecf1.gif - -[AbsolutePosition.gif]: https://user-images.githubusercontent.com/12690315/175751579-5a2357e8-2ecf-4afd-83c8-66e9771bde39.gif +* **Easy to use:** The package is ready to use out of the box. +* **Sortable:** Sort particle effects and other UI elements by sibling index. +* **Maskable:** Supports `Mask` or `RectMask2D`. +* **No extra components required:** No need for an additional `Camera`, `RenderTexture`, or `Canvas`. +* **Trail module support:** Fully supports the Trail module. +* **CanvasGroup alpha support:** Integrates with `CanvasGroup` alpha. +* **No allocations:** Efficiently renders particles without allocations. +* **Any canvas render mode support:** Works with overlay, camera space, and world space. +* **Any Render pipeline support:** Compatible with Universal Render Pipeline (URP) and High Definition Render Pipeline (HDRP). +* **Disabling domain reload support:** Supports disabling `Enter Play Mode Options > Reload Domain`. +* **Animatable material properties:** Supports changing material properties with AnimationClip (AnimatableProperty). + ![AnimatableProperty.gif](https://user-images.githubusercontent.com/12690315/53286323-2d94a980-37b0-11e9-8afb-c4a207805ff2.gif) +* **Multiple materials:** Supports 8+ materials. +* **Correct positioning:** Adjusts world space particle positions correctly when changing window size for standalone platforms (Windows, MacOSX, and Linux). +* **Adaptive scaling:** Provides adaptive scaling for UI (AutoScalingMode). +* **Performance optimization:** Mesh sharing group to improve performance. + MeshSharing.gif +* **Particle attractor:** Includes a particle attractor component. + ParticleAttractor.gif +* **Emission position mode:** Supports relative/absolute particle emission position modes. + AbsolutePosition.gif +* **Custom view size:** Fixes min/max particle size mismatch. + ![CustomViewSize.gif](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/dd929959-1a37-420b-b13d-e849022b9c9d)

@@ -76,44 +67,55 @@ or Canvas. [JMO]: https://assetstore.unity.com/publishers/1669 -

## ⚙ Installation -_This package requires Unity 2018.3 or later._ +_This package requires **Unity 2018.3 or later**._ #### Install via OpenUPM -This package is available on [OpenUPM](https://openupm.com) package registry. -This is the preferred method of installation, as you can easily receive updates as they're released. +- This package is available on [OpenUPM](https://openupm.com) package registry. +- This is the preferred method of installation, as you can easily receive updates as they're released. +- If you have [openupm-cli](https://github.com/openupm/openupm-cli) installed, then run the following command in your project's directory: + ``` + openupm add com.coffee.ui-particle + ``` +- To update the package, use Package Manager UI (`Window > Package Manager`) or run the following command with `@{version}`: + ``` + openupm add com.coffee.ui-particle@4.8.0 + ``` -If you have [openupm-cli](https://github.com/openupm/openupm-cli) installed, then run the following command in your -project's directory: +#### Install via UPM (with Package Manager UI) -```sh -openupm add com.coffee.ui-particle -``` +- Click `Window > Package Manager` to open Package Manager UI. +- Click `+ > Add package from git URL...` and input the repository URL: `https://github.com/mob-sakai/ParticleEffectForUGUI.git` + ![](https://gist.github.com/assets/12690315/24af63ed-8a2e-483d-9023-7aa53d913330) +- To update the package, change suffix `#{version}` to the target version. + - e.g. `https://github.com/mob-sakai/ParticleEffectForUGUI.git#4.8.0` -#### Install via UPM (using Git URL) +#### Install via UPM (Manually) -Navigate to your project's Packages folder and open the `manifest.json` file. Then add this package somewhere in -the `dependencies` block: - -```json -{ - "dependencies": { - "com.coffee.ui-particle": "https://github.com/mob-sakai/ParticleEffectForUGUI.git", - ... +- Open the `Packages/manifest.json` file in your project. Then add this package somewhere in the `dependencies` block: + ```json + { + "dependencies": { + "com.coffee.ui-particle": "https://github.com/mob-sakai/ParticleEffectForUGUI.git", + ... + } } -} -``` + ``` -To update the package, change suffix `#{version}` to the target version. +- To update the package, change suffix `#{version}` to the target version. + - e.g. `"com.coffee.ui-particle": "https://github.com/mob-sakai/ParticleEffectForUGUI.git#4.8.0",` -* e.g. `"com.coffee.ui-particle": "https://github.com/mob-sakai/ParticleEffectForUGUI.git#4.6.0",` +#### Install as Embedded Package -Or, use [UpmGitExtension](https://github.com/mob-sakai/UpmGitExtension) to install and update the package. +1. Download a source code zip file from [Releases](https://github.com/mob-sakai/ParticleEffectForUGUI.git/releases) and extract it. +2. Place it in your project's `Packages` directory. + ![](https://github.com/mob-sakai/mob-sakai/assets/12690315/0b7484b4-5fca-43b0-a9ef-e5dbd99bcdb4) +- If you want to fix bugs or add features, install it as an embedded package. +- To update the package, you need to re-download it and replace the contents.

@@ -123,19 +125,28 @@ Or, use [UpmGitExtension](https://github.com/mob-sakai/UpmGitExtension) to insta `UIParticle` controls the ParticleSystems that are attached to its own game objects and child game objects. -![](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/3559df45-63e7-4c4c-9233-f455779efa29) +![](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/1cf5753b-33fc-4cef-91c3-413c515a954f) -- **Maskable**: Does this graphic allow masking. -- **Scale**: Scale the rendering. When the `3D` toggle is enabled, 3D scale (x, y, z) is supported. +- **Maskable**: Does this graphic allow maskable. +- **Scale**: Scale the rendering particles. When the `3D` toggle is enabled, 3D scale (x, y, z) is supported. - **Animatable Properties**: If you want to update material properties (e.g., `_MainTex_ST`, `_Color`) in AnimationClip, - use this to mark the changes. + use this to mark as animatable. - **Mesh Sharing**: Particle simulation results are shared within the same group. A large number of the same effects can be displayed with a small load. When the `Random` toggle is enabled, it will be grouped randomly. + - **None:** Disable mesh sharing. + - **Auto:** Automatically select Primary/Replica. + - **Primary:** Provides particle simulation results to the same group. + - **Primary Simulator:** Primary, but do not render the particle (simulation only). + - **Replica:** Render simulation results provided by the primary. - **Position Mode**: Emission position mode. - - **Absolute:** Emit from the world position of the `ParticleSystem`. - - **Relative:** Emit from the scaled position of the `ParticleSystem`. -- **Auto Scaling**: `Transform.lossyScale` (=world scale) will be set to `(1, 1, 1)` on update. It prevents the - root-Canvas scale from affecting the hierarchy-scaled `ParticleSystem`. + - **Absolute:** The particles will be emitted from the world position. + - **Relative:** The particles will be emitted from the scaled position. +- **Auto Scaling Mode**: How to automatically adjust when the Canvas scale is changed by the screen size or reference resolution. + - **None:** Do nothing. + - **Transform:** Transform.lossyScale (=world scale) will be set to (1, 1, 1). + - **UIParticle:** UIParticle.scale will be adjusted. +- **Use Custom View:** Use this if the particles are not displayed correctly due to min/max particle size. + - **Custom view size:** Change the bake view size. - **Rendering Order**: The ParticleSystem list to be rendered. You can change the order and the materials. **NOTE:** Press the `Refresh` button to reconstruct the rendering order based on children ParticleSystem's sorting order @@ -176,9 +187,10 @@ section. ### Script usage ```cs -// Instant ParticleSystem prefab with UIParticle on runtime. +// Instantiate ParticleSystem prefab with UIParticle on runtime. var go = GameObject.Instantiate(prefab); var uiParticle = go.AddComponent(); +uiParticle.scale = 100; // Control by ParticleSystem. particleSystem.Play(); @@ -195,7 +207,7 @@ uiParticle.Stop(); `UIParticleAttractor` attracts particles generated by the specified ParticleSystem. -![](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/ea6ae0ed-f9a8-437c-8baa-47526303391e) +![](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/5c20ad73-4b9a-4f38-9cdc-119df5cce077) ![](https://user-images.githubusercontent.com/12690315/174311027-462929a4-13f0-4ec4-86ea-9c832f2eecf1.gif) - **Particle System**: Attracts particles generated by the specified particle system. diff --git a/Runtime/UIParticle.cs b/Runtime/UIParticle.cs index d0e9e8a..41aa22e 100644 --- a/Runtime/UIParticle.cs +++ b/Runtime/UIParticle.cs @@ -44,23 +44,26 @@ 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")] + [Tooltip("Scale the rendering particles. When the `3D` toggle is enabled, 3D scale (x, y, z) is supported.")] [SerializeField] private Vector3 m_Scale3D = new Vector3(10, 10, 10); - [Tooltip("Animatable material properties.\n" + - "If you want to change the material properties of the ParticleSystem in Animation, enable it.")] + [Tooltip("If you want to update material properties (e.g. _MainTex_ST, _Color) in AnimationClip, " + + "use this to mark as animatable.")] [SerializeField] internal AnimatableProperty[] m_AnimatableProperties = new AnimatableProperty[0]; @@ -68,12 +71,13 @@ namespace Coffee.UIExtensions [SerializeField] private List m_Particles = new List(); - [Tooltip("Mesh sharing.\n" + - "None: disable mesh sharing.\n" + - "Auto: automatically select Primary/Replica.\n" + - "Primary: provides particle simulation results to the same group.\n" + + [Tooltip("Particle simulation results are shared within the same group. " + + "A large number of the same effects can be displayed with a small load.\n" + + "None: Disable mesh sharing.\n" + + "Auto: Automatically select Primary/Replica.\n" + + "Primary: Provides particle simulation results to the same group.\n" + "Primary Simulator: Primary, but do not render the particle (simulation only).\n" + - "Replica: render simulation results provided by the primary.")] + "Replica: Render simulation results provided by the primary.")] [SerializeField] private MeshSharing m_MeshSharing = MeshSharing.None; @@ -85,18 +89,22 @@ namespace Coffee.UIExtensions [SerializeField] private int m_GroupMaxId; - [Tooltip("Relative: The particles will be emitted from the scaled position of ParticleSystem.\n" + - "Absolute: The particles will be emitted from the world position of ParticleSystem.")] + [Tooltip("Emission position mode.\n" + + "Relative: The particles will be emitted from the scaled position.\n" + + "Absolute: The particles will be emitted from the world position.")] [SerializeField] private PositionMode m_PositionMode = PositionMode.Relative; [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.")] + [Tooltip( + "How to automatically adjust when the Canvas scale is changed by the screen size or reference resolution.\n" + + "None: Do nothing.\n" + + "Transform: Transform.lossyScale (=world scale) will be set to (1, 1, 1).\n" + + "UIParticle: UIParticle.scale will be adjusted.")] private AutoScalingMode m_AutoScalingMode = AutoScalingMode.Transform; [SerializeField] @@ -110,11 +118,12 @@ namespace Coffee.UIExtensions private float m_CustomViewSize = 10; private readonly List _renderers = new List(); - private int _groupId; private Camera _bakeCamera; - private DrivenRectTransformTracker _tracker; - private Vector3 _storedScale; + private Canvas _canvas; + private int _groupId; private bool _isScaleStored; + private Vector3 _storedScale; + private DrivenRectTransformTracker _tracker; /// /// Should this graphic be considered a target for ray-casting? @@ -126,7 +135,8 @@ namespace Coffee.UIExtensions } /// - /// Mesh sharing. + /// Particle simulation results are shared within the same group. + /// A large number of the same effects can be displayed with a small load. /// None: disable mesh sharing. /// Auto: automatically select Primary/Replica. /// Primary: provides particle simulation results to the same group. @@ -169,9 +179,9 @@ namespace Coffee.UIExtensions } /// - /// Particle position mode. - /// Relative: The particles will be emitted from the scaled position of the ParticleSystem. - /// Absolute: The particles will be emitted from the world position of the ParticleSystem. + /// Emission position mode. + /// Relative: The particles will be emitted from the scaled position. + /// Absolute: The particles will be emitted from the world position. /// public PositionMode positionMode { @@ -184,6 +194,7 @@ namespace Coffee.UIExtensions /// Relative: The particles will be emitted from the scaled position of the ParticleSystem. /// Absolute: The particles will be emitted from the world position of the ParticleSystem. /// + [Obsolete("The absoluteMode is now obsolete. Please use the autoScalingMode instead.", false)] public bool absoluteMode { get => m_PositionMode == PositionMode.Absolute; @@ -201,8 +212,12 @@ namespace Coffee.UIExtensions } /// - /// Auto scaling mode. + /// How to automatically adjust when the Canvas scale is changed by the screen size or reference resolution. + /// + /// None: Do nothing. + /// /// Transform: Transform.lossyScale (=world scale) will be set to (1, 1, 1). + /// /// UIParticle: UIParticle.scale will be adjusted. /// public AutoScalingMode autoScalingMode @@ -286,24 +301,6 @@ namespace Coffee.UIExtensions public List particles => m_Particles; - /// - /// Get all base materials to render. - /// - public IEnumerable materials - { - get - { - for (var i = 0; i < _renderers.Count; i++) - { - var r = _renderers[i]; - if (!r || !r.material) continue; - yield return r.material; - } - } - } - - public override Material materialForRendering => null; - /// /// Paused. /// @@ -358,12 +355,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() + { + } + void ISerializationCallbackReceiver.OnBeforeSerialize() { } void ISerializationCallbackReceiver.OnAfterDeserialize() { +#pragma warning disable CS0612 // Type or member is obsolete if (m_IgnoreCanvasScaler || m_AutoScaling) { m_IgnoreCanvasScaler = false; @@ -376,31 +381,47 @@ namespace Coffee.UIExtensions m_AbsoluteMode = false; m_PositionMode = PositionMode.Absolute; } +#pragma warning restore CS0612 // Type or member is obsolete } + /// + /// Play the ParticleSystems. + /// public void Play() { particles.Exec(p => p.Simulate(0, false, true)); isPaused = false; } + /// + /// Pause the ParticleSystems. + /// public void Pause() { particles.Exec(p => p.Pause()); isPaused = true; } + /// + /// Unpause the ParticleSystems. + /// public void Resume() { isPaused = false; } + /// + /// Stop the ParticleSystems. + /// public void Stop() { particles.Exec(p => p.Stop()); isPaused = true; } + /// + /// Start emission of the ParticleSystems. + /// public void StartEmission() { particles.Exec(p => @@ -410,6 +431,9 @@ namespace Coffee.UIExtensions }); } + /// + /// Stop emission of the ParticleSystems. + /// public void StopEmission() { particles.Exec(p => @@ -419,17 +443,41 @@ namespace Coffee.UIExtensions }); } + /// + /// Clear the particles of the ParticleSystems. + /// public void Clear() { particles.Exec(p => p.Clear()); isPaused = true; } + /// + /// Get all base materials to render. + /// + public void GetMaterials(List result) + { + if (result == null) return; + + for (var i = 0; i < _renderers.Count; i++) + { + var r = _renderers[i]; + if (!r || !r.material) continue; + result.Add(r.material); + } + } + + /// + /// Refresh UIParticle using the ParticleSystem instance. + /// public void SetParticleSystemInstance(GameObject instance) { SetParticleSystemInstance(instance, true); } + /// + /// Refresh UIParticle using the ParticleSystem instance. + /// public void SetParticleSystemInstance(GameObject instance, bool destroyOldParticles) { if (!instance) return; @@ -455,6 +503,10 @@ namespace Coffee.UIExtensions RefreshParticles(instance); } + /// + /// Refresh UIParticle using the prefab. + /// The prefab is automatically instantiated. + /// public void SetParticleSystemPrefab(GameObject prefab) { if (!prefab) return; @@ -462,16 +514,31 @@ namespace Coffee.UIExtensions SetParticleSystemInstance(Instantiate(prefab.gameObject), true); } + /// + /// Refresh UIParticle. + /// Collect ParticleSystems under the GameObject and refresh the UIParticle. + /// public void RefreshParticles() { RefreshParticles(gameObject); } + /// + /// Refresh UIParticle. + /// Collect ParticleSystems under the GameObject and refresh the UIParticle. + /// private void RefreshParticles(GameObject root) { 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++) { @@ -486,31 +553,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); @@ -556,11 +631,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(); @@ -568,6 +642,7 @@ namespace Coffee.UIExtensions { var r = _renderers[i]; if (!r) continue; + r.UpdateMesh(bakeCamera); } } diff --git a/Runtime/UIParticleRenderer.cs b/Runtime/UIParticleRenderer.cs index 8d3693b..01e8f60 100644 --- a/Runtime/UIParticleRenderer.cs +++ b/Runtime/UIParticleRenderer.cs @@ -31,6 +31,7 @@ namespace Coffee.UIExtensions private Material _currentMaterialForRendering; private bool _delay; private int _index; + private bool _isPrevStored; private bool _isTrail; private Bounds _lastBounds; private Material _modifiedMaterial; @@ -39,9 +40,8 @@ namespace Coffee.UIExtensions private float _prevCanvasScale; private Vector3 _prevPsPos; private Vector3 _prevScale; - private bool _isPrevStored; private Vector2Int _prevScreenSize; - private bool _prewarm; + private bool _preWarm; private ParticleSystemRenderer _renderer; public override Texture mainTexture => _isTrail ? null : _particleSystem.GetTextureForSprite(); @@ -111,8 +111,7 @@ namespace Coffee.UIExtensions if (this && isActiveAndEnabled) { material = null; - workerMesh.Clear(); - canvasRenderer.SetMesh(workerMesh); + canvasRenderer.Clear(); _lastBounds = new Bounds(); enabled = false; } @@ -221,20 +220,20 @@ namespace Coffee.UIExtensions gameObject.layer = parent.gameObject.layer; _particleSystem = ps; - _prewarm = _particleSystem.main.prewarm; + _preWarm = _particleSystem.main.prewarm; #if UNITY_EDITOR if (Application.isPlaying) #endif { - if (_particleSystem.isPlaying || _prewarm) + if (_particleSystem.isPlaying || _preWarm) { _particleSystem.Clear(); _particleSystem.Pause(); } } - _renderer = ps.GetComponent(); + ps.TryGetComponent(out _renderer); _renderer.enabled = false; //_emitter = emitter; @@ -563,10 +562,7 @@ namespace Coffee.UIExtensions return Matrix4x4.Scale(scale); case ParticleSystemSimulationSpace.Custom: return Matrix4x4.Translate(_particleSystem.main.customSimulationSpace.position.GetScaled(scale)) - //* Matrix4x4.Translate(wpos) - * Matrix4x4.Scale(scale) - //* Matrix4x4.Translate(-wpos) - ; + * Matrix4x4.Scale(scale); default: throw new NotSupportedException(); } @@ -582,7 +578,8 @@ namespace Coffee.UIExtensions var screenSize = new Vector2Int(Screen.width, Screen.height); var isWorldSpace = _particleSystem.IsWorldSpace(); var canvasScale = _parent.canvas ? _parent.canvas.scaleFactor : 1f; - var resolutionChanged = _prevScreenSize != screenSize || _prevCanvasScale != canvasScale; + var resolutionChanged = _prevScreenSize != screenSize + || !Mathf.Approximately(_prevCanvasScale, canvasScale); if (resolutionChanged && isWorldSpace && _isPrevStored) { // Update particle array size and get particles. @@ -590,7 +587,7 @@ namespace Coffee.UIExtensions var particles = ParticleSystemExtensions.GetParticleArray(size); _particleSystem.GetParticles(particles, size); - // Resolusion resolver: + // Resolution resolver: // (psPos / scale) / (prevPsPos / prevScale) -> psPos * scale.inv * prevPsPos.inv * prevScale var modifier = psPos.GetScaled( scale.Inverse(), @@ -625,11 +622,11 @@ namespace Coffee.UIExtensions ? Time.unscaledDeltaTime : Time.deltaTime; - // Prewarm: - if (0 < deltaTime && _prewarm) + // Pre-warm: + if (0 < deltaTime && _preWarm) { deltaTime += main.duration; - _prewarm = false; + _preWarm = false; } // get world position. diff --git a/Runtime/UIParticleUpdater.cs b/Runtime/UIParticleUpdater.cs index 7437766..c2c33f2 100644 --- a/Runtime/UIParticleUpdater.cs +++ b/Runtime/UIParticleUpdater.cs @@ -39,8 +39,9 @@ namespace Coffee.UIExtensions #if UNITY_EDITOR [InitializeOnLoadMethod] +#else + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] #endif - [RuntimeInitializeOnLoadMethod] private static void InitializeOnLoad() { Canvas.willRenderCanvases -= Refresh;