Compare commits

...

17 Commits
4.7.1 ... 4.9.0

Author SHA1 Message Date
semantic-release-bot
4dfcb9075e chore(release): 4.9.0 [skip ci]
# [4.9.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.8.1...v4.9.0) (2024-07-18)

### Features

* ParticleAttractor supports multiple ParticleSystems ([3834780](3834780fdb))
2024-07-18 05:09:44 +00:00
mob-sakai
7916056868 demos: update demo 2024-07-18 13:40:58 +09:00
mob-sakai
b87a0a02af docs: update readme 2024-07-18 09:33:42 +09:00
mob-sakai
3052cab41b refactor: refactor multiple attractor
- Convert 'm_ParticleSystem' to 'm_ParticleSystems'
- Collect parent UIParticles on attract
- Improve performance
2024-07-18 00:56:13 +09:00
dogramacigokhan
a439248e1a feat: ParticleAttractor supports multiple ParticleSystems 2024-07-18 00:56:13 +09:00
semantic-release-bot
686fff8690 chore(release): 4.8.1 [skip ci]
## [4.8.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.8.0...v4.8.1) (2024-06-27)

### Bug Fixes

* remove debug code ([669deb4](669deb41d4))
2024-06-27 09:27:20 +00:00
mob-sakai
3638df524f fix: remove debug code 2024-06-27 18:26:47 +09:00
semantic-release-bot
592d871da2 chore(release): 4.8.0 [skip ci]
# [4.8.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.7.2...v4.8.0) (2024-06-27)

### Bug Fixes

* generated baking-camera object remains in the prefab or scene (again) ([de35cba](de35cba34c))
* SetParticleSystemInstance/Prefab APIs destroy generated objects ([ae3f3a8](ae3f3a8e62))

### Features

* add 'custom view' option. ([a703c29](a703c2921c))
* remove overlay window (editor) ([8358170](835817049f))
* restore `Transform.localScale` when setting `autoScalingMode` to something other than `Transform` (again) ([88a970d](88a970d93a))
* the rendering order list in inspector is now more compact ([be90172](be901724e0))
2024-06-27 06:40:30 +00:00
mob-sakai
4d0cfd3510 refactor 2024-06-27 14:12:53 +09:00
mob-sakai
4c5251a5ba feat: the rendering order list in inspector is now more compact 2024-06-27 10:48:34 +09:00
mob-sakai
cbb37b0b3a feat: restore Transform.localScale when setting autoScalingMode to something other than Transform (again) 2024-06-27 10:48:27 +09:00
mob-sakai
3c54f6dc8d feat: add 'custom view' option.
Use this if the particles are not displayed correctly due to min/max particle size.
2024-06-27 10:48:05 +09:00
mob-sakai
d3532b9708 fix: generated baking-camera object remains in the prefab or scene (again) 2024-06-27 10:46:25 +09:00
mob-sakai
2ac8a1175b fix: SetParticleSystemInstance/Prefab APIs destroy generated objects
# Conflicts:
#	Packages/src/Runtime/UIParticle.cs
2024-06-27 10:46:10 +09:00
mob-sakai
9a37e64b20 feat: remove overlay window (editor) 2024-06-27 10:44:13 +09:00
semantic-release-bot
bc0c2e4e63 chore(release): 4.7.2 [skip ci]
## [4.7.2](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.7.1...v4.7.2) (2024-06-21)

### Bug Fixes

* generated baking-camera object remains in the prefab or scene ([0bb8438](0bb8438301))
2024-06-21 05:22:49 +00:00
mob-sakai
929c0d686b fix: generated baking-camera object remains in the prefab or scene 2024-06-21 14:21:53 +09:00
15 changed files with 639 additions and 431 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

@@ -1,3 +1,40 @@
# [4.9.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.8.1...v4.9.0) (2024-07-18)
### Features
* ParticleAttractor supports multiple ParticleSystems ([3834780](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/3834780fdb43443fe6e1ef89df54d26a24d62a91))
## [4.8.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.8.0...v4.8.1) (2024-06-27)
### Bug Fixes
* remove debug code ([669deb4](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/669deb41d4ac589d9db93b29bc8e95383e7f28a5))
# [4.8.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.7.2...v4.8.0) (2024-06-27)
### Bug Fixes
* generated baking-camera object remains in the prefab or scene (again) ([de35cba](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/de35cba34c6312c1405ed522e9927c620c78e72d))
* SetParticleSystemInstance/Prefab APIs destroy generated objects ([ae3f3a8](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/ae3f3a8e62cc733420354d237ab765ac777127c8))
### Features
* add 'custom view' option. ([a703c29](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/a703c2921ca08c2280d0c8fde01e4c0b33b5c69e))
* remove overlay window (editor) ([8358170](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/835817049f4fcf00dd2bf98dbada14f041ad3544))
* restore `Transform.localScale` when setting `autoScalingMode` to something other than `Transform` (again) ([88a970d](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/88a970d93a2b69cf011d86bd1807569e90538e0e))
* the rendering order list in inspector is now more compact ([be90172](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/be901724e064befacf617f4940b0331e1d31e1ca))
## [4.7.2](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.7.1...v4.7.2) (2024-06-21)
### Bug Fixes
* generated baking-camera object remains in the prefab or scene ([0bb8438](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/0bb843830197d8c1252232928becc211c0ada08d))
## [4.7.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.7.0...v4.7.1) (2024-06-20) ## [4.7.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.7.0...v4.7.1) (2024-06-20)

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
@@ -26,41 +27,24 @@ namespace Coffee.UIExtensions
[CanEditMultipleObjects] [CanEditMultipleObjects]
internal class UIParticleEditor : GraphicEditor internal class UIParticleEditor : GraphicEditor
{ {
#if UNITY_2021_2_OR_NEWER
#if UNITY_2022_1_OR_NEWER
[Overlay(typeof(SceneView), "Scene View/UI Particles", "UI Particles", true,
defaultDockPosition = DockPosition.Bottom,
defaultDockZone = DockZone.Floating,
defaultLayout = Layout.Panel)]
#else
[Overlay(typeof(SceneView), "Scene View/UI Particles", "UI Particles", true)]
#endif
private class UIParticleOverlay : IMGUIOverlay, ITransientOverlay
{
public bool visible => s_SerializedObject != null;
public override void OnGUI()
{
if (visible)
{
WindowFunction();
}
}
}
#endif
//################################ //################################
// Constant or Static Members. // Constant or Static Members.
//################################ //################################
private static readonly GUIContent[] s_ContentMaterials = new[]
{
new GUIContent("Material"),
new GUIContent("Trail Material")
};
private static readonly GUIContent s_ContentRenderingOrder = new GUIContent("Rendering Order"); private static readonly GUIContent s_ContentRenderingOrder = new GUIContent("Rendering Order");
private static readonly GUIContent s_ContentRefresh = new GUIContent("Refresh"); private static readonly GUIContent s_ContentRefresh = new GUIContent("Refresh");
private static readonly GUIContent s_ContentFix = new GUIContent("Fix"); private static readonly GUIContent s_ContentFix = new GUIContent("Fix");
private static readonly GUIContent s_ContentMaterial = new GUIContent("Material");
private static readonly GUIContent s_ContentTrailMaterial = new GUIContent("Trail Material");
private static readonly GUIContent s_Content3D = new GUIContent("3D"); private static readonly GUIContent s_Content3D = new GUIContent("3D");
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 SerializedObject s_SerializedObject; 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;
@@ -71,6 +55,8 @@ namespace Coffee.UIExtensions
private SerializedProperty _groupMaxId; private SerializedProperty _groupMaxId;
private SerializedProperty _positionMode; private SerializedProperty _positionMode;
private SerializedProperty _autoScalingMode; private SerializedProperty _autoScalingMode;
private SerializedProperty _useCustomView;
private SerializedProperty _customViewSize;
private ReorderableList _ro; private ReorderableList _ro;
private bool _showMax; private bool _showMax;
@@ -88,56 +74,6 @@ namespace Coffee.UIExtensions
"_ColorMask" "_ColorMask"
}; };
[InitializeOnLoadMethod]
private static void Init()
{
#if !UNITY_2021_2_OR_NEWER
var miSceneViewOverlayWindow = Type.GetType("UnityEditor.SceneViewOverlay, UnityEditor")
?.GetMethods(BindingFlags.Public | BindingFlags.Static)
.First(x => x.Name == "Window" && 5 <= x.GetParameters().Length);
var windowFunction = (Action<Object, SceneView>)WindowFunction;
var windowFunctionType = Type.GetType("UnityEditor.SceneViewOverlay+WindowFunction, UnityEditor");
var windowFunctionDelegate = Delegate.CreateDelegate(windowFunctionType, windowFunction.Method);
var windowTitle = new GUIContent(ObjectNames.NicifyVariableName(nameof(UIParticle)));
#if UNITY_2019_2_OR_NEWER
//public static void Window(GUIContent title, WindowFunction sceneViewFunc, int order, Object target, WindowDisplayOption option, EditorWindow window = null)
var sceneViewArgs = new object[] { windowTitle, windowFunctionDelegate, 599, null, 2, null };
#else
//public static void Window(GUIContent title, WindowFunction sceneViewFunc, int order, Object target, WindowDisplayOption option)
var sceneViewArgs = new object[] { windowTitle, windowFunctionDelegate, 599, null, 2 };
#endif
#if UNITY_2019_1_OR_NEWER
SceneView.duringSceneGui += _ =>
#else
SceneView.onSceneGUIDelegate += _ =>
#endif
{
if (s_SerializedObject != null)
{
miSceneViewOverlayWindow.Invoke(null, sceneViewArgs);
}
};
#endif
SerializedObject CreateSerializeObject()
{
var uiParticles = Selection.gameObjects.Select(x => x.GetComponent<ParticleSystem>())
.Where(x => x)
.Select(x => x.GetComponentInParent<UIParticle>(true))
.Where(x => x && x.canvas)
.Concat(Selection.gameObjects.Select(x => x.GetComponent<UIParticle>())
.Where(x => x && x.canvas))
.Distinct()
.OfType<Object>()
.ToArray();
return 0 < uiParticles.Length ? new SerializedObject(uiParticles) : null;
}
s_SerializedObject = CreateSerializeObject();
Selection.selectionChanged += () => s_SerializedObject = CreateSerializeObject();
}
//################################ //################################
// Public/Protected Members. // Public/Protected Members.
//################################ //################################
@@ -156,31 +92,43 @@ namespace Coffee.UIExtensions
_groupMaxId = serializedObject.FindProperty("m_GroupMaxId"); _groupMaxId = serializedObject.FindProperty("m_GroupMaxId");
_positionMode = serializedObject.FindProperty("m_PositionMode"); _positionMode = serializedObject.FindProperty("m_PositionMode");
_autoScalingMode = serializedObject.FindProperty("m_AutoScalingMode"); _autoScalingMode = serializedObject.FindProperty("m_AutoScalingMode");
_useCustomView = serializedObject.FindProperty("m_UseCustomView");
_customViewSize = serializedObject.FindProperty("m_CustomViewSize");
var sp = serializedObject.FindProperty("m_Particles"); var sp = serializedObject.FindProperty("m_Particles");
_ro = new ReorderableList(sp.serializedObject, sp, true, true, true, true) _ro = new ReorderableList(sp.serializedObject, sp, true, true, true, true)
{ {
elementHeight = EditorGUIUtility.singleLineHeight * 3 + 4, elementHeightCallback = index =>
elementHeightCallback = _ => 3 * (EditorGUIUtility.singleLineHeight + 2), {
var ps = sp.GetArrayElementAtIndex(index).objectReferenceValue as ParticleSystem;
var materialCount = 0;
if (ps && ps.TryGetComponent<ParticleSystemRenderer>(out var psr))
{
materialCount = psr.sharedMaterials.Length;
}
return (materialCount + 1) * (EditorGUIUtility.singleLineHeight + 2);
},
drawElementCallback = (rect, index, _, __) => drawElementCallback = (rect, index, _, __) =>
{ {
EditorGUI.BeginDisabledGroup(sp.hasMultipleDifferentValues); rect.y += 2;
rect.y += 1;
rect.height = EditorGUIUtility.singleLineHeight; rect.height = EditorGUIUtility.singleLineHeight;
var p = sp.GetArrayElementAtIndex(index); var p = sp.GetArrayElementAtIndex(index);
EditorGUI.ObjectField(rect, p, GUIContent.none); EditorGUI.ObjectField(rect, p, GUIContent.none);
var ps = p.objectReferenceValue as ParticleSystem;
if (!ps || !ps.TryGetComponent<ParticleSystemRenderer>(out var psr)) return;
rect.x += 15; rect.x += 15;
rect.width -= 15; rect.width -= 15;
var ps = p.objectReferenceValue as ParticleSystem; var materials = new SerializedObject(psr).FindProperty("m_Materials");
var materials = ps var count = Mathf.Min(materials.arraySize, 2);
? new SerializedObject(ps.GetComponent<ParticleSystemRenderer>()).FindProperty("m_Materials") for (var i = 0; i < count; i++)
: null; {
rect.y += rect.height + 1; rect.y += rect.height + 2;
MaterialField(rect, s_ContentMaterial, materials, 0); EditorGUI.PropertyField(rect, materials.GetArrayElementAtIndex(i), s_ContentMaterials[i]);
rect.y += rect.height + 1; }
MaterialField(rect, s_ContentTrailMaterial, materials, 1);
EditorGUI.EndDisabledGroup(); if (materials.serializedObject.hasModifiedProperties)
if (materials != null && materials.serializedObject.hasModifiedProperties)
{ {
materials.serializedObject.ApplyModifiedProperties(); materials.serializedObject.ApplyModifiedProperties();
} }
@@ -216,20 +164,6 @@ namespace Coffee.UIExtensions
} }
} }
private static void MaterialField(Rect rect, GUIContent label, SerializedProperty sp, int index)
{
if (sp == null || sp.arraySize <= index)
{
EditorGUI.BeginDisabledGroup(true);
EditorGUI.ObjectField(rect, label, null, typeof(Material), true);
EditorGUI.EndDisabledGroup();
}
else
{
EditorGUI.PropertyField(rect, sp.GetArrayElementAtIndex(index), label);
}
}
/// <summary> /// <summary>
/// Implement this function to make a custom inspector. /// Implement this function to make a custom inspector.
/// </summary> /// </summary>
@@ -238,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
@@ -249,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();
@@ -263,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();
}
} }
} }
@@ -273,16 +206,29 @@ namespace Coffee.UIExtensions
EditorGUILayout.PropertyField(_positionMode); EditorGUILayout.PropertyField(_positionMode);
// Auto Scaling // Auto Scaling
DrawAutoScaling(_autoScalingMode, targets.OfType<UIParticle>()); EditorGUILayout.PropertyField(_autoScalingMode);
// Custom View Size
EditorGUILayout.PropertyField(_useCustomView);
EditorGUI.BeginChangeCheck();
EditorGUI.BeginDisabledGroup(!_useCustomView.boolValue);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_customViewSize);
EditorGUI.indentLevel--;
EditorGUI.EndDisabledGroup();
if (EditorGUI.EndChangeCheck())
{
_customViewSize.floatValue = Mathf.Max(0.1f, _customViewSize.floatValue);
}
// 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())
{ {
EditorApplication.QueuePlayerLoopUpdate();
foreach (var uip in targets.OfType<UIParticle>()) foreach (var uip in targets.OfType<UIParticle>())
{ {
uip.RefreshParticles(uip.particles); uip.RefreshParticles(uip.particles);
@@ -290,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;
@@ -303,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;
@@ -325,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.";
@@ -364,12 +316,14 @@ namespace Coffee.UIExtensions
} }
} }
#endif #endif
Profiler.EndSample();
} }
private 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
@@ -463,7 +417,7 @@ namespace Coffee.UIExtensions
{ {
EditorGUI.BeginDisabledGroup(true); EditorGUI.BeginDisabledGroup(true);
var obj = UIParticleUpdater.GetPrimary(spGroupId.intValue); var obj = UIParticleUpdater.GetPrimary(spGroupId.intValue);
EditorGUILayout.ObjectField("Primary", obj, typeof(UIParticle), false); EditorGUILayout.ObjectField(s_ContentPrimary, obj, typeof(UIParticle), false);
EditorGUI.EndDisabledGroup(); EditorGUI.EndDisabledGroup();
} }
@@ -473,43 +427,11 @@ 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);
} }
#if UNITY_2021_2_OR_NEWER
private static void WindowFunction()
#else
private static void WindowFunction(Object _, SceneView __)
#endif
{
try
{
if (s_SerializedObject == null || !s_SerializedObject.targetObject) return;
var uiParticles = s_SerializedObject.targetObjects.OfType<UIParticle>().ToArray();
if (uiParticles.Any(x => !x || !x.canvas)) return;
s_SerializedObject.Update();
using (new EditorGUILayout.VerticalScope(GUILayout.Width(220f)))
{
var labelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 100;
EditorGUILayout.PropertyField(s_SerializedObject.FindProperty("m_Enabled"));
s_XYZMode = DrawFloatOrVector3Field(s_SerializedObject.FindProperty("m_Scale3D"), s_XYZMode);
EditorGUILayout.PropertyField(s_SerializedObject.FindProperty("m_PositionMode"));
DrawAutoScaling(s_SerializedObject.FindProperty("m_AutoScalingMode"), uiParticles);
EditorGUIUtility.labelWidth = labelWidth;
}
s_SerializedObject.ApplyModifiedProperties();
}
catch
{
// ignored
}
}
private void DestroyUIParticle(UIParticle p, bool ignoreCurrent = false) private void DestroyUIParticle(UIParticle p, bool ignoreCurrent = false)
{ {
if (!p || (ignoreCurrent && target == p)) return; if (!p || (ignoreCurrent && target == p)) return;

212
README.md
View File

@@ -1,61 +1,84 @@
# 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) <!-- omit in toc -->
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.
[![](https://img.shields.io/npm/v/com.coffee.ui-particle?label=openupm&registry_uri=https://package.openupm.com)](https://openupm.com/packages/com.coffee.ui-particle/) [![](https://img.shields.io/npm/v/com.coffee.ui-particle?label=openupm&registry_uri=https://package.openupm.com)](https://openupm.com/packages/com.coffee.ui-particle/)
[![](https://img.shields.io/github/v/release/mob-sakai/ParticleEffectForUGUI?include_prereleases)](https://github.com/mob-sakai/ParticleEffectForUGUI/releases) [![](https://img.shields.io/github/v/release/mob-sakai/ParticleEffectForUGUI)](https://github.com/mob-sakai/ParticleEffectForUGUI/releases)
[![](https://img.shields.io/github/license/mob-sakai/ParticleEffectForUGUI.svg)](https://github.com/mob-sakai/ParticleEffectForUGUI/blob/main/LICENSE.txt) [![](https://img.shields.io/github/license/mob-sakai/ParticleEffectForUGUI.svg)](https://github.com/mob-sakai/ParticleEffectForUGUI/blob/main/LICENSE.md)
![](https://img.shields.io/badge/Unity-2018.2+-57b9d3.svg?style=flat&logo=unity) ![](https://img.shields.io/badge/Unity-2018.2+-57b9d3.svg?style=flat&logo=unity)
![](https://img.shields.io/badge/uGUI_2.0_Ready-57b9d3.svg?style=flat)
![](https://img.shields.io/badge/UPR%2FHDPR_Ready-57b9d3.svg?style=flat)
![](https://github.com/mob-sakai/ParticleEffectForUGUI/actions/workflows/test.yml/badge.svg?branch=develop) ![](https://github.com/mob-sakai/ParticleEffectForUGUI/actions/workflows/test.yml/badge.svg?branch=develop)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg)](http://makeapullrequest.com) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg)](http://makeapullrequest.com)
[![](https://img.shields.io/github/watchers/mob-sakai/ParticleEffectForUGUI.svg?style=social&label=Watch)](https://github.com/mob-sakai/ParticleEffectForUGUI/subscription)
[![](https://img.shields.io/twitter/follow/mob_sakai.svg?label=Follow&style=social)](https://twitter.com/intent/follow?screen_name=mob_sakai) [![](https://img.shields.io/twitter/follow/mob_sakai.svg?label=Follow&style=social)](https://twitter.com/intent/follow?screen_name=mob_sakai)
<< [📝 Description](#-description) | [🎮 Demo](#-demo) | [⚙ Installation](#-installation) | [🚀 Usage](#-usage) | [🛠 Development Note](#-development-note) | [🤝 Contributing](#-contributing) >> << [📝 Description](#-description-) | [📌 Key Features](#-key-features) | [🎮 Demo](#-demo) | [⚙ Installation](#-installation) | [🚀 Usage](#-usage) | [🛠 Development Note](#-development-note) | [🤝 Contributing](#-contributing) >>
<br><br> ## 📝 Description <!-- omit in toc -->
## 📝 Description
![](https://user-images.githubusercontent.com/12690315/41771577-8da4b968-7650-11e8-9524-cd162c422d9d.gif) ![](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](#-key-features)
- [🎮 Demo](#-demo)
- [⚙ Installation](#-installation)
- [Install via OpenUPM](#install-via-openupm)
- [Install via UPM (with Package Manager UI)](#install-via-upm-with-package-manager-ui)
- [Install via UPM (Manually)](#install-via-upm-manually)
- [Install as Embedded Package](#install-as-embedded-package)
- [🚀 Usage](#-usage)
- [Component: UIParticle](#component-uiparticle)
- [Basic Usage](#basic-usage)
- [Usage with Your Existing ParticleSystem Prefab](#usage-with-your-existing-particlesystem-prefab)
- [Usage with `Mask` or `RectMask2D` Component](#usage-with-mask-or-rectmask2d-component)
- [Usage with Script](#usage-with-script)
- [Component: UIParticleAttractor](#component-uiparticleattractor)
- [🛠 Development Note](#-development-note)
- [Compares the Baking mesh approach with the conventional approach](#compares-the-baking-mesh-approach-with-the-conventional-approach)
- [Performance test results](#performance-test-results)
- [🔍 FAQ: Why Are My UIParticles Not Displayed Correctly?](#-faq-why-are-my-uiparticles-not-displayed-correctly)
- [Shader Limitation](#shader-limitation)
- [Built-in shaders are not supported](#built-in-shaders-are-not-supported)
- [(Unity 2018 or 2019) UV.zw components will be discarded](#unity-2018-or-2019-uvzw-components-will-be-discarded)
- [(Unity 2018 or 2019) Custom vertex streams](#unity-2018-or-2019-custom-vertex-streams)
- [Overheads](#overheads)
- [How to Make a Custom Shader to Support `Mask` and `RectMask2D` Component](#how-to-make-a-custom-shader-to-support-mask-and-rectmask2d-component)
- [🤝 Contributing](#-contributing)
- [Issues](#issues)
- [Pull Requests](#pull-requests)
- [Support](#support)
- [License](#license)
- [Author](#author)
- [See Also](#see-also)
* Easy to use: The package is ready to use out of the box. <br><br>
* 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 ## 📌 Key Features
[MeshSharing.gif]: https://user-images.githubusercontent.com/12690315/174311048-c882df81-6c34-4eba-b0aa-5645457692f1.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.
[ParticleAttractor.gif]: https://user-images.githubusercontent.com/12690315/174311027-462929a4-13f0-4ec4-86ea-9c832f2eecf1.gif * **Maskable:** Supports `Mask` or `RectMask2D`.
* **No extra components required:** No need for an additional `Camera`, `RenderTexture`, or `Canvas`.
[AbsolutePosition.gif]: https://user-images.githubusercontent.com/12690315/175751579-5a2357e8-2ecf-4afd-83c8-66e9771bde39.gif * **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.
<img alt="MeshSharing.gif" src="https://user-images.githubusercontent.com/12690315/174311048-c882df81-6c34-4eba-b0aa-5645457692f1.gif" width="450"/>
* **Particle attractor:** Includes a particle attractor component.
<img alt="ParticleAttractor.gif" src="https://user-images.githubusercontent.com/12690315/174311027-462929a4-13f0-4ec4-86ea-9c832f2eecf1.gif" width="450"/>
* **Emission position mode:** Supports relative/absolute particle emission position modes.
<img alt="AbsolutePosition.gif" src="https://user-images.githubusercontent.com/12690315/175751579-5a2357e8-2ecf-4afd-83c8-66e9771bde39.gif" width="450"/>
* **Custom view size:** Fixes min/max particle size mismatch.
![CustomViewSize.gif](https://github.com/mob-sakai/ParticleEffectForUGUI/assets/12690315/dd929959-1a37-420b-b13d-e849022b9c9d)
<br><br> <br><br>
@@ -76,66 +99,86 @@ 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.9.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://github.com/user-attachments/assets/f88f47ad-c606-44bd-9e86-ee3f72eac548)
- To update the package, change suffix `#{version}` to the target version.
- e.g. `https://github.com/mob-sakai/ParticleEffectForUGUI.git#4.9.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.9.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>
## 🚀 Usage ## 🚀 Usage
### UIParticle Component ### Component: UIParticle
`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
@@ -143,7 +186,7 @@ and z-position.
<br><br> <br><br>
#### Basic Usage ### Basic Usage
1. Select `GameObject/UI/ParticleSystem` to create UIParticle with a ParticleSystem. 1. Select `GameObject/UI/ParticleSystem` to create UIParticle with a ParticleSystem.
![particle](https://user-images.githubusercontent.com/12690315/95007361-cad0e880-0649-11eb-8835-f145d62c5977.png) ![particle](https://user-images.githubusercontent.com/12690315/95007361-cad0e880-0649-11eb-8835-f145d62c5977.png)
@@ -152,7 +195,7 @@ and z-position.
<br> <br>
#### With Your Existing ParticleSystem Prefab ### Usage with Your Existing ParticleSystem Prefab
1. Select `GameObject/UI/ParticleSystem (Empty)` to create UIParticle. 1. Select `GameObject/UI/ParticleSystem (Empty)` to create UIParticle.
![empty](https://user-images.githubusercontent.com/12690315/95007362-cb697f00-0649-11eb-8a09-29b0a13791e4.png) ![empty](https://user-images.githubusercontent.com/12690315/95007362-cb697f00-0649-11eb-8a09-29b0a13791e4.png)
@@ -161,7 +204,7 @@ and z-position.
<br> <br>
#### With `Mask` or `RectMask2D` Component ### Usage with `Mask` or `RectMask2D` Component
If you want to mask particles, set a stencil-supported shader (such as `UI/UIAdditive`) to the material for If you want to mask particles, set a stencil-supported shader (such as `UI/UIAdditive`) to the material for
ParticleSystem. ParticleSystem.
@@ -173,12 +216,13 @@ section.
<br><br> <br><br>
### Script usage ### Usage with Script
```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();
@@ -191,14 +235,14 @@ uiParticle.Stop();
<br><br> <br><br>
### UIParticleAttractor component ### Component: UIParticleAttractor
`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 Systems**: Attracts particles generated by the specified ParticleSystems.
- **Destination Radius**: Once the particle is within the radius, the particle lifetime will become 0, and `OnAttracted` - **Destination Radius**: Once the particle is within the radius, the particle lifetime will become 0, and `OnAttracted`
will be called. will be called.
- **Delay Rate**: Delay to start attracting. It is a percentage of the particle's start lifetime. - **Delay Rate**: Delay to start attracting. It is a percentage of the particle's start lifetime.
@@ -343,7 +387,7 @@ When improving performance, keep the following in mind:
- Consider a single material, atlasing the sprites, and using `Sprite` mode in the `Texture Sheet Animation` module - Consider a single material, atlasing the sprites, and using `Sprite` mode in the `Texture Sheet Animation` module
in the ParticleSystem. in the ParticleSystem.
### How to Make a Custom Shader to Support Mask/RectMask2D Component ### How to Make a Custom Shader to Support `Mask` and `RectMask2D` Component
<details> <details>
<summary>Shader tips</summary> <summary>Shader tips</summary>

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,26 +89,41 @@ 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]
[Tooltip("Use a custom view.\n" +
"Use this if the particles are not displayed correctly due to min/max particle size.")]
private bool m_UseCustomView;
[SerializeField]
[Tooltip("Custom view size.\n" +
"Change the bake view size.")]
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?
@@ -116,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.
@@ -159,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
{ {
@@ -174,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;
@@ -191,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
@@ -211,6 +236,26 @@ namespace Coffee.UIExtensions
} }
} }
/// <summary>
/// Use a custom view.
/// Use this if the particles are not displayed correctly due to min/max particle size.
/// </summary>
public bool useCustomView
{
get => m_UseCustomView;
set => m_UseCustomView = value;
}
/// <summary>
/// Custom view size.
/// Change the bake view size.
/// </summary>
public float customViewSize
{
get => m_CustomViewSize;
set => m_CustomViewSize = Mathf.Max(0.1f, value);
}
internal bool useMeshSharing => m_MeshSharing != MeshSharing.None; internal bool useMeshSharing => m_MeshSharing != MeshSharing.None;
internal bool isPrimary => internal bool isPrimary =>
@@ -256,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>
@@ -328,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;
@@ -346,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 =>
@@ -380,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 =>
@@ -389,24 +443,52 @@ 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;
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;
if (go.TryGetComponent<Camera>(out var cam) && cam == _bakeCamera) continue;
if (go.TryGetComponent<UIParticleRenderer>(out var _)) continue;
go.SetActive(false); go.SetActive(false);
if (destroyOldParticles) if (destroyOldParticles)
{ {
@@ -421,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;
@@ -428,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++)
{ {
@@ -452,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);
@@ -501,8 +610,12 @@ namespace Coffee.UIExtensions
} }
var currentScale = transform.localScale; var currentScale = transform.localScale;
_storedScale = currentScale; if (!_isScaleStored)
_isScaleStored = true; {
_storedScale = currentScale.IsVisible() ? currentScale : Vector3.one;
_isScaleStored = true;
}
_tracker.Add(this, rectTransform, DrivenTransformProperties.Scale); _tracker.Add(this, rectTransform, DrivenTransformProperties.Scale);
var newScale = parentScale.Inverse(); var newScale = parentScale.Inverse();
if (currentScale != newScale) if (currentScale != newScale)
@@ -518,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();
@@ -530,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);
} }
} }
@@ -581,7 +694,16 @@ namespace Coffee.UIExtensions
private Camera GetBakeCamera() private Camera GetBakeCamera()
{ {
if (!canvas) return Camera.main; if (!canvas) return Camera.main;
if (_bakeCamera) return _bakeCamera; if (!useCustomView && canvas.renderMode != RenderMode.ScreenSpaceOverlay && canvas.rootCanvas.worldCamera)
{
return canvas.rootCanvas.worldCamera;
}
if (_bakeCamera)
{
_bakeCamera.orthographicSize = useCustomView ? customViewSize : 10;
return _bakeCamera;
}
// Find existing baking camera. // Find existing baking camera.
var childCount = transform.childCount; var childCount = transform.childCount;
@@ -598,11 +720,7 @@ namespace Coffee.UIExtensions
// Create baking camera. // Create baking camera.
if (!_bakeCamera) if (!_bakeCamera)
{ {
var go = new GameObject("[generated] UIParticle BakingCamera") var go = new GameObject("[generated] UIParticle BakingCamera");
{
// hideFlags = HideFlags.HideAndDontSave
hideFlags = HideFlags.DontSave
};
go.SetActive(false); go.SetActive(false);
go.transform.SetParent(transform, false); go.transform.SetParent(transform, false);
_bakeCamera = go.AddComponent<Camera>(); _bakeCamera = go.AddComponent<Camera>();
@@ -610,7 +728,7 @@ namespace Coffee.UIExtensions
// Setup baking camera. // Setup baking camera.
_bakeCamera.enabled = false; _bakeCamera.enabled = false;
_bakeCamera.orthographicSize = 1000; _bakeCamera.orthographicSize = useCustomView ? customViewSize : 10;
_bakeCamera.transform.SetPositionAndRotation(new Vector3(0, 0, -1000), Quaternion.identity); _bakeCamera.transform.SetPositionAndRotation(new Vector3(0, 0, -1000), Quaternion.identity);
_bakeCamera.orthographic = true; _bakeCamera.orthographic = true;
_bakeCamera.farClipPlane = 2000f; _bakeCamera.farClipPlane = 2000f;
@@ -621,6 +739,9 @@ namespace Coffee.UIExtensions
_bakeCamera.renderingPath = RenderingPath.Forward; _bakeCamera.renderingPath = RenderingPath.Forward;
_bakeCamera.useOcclusionCulling = false; _bakeCamera.useOcclusionCulling = false;
_bakeCamera.gameObject.SetActive(false);
_bakeCamera.gameObject.hideFlags = HideFlags.HideAndDontSave;
return _bakeCamera; return _bakeCamera;
} }
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using Coffee.UIParticleExtensions; using Coffee.UIParticleExtensions;
using UnityEngine; using UnityEngine;
using UnityEngine.Events; using UnityEngine.Events;
@@ -6,7 +7,7 @@ using UnityEngine.Events;
namespace Coffee.UIExtensions namespace Coffee.UIExtensions
{ {
[ExecuteAlways] [ExecuteAlways]
public class UIParticleAttractor : MonoBehaviour public class UIParticleAttractor : MonoBehaviour, ISerializationCallbackReceiver
{ {
public enum Movement public enum Movement
{ {
@@ -22,8 +23,12 @@ namespace Coffee.UIExtensions
} }
[SerializeField] [SerializeField]
[HideInInspector]
private ParticleSystem m_ParticleSystem; private ParticleSystem m_ParticleSystem;
[SerializeField]
private List<ParticleSystem> m_ParticleSystems = new List<ParticleSystem>();
[Range(0.1f, 10f)] [Range(0.1f, 10f)]
[SerializeField] [SerializeField]
private float m_DestinationRadius = 1; private float m_DestinationRadius = 1;
@@ -45,7 +50,7 @@ namespace Coffee.UIExtensions
[SerializeField] [SerializeField]
private UnityEvent m_OnAttracted; private UnityEvent m_OnAttracted;
private UIParticle _uiParticle; private List<UIParticle> _uiParticles = new List<UIParticle>();
public float destinationRadius public float destinationRadius
{ {
@@ -84,25 +89,46 @@ namespace Coffee.UIExtensions
} }
/// <summary> /// <summary>
/// The target ParticleSystem to attract. /// The target ParticleSystems to attract. Use <see cref="AddParticleSystem"/> and
/// <see cref="RemoveParticleSystem"/> to modify the list.
/// </summary> /// </summary>
#if UNITY_EDITOR public IReadOnlyList<ParticleSystem> particleSystems => m_ParticleSystems;
public new ParticleSystem particleSystem
#else public void AddParticleSystem(ParticleSystem ps)
public ParticleSystem particleSystem
#endif
{ {
get => m_ParticleSystem; if (m_ParticleSystems == null)
set
{ {
m_ParticleSystem = value; m_ParticleSystems = new List<ParticleSystem>();
ApplyParticleSystem();
} }
var i = m_ParticleSystems.IndexOf(ps);
if (0 <= i) return; // Already added: skip
m_ParticleSystems.Add(ps);
_uiParticles.Clear();
}
public void RemoveParticleSystem(ParticleSystem ps)
{
if (m_ParticleSystems == null)
{
return;
}
var i = m_ParticleSystems.IndexOf(ps);
if (i < 0) return; // Not found. skip
m_ParticleSystems.RemoveAt(i);
_uiParticles.Clear();
}
private void Awake()
{
UpgradeIfNeeded();
} }
private void OnEnable() private void OnEnable()
{ {
ApplyParticleSystem();
UIParticleUpdater.Register(this); UIParticleUpdater.Register(this);
} }
@@ -113,85 +139,96 @@ namespace Coffee.UIExtensions
private void OnDestroy() private void OnDestroy()
{ {
_uiParticle = null; _uiParticles = null;
m_ParticleSystem = null; m_ParticleSystems = null;
} }
internal void Attract() internal void Attract()
{ {
if (m_ParticleSystem == null) return; // Collect UIParticle if needed (same size as m_ParticleSystems)
CollectUIParticlesIfNeeded();
var count = m_ParticleSystem.particleCount; for (var particleIndex = 0; particleIndex < this.m_ParticleSystems.Count; particleIndex++)
if (count == 0) return;
var particles = ParticleSystemExtensions.GetParticleArray(count);
m_ParticleSystem.GetParticles(particles, count);
var dstPos = GetDestinationPosition();
for (var i = 0; i < count; i++)
{ {
// Attracted var particleSystem = m_ParticleSystems[particleIndex];
var p = particles[i];
if (0f < p.remainingLifetime && Vector3.Distance(p.position, dstPos) < m_DestinationRadius)
{
p.remainingLifetime = 0f;
particles[i] = p;
if (m_OnAttracted != null) // Skip: The ParticleSystem is not active
if (particleSystem == null || !particleSystem.gameObject.activeInHierarchy) continue;
// Skip: No active particles
var count = particleSystem.particleCount;
if (count == 0) continue;
var particles = ParticleSystemExtensions.GetParticleArray(count);
particleSystem.GetParticles(particles, count);
var uiParticle = _uiParticles[particleIndex];
var dstPos = this.GetDestinationPosition(uiParticle, particleSystem);
for (var i = 0; i < count; i++)
{
// Attracted
var p = particles[i];
if (0f < p.remainingLifetime && Vector3.Distance(p.position, dstPos) < this.m_DestinationRadius)
{ {
try p.remainingLifetime = 0f;
particles[i] = p;
if (this.m_OnAttracted != null)
{ {
m_OnAttracted.Invoke(); try
} {
catch (Exception e) this.m_OnAttracted.Invoke();
{ }
Debug.LogException(e); catch (Exception e)
{
Debug.LogException(e);
}
} }
continue;
} }
continue; // Calc attracting time
var delayTime = p.startLifetime * this.m_DelayRate;
var duration = p.startLifetime - delayTime;
var time = Mathf.Max(0, p.startLifetime - p.remainingLifetime - delayTime);
// Delay
if (time <= 0) continue;
// Attract
p.position = this.GetAttractedPosition(p.position, dstPos, duration, time);
p.velocity *= 0.5f;
particles[i] = p;
} }
// Calc attracting time particleSystem.SetParticles(particles, count);
var delayTime = p.startLifetime * m_DelayRate;
var duration = p.startLifetime - delayTime;
var time = Mathf.Max(0, p.startLifetime - p.remainingLifetime - delayTime);
// Delay
if (time <= 0) continue;
// Attract
p.position = GetAttractedPosition(p.position, dstPos, duration, time);
p.velocity *= 0.5f;
particles[i] = p;
} }
m_ParticleSystem.SetParticles(particles, count);
} }
private Vector3 GetDestinationPosition() private Vector3 GetDestinationPosition(UIParticle uiParticle, ParticleSystem particleSystem)
{ {
var isUI = _uiParticle && _uiParticle.enabled; var isUI = uiParticle && uiParticle.enabled;
var psPos = m_ParticleSystem.transform.position; var psPos = particleSystem.transform.position;
var attractorPos = transform.position; var attractorPos = transform.position;
var dstPos = attractorPos; var dstPos = attractorPos;
var isLocalSpace = m_ParticleSystem.IsLocalSpace(); var isLocalSpace = particleSystem.IsLocalSpace();
if (isLocalSpace) if (isLocalSpace)
{ {
dstPos = m_ParticleSystem.transform.InverseTransformPoint(dstPos); dstPos = particleSystem.transform.InverseTransformPoint(dstPos);
} }
if (isUI) if (isUI)
{ {
var inverseScale = _uiParticle.parentScale.Inverse(); var inverseScale = uiParticle.parentScale.Inverse();
var scale3d = _uiParticle.scale3DForCalc; var scale3d = uiParticle.scale3DForCalc;
dstPos = dstPos.GetScaled(inverseScale, scale3d.Inverse()); dstPos = dstPos.GetScaled(inverseScale, scale3d.Inverse());
// Relative mode // Relative mode
if (_uiParticle.positionMode == UIParticle.PositionMode.Relative) if (uiParticle.positionMode == UIParticle.PositionMode.Relative)
{ {
var diff = _uiParticle.transform.position - psPos; var diff = uiParticle.transform.position - psPos;
diff.Scale(scale3d - inverseScale); diff.Scale(scale3d - inverseScale);
diff.Scale(scale3d.Inverse()); diff.Scale(scale3d.Inverse());
dstPos += diff; dstPos += diff;
@@ -237,25 +274,59 @@ namespace Coffee.UIExtensions
return Vector3.MoveTowards(current, target, speed); return Vector3.MoveTowards(current, target, speed);
} }
private void ApplyParticleSystem() private void CollectUIParticlesIfNeeded()
{ {
_uiParticle = null; if (m_ParticleSystems.Count == 0 || _uiParticles.Count != 0) return;
if (m_ParticleSystem == null)
{
#if UNITY_EDITOR
if (Application.isPlaying)
#endif
{
Debug.LogError("No particle system attached to particle attractor script", this);
}
return; // Expand capacity
if (_uiParticles.Capacity < m_ParticleSystems.Capacity)
{
_uiParticles.Capacity = m_ParticleSystems.Capacity;
} }
_uiParticle = m_ParticleSystem.GetComponentInParent<UIParticle>(true); // Find UIParticle that controls the ParticleSystem
if (_uiParticle && !_uiParticle.particles.Contains(m_ParticleSystem)) for (var i = 0; i < m_ParticleSystems.Count; i++)
{ {
_uiParticle = null; var ps = m_ParticleSystems[i];
if (ps == null)
{
_uiParticles.Add(null);
continue;
}
var uiParticle = ps.GetComponentInParent<UIParticle>(true);
_uiParticles.Add(uiParticle.particles.Contains(ps) ? uiParticle : null);
}
}
#if UNITY_EDITOR
private void OnValidate()
{
_uiParticles.Clear();
}
#endif
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
UpgradeIfNeeded();
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
}
private void UpgradeIfNeeded()
{
// Multiple ParticleSystems support: from 'm_ParticleSystem' to 'm_ParticleSystems'
if (m_ParticleSystem != null)
{
if (!m_ParticleSystems.Contains(m_ParticleSystem))
{
m_ParticleSystems.Add(m_ParticleSystem);
}
m_ParticleSystem = null;
Debug.Log($"Upgraded!");
} }
} }
} }

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;

View File

@@ -51,7 +51,11 @@ namespace Coffee.UIExtensions.Demo
public void EnableAnimations(bool flag) public void EnableAnimations(bool flag)
{ {
#if UNITY_2023_1_OR_NEWER
foreach (var animator in FindObjectsByType<Animator>(FindObjectsInactive.Include, FindObjectsSortMode.None))
#else
foreach (var animator in FindObjectsOfType<Animator>()) foreach (var animator in FindObjectsOfType<Animator>())
#endif
{ {
animator.enabled = flag; animator.enabled = flag;
} }
@@ -79,7 +83,11 @@ namespace Coffee.UIExtensions.Demo
public void UIParticle_Scale(float scale) public void UIParticle_Scale(float scale)
{ {
#if UNITY_2023_1_OR_NEWER
foreach (var uip in FindObjectsByType<UIParticle>(FindObjectsInactive.Include, FindObjectsSortMode.None))
#else
foreach (var uip in FindObjectsOfType<UIParticle>()) foreach (var uip in FindObjectsOfType<UIParticle>())
#endif
{ {
uip.scale = scale; uip.scale = scale;
} }
@@ -87,7 +95,11 @@ namespace Coffee.UIExtensions.Demo
public void ParticleSystem_WorldSpaseSimulation(bool flag) public void ParticleSystem_WorldSpaseSimulation(bool flag)
{ {
#if UNITY_2023_1_OR_NEWER
foreach (var p in FindObjectsByType<ParticleSystem>(FindObjectsInactive.Include, FindObjectsSortMode.None))
#else
foreach (var p in FindObjectsOfType<ParticleSystem>()) foreach (var p in FindObjectsOfType<ParticleSystem>())
#endif
{ {
var main = p.main; var main = p.main;
main.simulationSpace = flag main.simulationSpace = flag
@@ -123,7 +135,11 @@ namespace Coffee.UIExtensions.Demo
public void ParticleSystem_SetScale(float scale) public void ParticleSystem_SetScale(float scale)
{ {
#if UNITY_2023_1_OR_NEWER
foreach (var ps in FindObjectsByType<ParticleSystem>(FindObjectsInactive.Include, FindObjectsSortMode.None))
#else
foreach (var ps in FindObjectsOfType<ParticleSystem>()) foreach (var ps in FindObjectsOfType<ParticleSystem>())
#endif
{ {
ps.transform.localScale = new Vector3(scale, scale, scale); ps.transform.localScale = new Vector3(scale, scale, scale);
} }

View File

@@ -42,12 +42,12 @@ TextureImporter:
compressionQuality: 50 compressionQuality: 50
spriteMode: 1 spriteMode: 1
spriteExtrude: 1 spriteExtrude: 1
spriteMeshType: 1 spriteMeshType: 0
alignment: 0 alignment: 0
spritePivot: {x: 0.5, y: 0.5} spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100 spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0} spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1 spriteGenerateFallbackPhysicsShape: 0
alphaUsage: 1 alphaUsage: 1
alphaIsTransparency: 1 alphaIsTransparency: 1
spriteTessellationDetail: -1 spriteTessellationDetail: -1

View File

@@ -27,7 +27,7 @@ TextureImporter:
generateCubemap: 6 generateCubemap: 6
cubemapConvolution: 0 cubemapConvolution: 0
seamlessCubemap: 0 seamlessCubemap: 0
textureFormat: 1 textureFormat: 4
maxTextureSize: 2048 maxTextureSize: 2048
textureSettings: textureSettings:
serializedVersion: 2 serializedVersion: 2
@@ -42,12 +42,12 @@ TextureImporter:
compressionQuality: 50 compressionQuality: 50
spriteMode: 1 spriteMode: 1
spriteExtrude: 1 spriteExtrude: 1
spriteMeshType: 1 spriteMeshType: 0
alignment: 0 alignment: 0
spritePivot: {x: 0.5, y: 0.5} spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100 spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0} spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1 spriteGenerateFallbackPhysicsShape: 0
alphaUsage: 1 alphaUsage: 1
alphaIsTransparency: 1 alphaIsTransparency: 1
spriteTessellationDetail: -1 spriteTessellationDetail: -1

View File

@@ -42,12 +42,12 @@ TextureImporter:
compressionQuality: 50 compressionQuality: 50
spriteMode: 1 spriteMode: 1
spriteExtrude: 1 spriteExtrude: 1
spriteMeshType: 1 spriteMeshType: 0
alignment: 0 alignment: 0
spritePivot: {x: 0.5, y: 0.5} spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100 spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0} spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1 spriteGenerateFallbackPhysicsShape: 0
alphaUsage: 0 alphaUsage: 0
alphaIsTransparency: 1 alphaIsTransparency: 1
spriteTessellationDetail: -1 spriteTessellationDetail: -1

View File

@@ -27,7 +27,7 @@ TextureImporter:
generateCubemap: 6 generateCubemap: 6
cubemapConvolution: 0 cubemapConvolution: 0
seamlessCubemap: 0 seamlessCubemap: 0
textureFormat: -1 textureFormat: 4
maxTextureSize: 2048 maxTextureSize: 2048
textureSettings: textureSettings:
serializedVersion: 2 serializedVersion: 2
@@ -42,12 +42,12 @@ TextureImporter:
compressionQuality: 50 compressionQuality: 50
spriteMode: 1 spriteMode: 1
spriteExtrude: 1 spriteExtrude: 1
spriteMeshType: 1 spriteMeshType: 0
alignment: 0 alignment: 0
spritePivot: {x: 0.5, y: 0.5} spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100 spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0} spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1 spriteGenerateFallbackPhysicsShape: 0
alphaUsage: 1 alphaUsage: 1
alphaIsTransparency: 1 alphaIsTransparency: 1
spriteTessellationDetail: -1 spriteTessellationDetail: -1

View File

@@ -2,7 +2,7 @@
"name": "com.coffee.ui-particle", "name": "com.coffee.ui-particle",
"displayName": "UI Particle", "displayName": "UI Particle",
"description": "This package provides a component to render particle effects for uGUI.\nThe particle rendering is maskable and sortable, without the need for an extra Camera, RenderTexture, or Canvas.", "description": "This package provides a component to render particle effects for uGUI.\nThe particle rendering is maskable and sortable, without the need for an extra Camera, RenderTexture, or Canvas.",
"version": "4.7.1", "version": "4.9.0",
"unity": "2018.2", "unity": "2018.2",
"license": "MIT", "license": "MIT",
"repository": { "repository": {