pull/336/head
mob-sakai 2024-06-27 11:38:28 +09:00
parent 4c5251a5ba
commit 4d0cfd3510
7 changed files with 275 additions and 182 deletions

View File

@ -4,7 +4,7 @@
"release-4.x", "release-4.x",
{ {
"name": "release-preview", "name": "release-preview",
"prerelease": true "prerelease": "preview"
} }
], ],
"plugins": [ "plugins": [

View File

@ -31,35 +31,35 @@ namespace Coffee.UIExtensions
} }
else 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; s_Sb.Length -= 2;
} }
return s_Sb.ToString(); return s_Sb.ToString();
} }
public static void Draw(SerializedProperty sp, Material[] mats) public static void Draw(SerializedProperty sp, List<Material> mats)
{ {
bool isClicked; var pos = EditorGUILayout.GetControlRect(true);
using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(false))) var label = new GUIContent(sp.displayName, sp.tooltip);
{ var rect = EditorGUI.PrefixLabel(pos, label);
var pos = EditorGUILayout.GetControlRect(true); var text = sp.hasMultipleDifferentValues
var label = new GUIContent(sp.displayName, sp.tooltip); ? "-"
var rect = EditorGUI.PrefixLabel(pos, label); : CollectActiveNames(sp, s_ActiveNames);
var text = sp.hasMultipleDifferentValues
? "-"
: CollectActiveNames(sp, s_ActiveNames);
isClicked = GUI.Button(rect, text, EditorStyles.popup);
}
if (!isClicked) return; if (!GUI.Button(rect, text, EditorStyles.popup)) return;
var gm = new GenericMenu(); var gm = new GenericMenu();
gm.AddItem(s_ContentNothing, s_ActiveNames.Count == 0, () => gm.AddItem(s_ContentNothing, s_ActiveNames.Count == 0, x =>
{ {
sp.ClearArray(); var current = (SerializedProperty)x;
sp.serializedObject.ApplyModifiedProperties(); current.ClearArray();
}); current.serializedObject.ApplyModifiedProperties();
}, sp);
if (!sp.hasMultipleDifferentValues) if (!sp.hasMultipleDifferentValues)
{ {
@ -73,7 +73,7 @@ namespace Coffee.UIExtensions
} }
s_Names.Clear(); s_Names.Clear();
for (var j = 0; j < mats.Length; j++) for (var j = 0; j < mats.Count; j++)
{ {
var mat = mats[j]; var mat = mats[j];
if (!mat || !mat.shader) continue; if (!mat || !mat.shader) continue;
@ -82,8 +82,7 @@ namespace Coffee.UIExtensions
{ {
var name = ShaderUtil.GetPropertyName(mat.shader, i); var name = ShaderUtil.GetPropertyName(mat.shader, i);
var type = (AnimatableProperty.ShaderPropertyType)ShaderUtil.GetPropertyType(mat.shader, i); var type = (AnimatableProperty.ShaderPropertyType)ShaderUtil.GetPropertyType(mat.shader, i);
if (s_Names.Contains(name)) continue; if (!s_Names.Add(name)) continue;
s_Names.Add(name);
AddMenu(gm, sp, new ShaderProperty(name, type), true); AddMenu(gm, sp, new ShaderProperty(name, type), true);

View File

@ -5,6 +5,7 @@ using UnityEditor;
using UnityEditor.UI; using UnityEditor.UI;
using UnityEditorInternal; using UnityEditorInternal;
using UnityEngine; using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.UI; using UnityEngine.UI;
using Coffee.UIParticleExtensions; using Coffee.UIParticleExtensions;
#if UNITY_2021_2_OR_NEWER #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_ContentRandom = new GUIContent("Random");
private static readonly GUIContent s_ContentScale = new GUIContent("Scale"); private static readonly GUIContent s_ContentScale = new GUIContent("Scale");
private static readonly GUIContent s_ContentPrimary = new GUIContent("Primary"); 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<Material> s_TempMaterials = new List<Material>();
private static bool s_XYZMode; private static bool s_XYZMode;
private SerializedProperty _maskable; private SerializedProperty _maskable;
@ -169,6 +172,7 @@ namespace Coffee.UIExtensions
var current = target as UIParticle; var current = target as UIParticle;
if (!current) return; if (!current) return;
Profiler.BeginSample("(UIP:E) OnInspectorGUI");
serializedObject.Update(); serializedObject.Update();
// Maskable // Maskable
@ -180,13 +184,8 @@ namespace Coffee.UIExtensions
EditorGUI.EndDisabledGroup(); EditorGUI.EndDisabledGroup();
// AnimatableProperties // AnimatableProperties
var mats = current.particles current.GetMaterials(s_TempMaterials);
.Where(x => x) AnimatablePropertyEditor.Draw(_animatableProperties, s_TempMaterials);
.Select(x => x.GetComponent<ParticleSystemRenderer>().sharedMaterial)
.Where(x => x)
.ToArray();
AnimatablePropertyEditor.Draw(_animatableProperties, mats);
// Mesh sharing // Mesh sharing
EditorGUI.BeginChangeCheck(); EditorGUI.BeginChangeCheck();
@ -194,9 +193,12 @@ namespace Coffee.UIExtensions
if (EditorGUI.EndChangeCheck()) if (EditorGUI.EndChangeCheck())
{ {
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
foreach (var uip in targets.OfType<UIParticle>()) foreach (var t in targets)
{ {
uip.ResetGroupId(); if (t is UIParticle uip)
{
uip.ResetGroupId();
}
} }
} }
@ -204,7 +206,7 @@ namespace Coffee.UIExtensions
EditorGUILayout.PropertyField(_positionMode); EditorGUILayout.PropertyField(_positionMode);
// Auto Scaling // Auto Scaling
DrawAutoScaling(_autoScalingMode, targets.OfType<UIParticle>()); EditorGUILayout.PropertyField(_autoScalingMode);
// Custom View Size // Custom View Size
EditorGUILayout.PropertyField(_useCustomView); EditorGUILayout.PropertyField(_useCustomView);
@ -221,9 +223,7 @@ namespace Coffee.UIExtensions
// Target ParticleSystems. // Target ParticleSystems.
EditorGUI.BeginChangeCheck(); EditorGUI.BeginChangeCheck();
EditorGUI.BeginDisabledGroup(targets.OfType<UIParticle>().Any(x => !x.canvas));
_ro.DoLayoutList(); _ro.DoLayoutList();
EditorGUI.EndDisabledGroup();
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
if (EditorGUI.EndChangeCheck()) if (EditorGUI.EndChangeCheck())
@ -236,7 +236,8 @@ namespace Coffee.UIExtensions
} }
// Non-UI built-in shader is not supported. // 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; if (!mat || !mat.shader) continue;
var shader = mat.shader; var shader = mat.shader;
@ -249,15 +250,18 @@ namespace Coffee.UIExtensions
} }
} }
Profiler.EndSample();
// Does the shader support UI masks? // Does the shader support UI masks?
Profiler.BeginSample("(UIP:E) Does the shader support UI masks?");
if (current.maskable && current.GetComponentInParent<Mask>(false)) if (current.maskable && current.GetComponentInParent<Mask>(false))
{ {
foreach (var mat in current.materials) foreach (var mat in s_TempMaterials)
{ {
if (!mat || !mat.shader) continue; if (!mat || !mat.shader) continue;
var shader = mat.shader; var shader = mat.shader;
if (s_Shaders.Contains(shader)) continue; if (!s_Shaders.Add(shader)) continue;
s_Shaders.Add(shader);
foreach (var propName in s_MaskablePropertyNames) foreach (var propName in s_MaskablePropertyNames)
{ {
if (mat.HasProperty(propName)) continue; if (mat.HasProperty(propName)) continue;
@ -271,7 +275,9 @@ namespace Coffee.UIExtensions
} }
} }
s_TempMaterials.Clear();
s_Shaders.Clear(); s_Shaders.Clear();
Profiler.EndSample();
// UIParticle for trail should be removed. // UIParticle for trail should be removed.
var label = "This UIParticle component should be removed. The UIParticle for trails is no longer needed."; var label = "This UIParticle component should be removed. The UIParticle for trails is no longer needed.";
@ -310,12 +316,15 @@ namespace Coffee.UIExtensions
} }
} }
#endif #endif
Profiler.EndSample();
EditorApplication.delayCall += () => Profiler.enabled = false;
} }
private static bool IsBuiltInObject(Object obj) private static bool IsBuiltInObject(Object obj)
{ {
return AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long _) return AssetDatabase.IsMainAsset(obj)
&& Regex.IsMatch(guid, "^0{16}.0{15}$", RegexOptions.Compiled); && AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long _)
&& s_RegexBuiltInGuid.IsMatch(guid);
} }
#if UNITY_2018 || UNITY_2019 #if UNITY_2018 || UNITY_2019
@ -419,7 +428,7 @@ namespace Coffee.UIExtensions
return showMax; return showMax;
} }
private static void DrawAutoScaling(SerializedProperty prop, IEnumerable<UIParticle> uiParticles) private static void DrawAutoScaling(SerializedProperty prop)
{ {
EditorGUILayout.PropertyField(prop); EditorGUILayout.PropertyField(prop);
} }

152
README.md
View File

@ -1,4 +1,4 @@
# Particle Effect For UGUI (UI Particle) # <img alt="UIParticleIcon" src="https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/d76e105e-a840-4f61-a1f6-8cf311c0812d" width="26"/> Particle Effect For UGUI (UI Particle)
This package provides a component to render particle effects for uGUI in Unity 2018.2 or later. 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. 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 ## 📝 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 This package uses the new APIs `MeshBake/MeshTrailBake` (introduced in Unity 2018.2) to render particles through CanvasRenderer.
CanvasRenderer. You can render, mask, and sort your ParticleSystems for UI without the need for an additional Camera, RenderTexture, or Canvas.
You can render, mask, and sort your ParticleSystems for UI without the necessity of an additional Camera, RenderTexture,
or Canvas.
### Features ### Key Features
* Easy to use: The package is ready to use out of the box. * **Easy to use:** The package is ready to use out of the box.
* Sort particle effects and other UI by sibling index. * **Sortable:** Sort particle effects and other UI elements by sibling index.
* No extra Camera, RenderTexture, or Canvas required. * **Maskable:** Supports `Mask` or `RectMask2D`.
* Masking options for Mask or RectMask2D. * **No extra components required:** No need for an additional `Camera`, `RenderTexture`, or `Canvas`.
* Support for the Trail module. * **Trail module support:** Fully supports the Trail module.
* Support for CanvasGroup alpha. * **CanvasGroup alpha support:** Integrates with `CanvasGroup` alpha.
* No allocations needed to render particles. * **No allocations:** Efficiently renders particles without allocations.
* Compatibility with overlay, camera space, and world space. * **Any canvas render mode support:** Works with overlay, camera space, and world space.
* Support for Universal Render Pipeline (URP) and High Definition Render Pipeline (HDRP). * **Any Render pipeline support:** Compatible with Universal Render Pipeline (URP) and High Definition Render Pipeline (HDRP).
* Support for disabling `Enter Play Mode Options > Reload Domain`. * **Disabling domain reload support:** Supports disabling `Enter Play Mode Options > Reload Domain`.
* Support for changing material property with AnimationClip (AnimatableProperty). * **Animatable material properties:** Supports changing material properties with AnimationClip (AnimatableProperty).
![AnimatableProperty.gif][AnimatableProperty.gif] ![AnimatableProperty.gif](https://user-images.githubusercontent.com/12690315/53286323-2d94a980-37b0-11e9-8afb-c4a207805ff2.gif)
* [4.0.0+] Support for 8+ materials. * **Multiple materials:** Supports 8+ materials.
* [4.0.0+] Correct world space particle position adjustment when changing window size for standalone platforms (Windows, * **Correct positioning:** Adjusts world space particle positions correctly when changing window size for standalone platforms (Windows, MacOSX, and Linux).
MacOSX, and Linux). * **Adaptive scaling:** Provides adaptive scaling for UI (AutoScalingMode).
* [4.0.0+] Adaptive scaling for UI. * **Performance optimization:** Mesh sharing group to improve performance.
* [4.0.0+] Mesh sharing group to improve performance. <img alt="MeshSharing.gif" src="https://user-images.githubusercontent.com/12690315/174311048-c882df81-6c34-4eba-b0aa-5645457692f1.gif" width="450"/>
![MeshSharing.gif][MeshSharing.gif] * **Particle attractor:** Includes a particle attractor component.
* [4.0.0+] Particle attractor component. <img alt="ParticleAttractor.gif" src="https://user-images.githubusercontent.com/12690315/174311027-462929a4-13f0-4ec4-86ea-9c832f2eecf1.gif" width="450"/>
![ParticleAttractor.gif][ParticleAttractor.gif] * **Emission position mode:** Supports relative/absolute particle emission position modes.
* [4.1.0+] Relative/Absolute particle position mode. <img alt="AbsolutePosition.gif" src="https://user-images.githubusercontent.com/12690315/175751579-5a2357e8-2ecf-4afd-83c8-66e9771bde39.gif" width="450"/>
![AbsolutePosition.gif][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)
[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
<br><br> <br><br>
@ -76,44 +67,55 @@ or Canvas.
[JMO]: https://assetstore.unity.com/publishers/1669 [JMO]: https://assetstore.unity.com/publishers/1669
<br><br> <br><br>
## ⚙ Installation ## ⚙ Installation
_This package requires Unity 2018.3 or later._ _This package requires **Unity 2018.3 or later**._
#### Install via OpenUPM #### Install via OpenUPM
This package is available on [OpenUPM](https://openupm.com) package registry. - 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 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 #### Install via UPM (with Package Manager UI)
project's directory:
```sh - Click `Window > Package Manager` to open Package Manager UI.
openupm add com.coffee.ui-particle - 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 - Open the `Packages/manifest.json` file in your project. Then add this package somewhere in the `dependencies` block:
the `dependencies` block: ```json
{
```json "dependencies": {
{ "com.coffee.ui-particle": "https://github.com/mob-sakai/ParticleEffectForUGUI.git",
"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.
<br><br> <br><br>
@ -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. `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. - **Maskable**: Does this graphic allow maskable.
- **Scale**: Scale the rendering. When the `3D` toggle is enabled, 3D scale (x, y, z) is supported. - **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, - **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 - **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. 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. - **Position Mode**: Emission position mode.
- **Absolute:** Emit from the world position of the `ParticleSystem`. - **Absolute:** The particles will be emitted from the world position.
- **Relative:** Emit from the scaled position of the `ParticleSystem`. - **Relative:** The particles will be emitted from the scaled position.
- **Auto Scaling**: `Transform.lossyScale` (=world scale) will be set to `(1, 1, 1)` on update. It prevents the - **Auto Scaling Mode**: How to automatically adjust when the Canvas scale is changed by the screen size or reference resolution.
root-Canvas scale from affecting the hierarchy-scaled `ParticleSystem`. - **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. - **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 **NOTE:** Press the `Refresh` button to reconstruct the rendering order based on children ParticleSystem's sorting order
@ -176,9 +187,10 @@ section.
### Script usage ### Script usage
```cs ```cs
// Instant ParticleSystem prefab with UIParticle on runtime. // Instantiate ParticleSystem prefab with UIParticle on runtime.
var go = GameObject.Instantiate(prefab); var go = GameObject.Instantiate(prefab);
var uiParticle = go.AddComponent<UIParticle>(); var uiParticle = go.AddComponent<UIParticle>();
uiParticle.scale = 100;
// Control by ParticleSystem. // Control by ParticleSystem.
particleSystem.Play(); particleSystem.Play();
@ -195,7 +207,7 @@ uiParticle.Stop();
`UIParticleAttractor` attracts particles generated by the specified ParticleSystem. `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) ![](https://user-images.githubusercontent.com/12690315/174311027-462929a4-13f0-4ec4-86ea-9c832f2eecf1.gif)
- **Particle System**: Attracts particles generated by the specified particle system. - **Particle System**: Attracts particles generated by the specified particle system.

View File

@ -44,23 +44,26 @@ namespace Coffee.UIExtensions
[HideInInspector] [HideInInspector]
[SerializeField] [SerializeField]
[Obsolete]
internal bool m_IsTrail; internal bool m_IsTrail;
[HideInInspector] [HideInInspector]
[FormerlySerializedAs("m_IgnoreParent")] [FormerlySerializedAs("m_IgnoreParent")]
[SerializeField] [SerializeField]
[Obsolete]
private bool m_IgnoreCanvasScaler; private bool m_IgnoreCanvasScaler;
[HideInInspector] [HideInInspector]
[SerializeField] [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] [SerializeField]
private Vector3 m_Scale3D = new Vector3(10, 10, 10); private Vector3 m_Scale3D = new Vector3(10, 10, 10);
[Tooltip("Animatable material properties.\n" + [Tooltip("If you want to update material properties (e.g. _MainTex_ST, _Color) in AnimationClip, " +
"If you want to change the material properties of the ParticleSystem in Animation, enable it.")] "use this to mark as animatable.")]
[SerializeField] [SerializeField]
internal AnimatableProperty[] m_AnimatableProperties = new AnimatableProperty[0]; internal AnimatableProperty[] m_AnimatableProperties = new AnimatableProperty[0];
@ -68,12 +71,13 @@ namespace Coffee.UIExtensions
[SerializeField] [SerializeField]
private List<ParticleSystem> m_Particles = new List<ParticleSystem>(); private List<ParticleSystem> m_Particles = new List<ParticleSystem>();
[Tooltip("Mesh sharing.\n" + [Tooltip("Particle simulation results are shared within the same group. " +
"None: disable mesh sharing.\n" + "A large number of the same effects can be displayed with a small load.\n" +
"Auto: automatically select Primary/Replica.\n" + "None: Disable mesh sharing.\n" +
"Primary: provides particle simulation results to the same group.\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" + "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] [SerializeField]
private MeshSharing m_MeshSharing = MeshSharing.None; private MeshSharing m_MeshSharing = MeshSharing.None;
@ -85,18 +89,22 @@ namespace Coffee.UIExtensions
[SerializeField] [SerializeField]
private int m_GroupMaxId; private int m_GroupMaxId;
[Tooltip("Relative: The particles will be emitted from the scaled position of ParticleSystem.\n" + [Tooltip("Emission position mode.\n" +
"Absolute: The particles will be emitted from the world position of ParticleSystem.")] "Relative: The particles will be emitted from the scaled position.\n" +
"Absolute: The particles will be emitted from the world position.")]
[SerializeField] [SerializeField]
private PositionMode m_PositionMode = PositionMode.Relative; private PositionMode m_PositionMode = PositionMode.Relative;
[SerializeField] [SerializeField]
[Tooltip("Prevent the root-Canvas scale from affecting the hierarchy-scaled ParticleSystem.")] [Obsolete]
private bool m_AutoScaling = true; internal bool m_AutoScaling;
[SerializeField] [SerializeField]
[Tooltip("Transform: Transform.lossyScale (=world scale) will be set to (1, 1, 1)." + [Tooltip(
"UIParticle: UIParticle.scale will be adjusted.")] "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; private AutoScalingMode m_AutoScalingMode = AutoScalingMode.Transform;
[SerializeField] [SerializeField]
@ -110,11 +118,12 @@ namespace Coffee.UIExtensions
private float m_CustomViewSize = 10; private float m_CustomViewSize = 10;
private readonly List<UIParticleRenderer> _renderers = new List<UIParticleRenderer>(); private readonly List<UIParticleRenderer> _renderers = new List<UIParticleRenderer>();
private int _groupId;
private Camera _bakeCamera; private Camera _bakeCamera;
private DrivenRectTransformTracker _tracker; private Canvas _canvas;
private Vector3 _storedScale; private int _groupId;
private bool _isScaleStored; private bool _isScaleStored;
private Vector3 _storedScale;
private DrivenRectTransformTracker _tracker;
/// <summary> /// <summary>
/// Should this graphic be considered a target for ray-casting? /// Should this graphic be considered a target for ray-casting?
@ -126,7 +135,8 @@ namespace Coffee.UIExtensions
} }
/// <summary> /// <summary>
/// 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. /// None: disable mesh sharing.
/// Auto: automatically select Primary/Replica. /// Auto: automatically select Primary/Replica.
/// Primary: provides particle simulation results to the same group. /// Primary: provides particle simulation results to the same group.
@ -169,9 +179,9 @@ namespace Coffee.UIExtensions
} }
/// <summary> /// <summary>
/// Particle position mode. /// Emission position mode.
/// Relative: The particles will be emitted from the scaled position of the ParticleSystem. /// Relative: The particles will be emitted from the scaled position.
/// Absolute: The particles will be emitted from the world position of the ParticleSystem. /// Absolute: The particles will be emitted from the world position.
/// </summary> /// </summary>
public PositionMode positionMode public PositionMode positionMode
{ {
@ -184,6 +194,7 @@ namespace Coffee.UIExtensions
/// Relative: The particles will be emitted from the scaled position of the ParticleSystem. /// 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. /// Absolute: The particles will be emitted from the world position of the ParticleSystem.
/// </summary> /// </summary>
[Obsolete("The absoluteMode is now obsolete. Please use the autoScalingMode instead.", false)]
public bool absoluteMode public bool absoluteMode
{ {
get => m_PositionMode == PositionMode.Absolute; get => m_PositionMode == PositionMode.Absolute;
@ -201,8 +212,12 @@ namespace Coffee.UIExtensions
} }
/// <summary> /// <summary>
/// Auto scaling mode. /// How to automatically adjust when the Canvas scale is changed by the screen size or reference resolution.
/// <para/>
/// None: Do nothing.
/// <para/>
/// Transform: Transform.lossyScale (=world scale) will be set to (1, 1, 1). /// Transform: Transform.lossyScale (=world scale) will be set to (1, 1, 1).
/// <para/>
/// UIParticle: UIParticle.scale will be adjusted. /// UIParticle: UIParticle.scale will be adjusted.
/// </summary> /// </summary>
public AutoScalingMode autoScalingMode public AutoScalingMode autoScalingMode
@ -286,24 +301,6 @@ namespace Coffee.UIExtensions
public List<ParticleSystem> particles => m_Particles; public List<ParticleSystem> particles => m_Particles;
/// <summary>
/// Get all base materials to render.
/// </summary>
public IEnumerable<Material> 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;
/// <summary> /// <summary>
/// Paused. /// Paused.
/// </summary> /// </summary>
@ -358,12 +355,20 @@ namespace Coffee.UIExtensions
{ {
} }
/// <summary>
/// This function is called when a direct or indirect parent of the transform of the GameObject has changed.
/// </summary>
protected override void OnTransformParentChanged()
{
}
void ISerializationCallbackReceiver.OnBeforeSerialize() void ISerializationCallbackReceiver.OnBeforeSerialize()
{ {
} }
void ISerializationCallbackReceiver.OnAfterDeserialize() void ISerializationCallbackReceiver.OnAfterDeserialize()
{ {
#pragma warning disable CS0612 // Type or member is obsolete
if (m_IgnoreCanvasScaler || m_AutoScaling) if (m_IgnoreCanvasScaler || m_AutoScaling)
{ {
m_IgnoreCanvasScaler = false; m_IgnoreCanvasScaler = false;
@ -376,31 +381,47 @@ namespace Coffee.UIExtensions
m_AbsoluteMode = false; m_AbsoluteMode = false;
m_PositionMode = PositionMode.Absolute; m_PositionMode = PositionMode.Absolute;
} }
#pragma warning restore CS0612 // Type or member is obsolete
} }
/// <summary>
/// Play the ParticleSystems.
/// </summary>
public void Play() public void Play()
{ {
particles.Exec(p => p.Simulate(0, false, true)); particles.Exec(p => p.Simulate(0, false, true));
isPaused = false; isPaused = false;
} }
/// <summary>
/// Pause the ParticleSystems.
/// </summary>
public void Pause() public void Pause()
{ {
particles.Exec(p => p.Pause()); particles.Exec(p => p.Pause());
isPaused = true; isPaused = true;
} }
/// <summary>
/// Unpause the ParticleSystems.
/// </summary>
public void Resume() public void Resume()
{ {
isPaused = false; isPaused = false;
} }
/// <summary>
/// Stop the ParticleSystems.
/// </summary>
public void Stop() public void Stop()
{ {
particles.Exec(p => p.Stop()); particles.Exec(p => p.Stop());
isPaused = true; isPaused = true;
} }
/// <summary>
/// Start emission of the ParticleSystems.
/// </summary>
public void StartEmission() public void StartEmission()
{ {
particles.Exec(p => particles.Exec(p =>
@ -410,6 +431,9 @@ namespace Coffee.UIExtensions
}); });
} }
/// <summary>
/// Stop emission of the ParticleSystems.
/// </summary>
public void StopEmission() public void StopEmission()
{ {
particles.Exec(p => particles.Exec(p =>
@ -419,17 +443,41 @@ namespace Coffee.UIExtensions
}); });
} }
/// <summary>
/// Clear the particles of the ParticleSystems.
/// </summary>
public void Clear() public void Clear()
{ {
particles.Exec(p => p.Clear()); particles.Exec(p => p.Clear());
isPaused = true; isPaused = true;
} }
/// <summary>
/// Get all base materials to render.
/// </summary>
public void GetMaterials(List<Material> 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);
}
}
/// <summary>
/// Refresh UIParticle using the ParticleSystem instance.
/// </summary>
public void SetParticleSystemInstance(GameObject instance) public void SetParticleSystemInstance(GameObject instance)
{ {
SetParticleSystemInstance(instance, true); SetParticleSystemInstance(instance, true);
} }
/// <summary>
/// Refresh UIParticle using the ParticleSystem instance.
/// </summary>
public void SetParticleSystemInstance(GameObject instance, bool destroyOldParticles) public void SetParticleSystemInstance(GameObject instance, bool destroyOldParticles)
{ {
if (!instance) return; if (!instance) return;
@ -455,6 +503,10 @@ namespace Coffee.UIExtensions
RefreshParticles(instance); RefreshParticles(instance);
} }
/// <summary>
/// Refresh UIParticle using the prefab.
/// The prefab is automatically instantiated.
/// </summary>
public void SetParticleSystemPrefab(GameObject prefab) public void SetParticleSystemPrefab(GameObject prefab)
{ {
if (!prefab) return; if (!prefab) return;
@ -462,16 +514,31 @@ namespace Coffee.UIExtensions
SetParticleSystemInstance(Instantiate(prefab.gameObject), true); SetParticleSystemInstance(Instantiate(prefab.gameObject), true);
} }
/// <summary>
/// Refresh UIParticle.
/// Collect ParticleSystems under the GameObject and refresh the UIParticle.
/// </summary>
public void RefreshParticles() public void RefreshParticles()
{ {
RefreshParticles(gameObject); RefreshParticles(gameObject);
} }
/// <summary>
/// Refresh UIParticle.
/// Collect ParticleSystems under the GameObject and refresh the UIParticle.
/// </summary>
private void RefreshParticles(GameObject root) private void RefreshParticles(GameObject root)
{ {
if (!root) return; if (!root) return;
root.GetComponentsInChildren(true, particles); root.GetComponentsInChildren(true, particles);
particles.RemoveAll(x => x.GetComponentInParent<UIParticle>(true) != this); for (var i = particles.Count - 1; 0 <= i; i--)
{
var ps = particles[i];
if (!ps || ps.GetComponentInParent<UIParticle>(true) != this)
{
particles.RemoveAt(i);
}
}
for (var i = 0; i < particles.Count; i++) for (var i = 0; i < particles.Count; i++)
{ {
@ -486,31 +553,39 @@ namespace Coffee.UIExtensions
RefreshParticles(particles); RefreshParticles(particles);
} }
public void RefreshParticles(List<ParticleSystem> particles) /// <summary>
/// Refresh UIParticle using a list of ParticleSystems.
/// </summary>
public void RefreshParticles(List<ParticleSystem> particleSystems)
{ {
// Collect children UIParticleRenderer components.
// #246: Nullptr exceptions when using nested UIParticle components in hierarchy // #246: Nullptr exceptions when using nested UIParticle components in hierarchy
_renderers.Clear(); _renderers.Clear();
foreach (Transform child in transform) var childCount = transform.childCount;
for (var i = 0; i < childCount; i++)
{ {
var uiParticleRenderer = child.GetComponent<UIParticleRenderer>(); var child = transform.GetChild(i);
if (child.TryGetComponent(out UIParticleRenderer uiParticleRenderer))
if (uiParticleRenderer != null)
{ {
_renderers.Add(uiParticleRenderer); _renderers.Add(uiParticleRenderer);
} }
} }
// Reset the UIParticleRenderer components.
for (var i = 0; i < _renderers.Count; i++) for (var i = 0; i < _renderers.Count; i++)
{ {
_renderers[i].Reset(i); _renderers[i].Reset(i);
} }
// Set the ParticleSystem to the UIParticleRenderer. If the trail is enabled, set it additionally.
var j = 0; 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; if (!ps) continue;
GetRenderer(j++).Set(this, ps, false); GetRenderer(j++).Set(this, ps, false);
// If the trail is enabled, set it additionally.
if (ps.trails.enabled) if (ps.trails.enabled)
{ {
GetRenderer(j++).Set(this, ps, true); GetRenderer(j++).Set(this, ps, true);
@ -556,11 +631,10 @@ namespace Coffee.UIExtensions
for (var i = 0; i < _renderers.Count; i++) for (var i = 0; i < _renderers.Count; i++)
{ {
var r = _renderers[i]; var r = _renderers[i];
if (!r) if (r) continue;
{
RefreshParticles(particles); RefreshParticles(particles);
break; break;
}
} }
var bakeCamera = GetBakeCamera(); var bakeCamera = GetBakeCamera();
@ -568,6 +642,7 @@ namespace Coffee.UIExtensions
{ {
var r = _renderers[i]; var r = _renderers[i];
if (!r) continue; if (!r) continue;
r.UpdateMesh(bakeCamera); r.UpdateMesh(bakeCamera);
} }
} }

View File

@ -31,6 +31,7 @@ namespace Coffee.UIExtensions
private Material _currentMaterialForRendering; private Material _currentMaterialForRendering;
private bool _delay; private bool _delay;
private int _index; private int _index;
private bool _isPrevStored;
private bool _isTrail; private bool _isTrail;
private Bounds _lastBounds; private Bounds _lastBounds;
private Material _modifiedMaterial; private Material _modifiedMaterial;
@ -39,9 +40,8 @@ namespace Coffee.UIExtensions
private float _prevCanvasScale; private float _prevCanvasScale;
private Vector3 _prevPsPos; private Vector3 _prevPsPos;
private Vector3 _prevScale; private Vector3 _prevScale;
private bool _isPrevStored;
private Vector2Int _prevScreenSize; private Vector2Int _prevScreenSize;
private bool _prewarm; private bool _preWarm;
private ParticleSystemRenderer _renderer; private ParticleSystemRenderer _renderer;
public override Texture mainTexture => _isTrail ? null : _particleSystem.GetTextureForSprite(); public override Texture mainTexture => _isTrail ? null : _particleSystem.GetTextureForSprite();
@ -111,8 +111,7 @@ namespace Coffee.UIExtensions
if (this && isActiveAndEnabled) if (this && isActiveAndEnabled)
{ {
material = null; material = null;
workerMesh.Clear(); canvasRenderer.Clear();
canvasRenderer.SetMesh(workerMesh);
_lastBounds = new Bounds(); _lastBounds = new Bounds();
enabled = false; enabled = false;
} }
@ -221,20 +220,20 @@ namespace Coffee.UIExtensions
gameObject.layer = parent.gameObject.layer; gameObject.layer = parent.gameObject.layer;
_particleSystem = ps; _particleSystem = ps;
_prewarm = _particleSystem.main.prewarm; _preWarm = _particleSystem.main.prewarm;
#if UNITY_EDITOR #if UNITY_EDITOR
if (Application.isPlaying) if (Application.isPlaying)
#endif #endif
{ {
if (_particleSystem.isPlaying || _prewarm) if (_particleSystem.isPlaying || _preWarm)
{ {
_particleSystem.Clear(); _particleSystem.Clear();
_particleSystem.Pause(); _particleSystem.Pause();
} }
} }
_renderer = ps.GetComponent<ParticleSystemRenderer>(); ps.TryGetComponent(out _renderer);
_renderer.enabled = false; _renderer.enabled = false;
//_emitter = emitter; //_emitter = emitter;
@ -563,10 +562,7 @@ namespace Coffee.UIExtensions
return Matrix4x4.Scale(scale); return Matrix4x4.Scale(scale);
case ParticleSystemSimulationSpace.Custom: case ParticleSystemSimulationSpace.Custom:
return Matrix4x4.Translate(_particleSystem.main.customSimulationSpace.position.GetScaled(scale)) return Matrix4x4.Translate(_particleSystem.main.customSimulationSpace.position.GetScaled(scale))
//* Matrix4x4.Translate(wpos) * Matrix4x4.Scale(scale);
* Matrix4x4.Scale(scale)
//* Matrix4x4.Translate(-wpos)
;
default: default:
throw new NotSupportedException(); throw new NotSupportedException();
} }
@ -582,7 +578,8 @@ namespace Coffee.UIExtensions
var screenSize = new Vector2Int(Screen.width, Screen.height); var screenSize = new Vector2Int(Screen.width, Screen.height);
var isWorldSpace = _particleSystem.IsWorldSpace(); var isWorldSpace = _particleSystem.IsWorldSpace();
var canvasScale = _parent.canvas ? _parent.canvas.scaleFactor : 1f; 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) if (resolutionChanged && isWorldSpace && _isPrevStored)
{ {
// Update particle array size and get particles. // Update particle array size and get particles.
@ -590,7 +587,7 @@ namespace Coffee.UIExtensions
var particles = ParticleSystemExtensions.GetParticleArray(size); var particles = ParticleSystemExtensions.GetParticleArray(size);
_particleSystem.GetParticles(particles, size); _particleSystem.GetParticles(particles, size);
// Resolusion resolver: // Resolution resolver:
// (psPos / scale) / (prevPsPos / prevScale) -> psPos * scale.inv * prevPsPos.inv * prevScale // (psPos / scale) / (prevPsPos / prevScale) -> psPos * scale.inv * prevPsPos.inv * prevScale
var modifier = psPos.GetScaled( var modifier = psPos.GetScaled(
scale.Inverse(), scale.Inverse(),
@ -625,11 +622,11 @@ namespace Coffee.UIExtensions
? Time.unscaledDeltaTime ? Time.unscaledDeltaTime
: Time.deltaTime; : Time.deltaTime;
// Prewarm: // Pre-warm:
if (0 < deltaTime && _prewarm) if (0 < deltaTime && _preWarm)
{ {
deltaTime += main.duration; deltaTime += main.duration;
_prewarm = false; _preWarm = false;
} }
// get world position. // get world position.

View File

@ -39,8 +39,9 @@ namespace Coffee.UIExtensions
#if UNITY_EDITOR #if UNITY_EDITOR
[InitializeOnLoadMethod] [InitializeOnLoadMethod]
#else
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
#endif #endif
[RuntimeInitializeOnLoadMethod]
private static void InitializeOnLoad() private static void InitializeOnLoad()
{ {
Canvas.willRenderCanvases -= Refresh; Canvas.willRenderCanvases -= Refresh;