Compare commits

..

27 Commits

Author SHA1 Message Date
semantic-release-bot
16ab8d7bb8 chore(release): 4.13.1 [skip ci]
## [4.13.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.13.0...v4.13.1) (2026-06-26)

### Bug Fixes

* `ParticleSystemPreviewer` is no longer included in build ([bad976c](bad976cf01))
* add support for Unity 6.7 ([26fd3da](26fd3da20b)), closes [#408](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/408)
2026-06-26 02:57:47 +00:00
mob-sakai
2350ea82d2 fix: ParticleSystemPreviewer is no longer included in build 2026-06-26 11:55:28 +09:00
mob-sakai
a7f64c6033 fix: add support for Unity 6.7
close #408
2026-06-26 11:51:43 +09:00
semantic-release-bot
51a4708b7b chore(release): 4.13.0 [skip ci]
# [4.13.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.12.2...v4.13.0) (2026-06-25)

### Bug Fixes

* `UI/Additive` shader does not support RectMask2D softness. ([83145d3](83145d3a6e))
* rename `UIParticleProjectSettings.enableLinearToGamma` to `autoColorCorrection` ([10f82a8](10f82a8579))
* Support for skipping "reload domain" ([b7bb112](b7bb1124f6)), closes [#406](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/406)
* there is a compilation error in Unity 2019.2 or earlier ([c327632](c327632e1e)), closes [#407](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/407)

### Features

* add fields located outside the `Properties` block in the shader as `Animatable Properties` ([ea2fcfd](ea2fcfd809)), closes [#399](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/399)
* preview the ParticleSystem playback when selecting a UIParticle in the Editor ([108bcfb](108bcfbd24))
* project-wide default view size for baking ([8e0ff10](8e0ff10c73)), closes [#360](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/360)
2026-06-25 11:18:58 +00:00
mob-sakai
ba79bcca56 docs: update doc 2026-06-25 20:07:03 +09:00
mob-sakai
b9b664a11c feat: add fields located outside the Properties block in the shader as Animatable Properties
close #399
2026-06-25 19:40:46 +09:00
mob-sakai
bc5a1f4435 feat: project-wide default view size for baking
close #360
2026-06-25 19:40:46 +09:00
mob-sakai
733901436d fix: Support for skipping "reload domain"
close #406
2026-06-25 13:07:47 +09:00
mob-sakai
f0b036de65 docs: update readme 2026-06-25 13:07:47 +09:00
mob-sakai
d93cacc70d fix: rename UIParticleProjectSettings.enableLinearToGamma to autoColorCorrection 2026-06-25 13:07:47 +09:00
mob-sakai
5aff2b480a fix: UI/Additive shader does not support RectMask2D softness. 2026-06-25 13:07:46 +09:00
mob-sakai
de80249552 fix: there is a compilation error in Unity 2019.2 or earlier
close #407
2026-06-25 13:07:46 +09:00
mob-sakai
c0a8e322b1 chore: update internal 2026-06-25 13:07:46 +09:00
mob-sakai
1ac1b347af feat: preview the ParticleSystem playback when selecting a UIParticle in the Editor 2026-06-25 13:07:46 +09:00
semantic-release-bot
6b956b92e0 chore(release): 4.12.2 [skip ci]
## [4.12.2](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.12.1...v4.12.2) (2026-06-08)

### Bug Fixes

* add `meshCleared` flag to optimize mesh clearing ([859fa20](859fa20d29))
* fix Unity6.5 compile errors and warnings ([a5ee687](a5ee687821)), closes [#400](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/400)
* potential access to UIParticleRenderer that has already been destroyed ([b740dd6](b740dd662d)), closes [#403](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/403)
* updated support for some changed menu paths ([f8ac986](f8ac9869f1)), closes [#397](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/397)
2026-06-08 13:33:41 +00:00
mob-sakai
d03bf19b08 update internal code 2026-06-08 15:55:42 +09:00
mob-sakai
00be26d4ce fix: potential access to UIParticleRenderer that has already been destroyed
close #403
2026-06-08 13:28:24 +09:00
tako
34588b6e5c fix: updated support for some changed menu paths
close #397
2026-06-08 13:28:24 +09:00
tako
c2fb60a988 fix: fix Unity6.5 compile errors and warnings
close #400
2026-06-08 13:28:24 +09:00
Minhyuk Kim
babd2a7275 fix: add meshCleared flag to optimize mesh clearing 2026-06-08 11:33:57 +09:00
semantic-release-bot
92fb173507 chore(release): 4.12.1 [skip ci]
## [4.12.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.12.0...v4.12.1) (2026-03-24)

### Bug Fixes

* ignore "EditorOnly" tagged gameObjects on refresh ([031d46a](031d46a321))
2026-03-24 08:53:33 +00:00
mob-sakai
298dc9a093 fix: ignore "EditorOnly" tagged gameObjects on refresh 2026-03-24 17:53:01 +09:00
semantic-release-bot
143face565 chore(release): 4.12.0 [skip ci]
# [4.12.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.11.4...v4.12.0) (2026-03-24)

### Features

* explicit null checks ([5384f61](5384f61c56))
2026-03-24 08:34:58 +00:00
mob-sakai
319ab5fe06 feat: explicit null checks 2026-03-24 17:32:31 +09:00
semantic-release-bot
04c1ca72cd chore(release): 4.11.4 [skip ci]
## [4.11.4](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.11.3...v4.11.4) (2025-12-24)

### Bug Fixes

* add early return for case where subEmitter module is disabled ([d1386a1](d1386a1221))
* avoid endless loop ([eb2e862](eb2e862e80)), closes [#392](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/392)
2025-12-24 11:17:10 +00:00
mob-sakai
f2df47aeec fix: avoid endless loop
close #392
2025-12-24 20:10:01 +09:00
mob-sakai
1dfd756721 fix: add early return for case where subEmitter module is disabled 2025-12-05 09:58:23 +09:00
44 changed files with 1557 additions and 257 deletions

View File

@@ -1,3 +1,60 @@
## [4.13.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.13.0...v4.13.1) (2026-06-26)
### Bug Fixes
* `ParticleSystemPreviewer` is no longer included in build ([bad976c](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/bad976cf01180bdabf5cd2f1760e069afdcb644a))
* add support for Unity 6.7 ([26fd3da](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/26fd3da20b6b07f7a26df662358c53757a7cc5f5)), closes [#408](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/408)
# [4.13.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.12.2...v4.13.0) (2026-06-25)
### Bug Fixes
* `UI/Additive` shader does not support RectMask2D softness. ([83145d3](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/83145d3a6ede3831d1f7c49afde6ef7d8a498d21))
* rename `UIParticleProjectSettings.enableLinearToGamma` to `autoColorCorrection` ([10f82a8](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/10f82a8579a2d055f897e7a8d6f3b5ba1fc43ae8))
* Support for skipping "reload domain" ([b7bb112](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/b7bb1124f6921ebca1b48991462637767b95e504)), closes [#406](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/406)
* there is a compilation error in Unity 2019.2 or earlier ([c327632](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/c327632e1eb203351ba1a1087a19eb7838c22530)), closes [#407](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/407)
### Features
* add fields located outside the `Properties` block in the shader as `Animatable Properties` ([ea2fcfd](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/ea2fcfd8099a24b92624d34600beb519fb9d2b38)), closes [#399](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/399)
* preview the ParticleSystem playback when selecting a UIParticle in the Editor ([108bcfb](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/108bcfbd246960dc730546dc334424cf2e17bfd4))
* project-wide default view size for baking ([8e0ff10](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/8e0ff10c7344d9f53d2f4f096ddeb7392b67f930)), closes [#360](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/360)
## [4.12.2](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.12.1...v4.12.2) (2026-06-08)
### Bug Fixes
* add `meshCleared` flag to optimize mesh clearing ([859fa20](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/859fa20d297c3f44e3361f20dbb7ce966407e03e))
* fix Unity6.5 compile errors and warnings ([a5ee687](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/a5ee6878212be2fc4d7b48879426f239e8753009)), closes [#400](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/400)
* potential access to UIParticleRenderer that has already been destroyed ([b740dd6](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/b740dd662d423c6bef849662ce1b0bfbb4940ed4)), closes [#403](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/403)
* updated support for some changed menu paths ([f8ac986](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/f8ac9869f141238169730e74f5d65c4fc6081f51)), closes [#397](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/397)
## [4.12.1](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.12.0...v4.12.1) (2026-03-24)
### Bug Fixes
* ignore "EditorOnly" tagged gameObjects on refresh ([031d46a](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/031d46a3216c942d2d1a6ccfadf5f0b9e3ce3006))
# [4.12.0](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.11.4...v4.12.0) (2026-03-24)
### Features
* explicit null checks ([5384f61](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/5384f61c569e9f78ff9d5b45acfc6f5c2f021a87))
## [4.11.4](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.11.3...v4.11.4) (2025-12-24)
### Bug Fixes
* add early return for case where subEmitter module is disabled ([d1386a1](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/d1386a12216743a6e09f1b9b87bea1dfcf7702e4))
* avoid endless loop ([eb2e862](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/eb2e862e80e549c8cf16ddfed776c101c2413bac)), closes [#392](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/392)
## [4.11.3](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.11.2...v4.11.3) (2025-10-14) ## [4.11.3](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v4.11.2...v4.11.3) (2025-10-14)

View File

@@ -3,15 +3,19 @@ using System.Linq;
using System.Text; using System.Text;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using ShaderPropertyType = Coffee.UIExtensions.AnimatableProperty.ShaderPropertyType;
namespace Coffee.UIExtensions namespace Coffee.UIExtensions
{ {
internal static class AnimatablePropertyEditor internal static class AnimatablePropertyEditor
{ {
private static readonly GUIContent s_ContentNothing = new GUIContent("Nothing"); private static readonly GUIContent s_ContentNothing = new GUIContent("Nothing");
private static readonly GUIContent s_ContentCustom = new GUIContent("Add Custom...");
private static readonly List<string> s_ActiveNames = new List<string>(); private static readonly List<string> s_ActiveNames = new List<string>();
private static readonly StringBuilder s_Sb = new StringBuilder(); private static readonly StringBuilder s_Sb = new StringBuilder();
private static readonly HashSet<string> s_Names = new HashSet<string>(); private static readonly HashSet<string> s_Names = new HashSet<string>();
private static ShaderProperty s_CustomProperty = new ShaderProperty("", ShaderPropertyType.None);
private static bool s_ShowCustomProperty = false;
private static string CollectActiveNames(SerializedProperty sp, List<string> result) private static string CollectActiveNames(SerializedProperty sp, List<string> result)
{ {
@@ -51,8 +55,19 @@ namespace Coffee.UIExtensions
? "-" ? "-"
: CollectActiveNames(sp, s_ActiveNames); : CollectActiveNames(sp, s_ActiveNames);
if (!GUI.Button(rect, text, EditorStyles.popup)) return; if (GUI.Button(rect, text, EditorStyles.popup))
{
ShowMenu(sp, mats);
}
if (s_ShowCustomProperty)
{
DrawCustomProperty(sp, ref s_CustomProperty);
}
}
private static void ShowMenu(SerializedProperty sp, List<Material> mats)
{
var gm = new GenericMenu(); var gm = new GenericMenu();
gm.AddItem(s_ContentNothing, s_ActiveNames.Count == 0, x => gm.AddItem(s_ContentNothing, s_ActiveNames.Count == 0, x =>
{ {
@@ -61,13 +76,20 @@ namespace Coffee.UIExtensions
current.serializedObject.ApplyModifiedProperties(); current.serializedObject.ApplyModifiedProperties();
}, sp); }, sp);
gm.AddItem(s_ContentCustom, s_ShowCustomProperty, () =>
{
s_ShowCustomProperty = !s_ShowCustomProperty;
s_CustomProperty.Reset();
});
gm.AddSeparator("");
if (!sp.hasMultipleDifferentValues) if (!sp.hasMultipleDifferentValues)
{ {
for (var i = 0; i < sp.arraySize; i++) for (var i = 0; i < sp.arraySize; i++)
{ {
var p = sp.GetArrayElementAtIndex(i); var p = sp.GetArrayElementAtIndex(i);
var name = p.FindPropertyRelative("m_Name").stringValue; var name = p.FindPropertyRelative("m_Name").stringValue;
var type = (AnimatableProperty.ShaderPropertyType)p.FindPropertyRelative("m_Type").intValue; var type = (ShaderPropertyType)p.FindPropertyRelative("m_Type").intValue;
AddMenu(gm, sp, new ShaderProperty(name, type), false); AddMenu(gm, sp, new ShaderProperty(name, type), false);
} }
} }
@@ -76,17 +98,26 @@ namespace Coffee.UIExtensions
for (var j = 0; j < mats.Count; j++) for (var j = 0; j < mats.Count; j++)
{ {
var mat = mats[j]; var mat = mats[j];
if (!mat || !mat.shader) continue; if (mat == null || mat.shader == null) continue;
#if UNITY_6000_5_OR_NEWER
for (var i = 0; i < mat.shader.GetPropertyCount(); i++)
#else
for (var i = 0; i < ShaderUtil.GetPropertyCount(mat.shader); i++) for (var i = 0; i < ShaderUtil.GetPropertyCount(mat.shader); i++)
#endif
{ {
#if UNITY_6000_5_OR_NEWER
var name = mat.shader.GetPropertyName(i);
var type = (ShaderPropertyType)mat.shader.GetPropertyType(i);
#else
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 = (ShaderPropertyType)ShaderUtil.GetPropertyType(mat.shader, i);
#endif
if (!s_Names.Add(name)) continue; if (!s_Names.Add(name)) continue;
AddMenu(gm, sp, new ShaderProperty(name, type), true); AddMenu(gm, sp, new ShaderProperty(name, type), true);
if (type != AnimatableProperty.ShaderPropertyType.Texture) continue; if (type != ShaderPropertyType.Texture) continue;
AddMenu(gm, sp, new ShaderProperty($"{name}_ST"), true); AddMenu(gm, sp, new ShaderProperty($"{name}_ST"), true);
AddMenu(gm, sp, new ShaderProperty($"{name}_HDR"), true); AddMenu(gm, sp, new ShaderProperty($"{name}_HDR"), true);
@@ -102,41 +133,82 @@ namespace Coffee.UIExtensions
if (add && s_ActiveNames.Contains(prop.name)) return; if (add && s_ActiveNames.Contains(prop.name)) return;
var label = new GUIContent($"{prop.name} ({prop.type})"); var label = new GUIContent($"{prop.name} ({prop.type})");
menu.AddItem(label, s_ActiveNames.Contains(prop.name), () => menu.AddItem(label, s_ActiveNames.Contains(prop.name), () => AddProp(sp, prop));
{ }
var index = s_ActiveNames.IndexOf(prop.name);
if (0 <= index)
{
sp.DeleteArrayElementAtIndex(index);
}
else
{
sp.InsertArrayElementAtIndex(sp.arraySize);
var p = sp.GetArrayElementAtIndex(sp.arraySize - 1);
p.FindPropertyRelative("m_Name").stringValue = prop.name;
p.FindPropertyRelative("m_Type").intValue = (int)prop.type;
}
sp.serializedObject.ApplyModifiedProperties(); private static void DrawCustomProperty(SerializedProperty sp, ref ShaderProperty prop)
}); {
var r = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight + 8);
r.xMin += 60;
GUI.Label(r, (Texture)null, EditorStyles.helpBox);
r = new Rect(r.x + 4, r.y + 4, r.width - 8 - 100 - 16, r.height - 8);
prop.name = EditorGUI.TextField(r, prop.name);
r.x += r.width + 2;
r.width = 100 - 2;
prop.type = (ShaderPropertyType)EditorGUI.EnumPopup(r, prop.type);
r.x += r.width;
r.width = 16;
EditorGUI.BeginDisabledGroup(!prop.IsValid(s_ActiveNames));
if (GUI.Button(r, EditorGUIUtility.IconContent("Toolbar Plus"), EditorStyles.label))
{
GUI.FocusControl("");
AddProp(sp, prop);
prop.Reset();
}
EditorGUI.EndDisabledGroup();
}
private static void AddProp(SerializedProperty sp, ShaderProperty prop)
{
var index = s_ActiveNames.IndexOf(prop.name);
if (0 <= index)
{
sp.DeleteArrayElementAtIndex(index);
}
else
{
sp.InsertArrayElementAtIndex(sp.arraySize);
var p = sp.GetArrayElementAtIndex(sp.arraySize - 1);
p.FindPropertyRelative("m_Name").stringValue = prop.name;
p.FindPropertyRelative("m_Type").intValue = (int)prop.type;
}
sp.serializedObject.ApplyModifiedProperties();
} }
private struct ShaderProperty private struct ShaderProperty
{ {
public readonly string name; public string name;
public readonly AnimatableProperty.ShaderPropertyType type; public ShaderPropertyType type;
public ShaderProperty(string name) public ShaderProperty(string name)
{ {
this.name = name; this.name = name;
type = AnimatableProperty.ShaderPropertyType.Vector; type = ShaderPropertyType.Vector;
} }
public ShaderProperty(string name, AnimatableProperty.ShaderPropertyType type) public ShaderProperty(string name, ShaderPropertyType type)
{ {
this.name = name; this.name = name;
this.type = type; this.type = type;
} }
public void Reset()
{
name = "";
type = ShaderPropertyType.None;
}
public bool IsValid(List<string> activeNames)
{
if (string.IsNullOrEmpty(name)) return false;
if (type == ShaderPropertyType.None) return false;
if (activeNames.Contains(name)) return false;
return true;
}
} }
} }
} }

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -8,12 +9,11 @@ using UnityEngine;
using UnityEngine.Profiling; using UnityEngine.Profiling;
using UnityEngine.UI; using UnityEngine.UI;
using Coffee.UIParticleInternal; using Coffee.UIParticleInternal;
using Object = UnityEngine.Object;
#if UNITY_2021_2_OR_NEWER #if UNITY_2021_2_OR_NEWER
using UnityEditor.Overlays; using UnityEditor.Overlays;
#else #else
using System;
using System.Reflection; using System.Reflection;
using Object = UnityEngine.Object;
#endif #endif
#if UNITY_2021_2_OR_NEWER #if UNITY_2021_2_OR_NEWER
using UnityEditor.SceneManagement; using UnityEditor.SceneManagement;
@@ -49,6 +49,7 @@ 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 GUIContent s_ViewSize = new GUIContent("View Size");
private static readonly Regex s_RegexBuiltInGuid = new Regex(@"^0{16}.0{15}$", RegexOptions.Compiled); 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 readonly List<Material> s_TempMaterials = new List<Material>();
@@ -66,6 +67,7 @@ namespace Coffee.UIExtensions
private ReorderableList _ro; private ReorderableList _ro;
private bool _showMax; private bool _showMax;
private bool _is3DScaleMode; private bool _is3DScaleMode;
private GameObject[] _gameObjects;
private static readonly HashSet<Shader> s_Shaders = new HashSet<Shader>(); private static readonly HashSet<Shader> s_Shaders = new HashSet<Shader>();
#if UNITY_2018 || UNITY_2019 #if UNITY_2018 || UNITY_2019
@@ -110,7 +112,7 @@ namespace Coffee.UIExtensions
{ {
var ps = sp.GetArrayElementAtIndex(index).objectReferenceValue as ParticleSystem; var ps = sp.GetArrayElementAtIndex(index).objectReferenceValue as ParticleSystem;
var materialCount = 0; var materialCount = 0;
if (ps && ps.TryGetComponent<ParticleSystemRenderer>(out var psr)) if (ps != null && ps.TryGetComponent<ParticleSystemRenderer>(out var psr))
{ {
materialCount = psr.sharedMaterials.Length; materialCount = psr.sharedMaterials.Length;
} }
@@ -124,7 +126,7 @@ namespace Coffee.UIExtensions
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; var ps = p.objectReferenceValue as ParticleSystem;
if (!ps || !ps.TryGetComponent<ParticleSystemRenderer>(out var psr)) return; if (ps == null || !ps.TryGetComponent<ParticleSystemRenderer>(out var psr)) return;
rect.x += 15; rect.x += 15;
rect.width -= 15; rect.width -= 15;
@@ -183,6 +185,13 @@ namespace Coffee.UIExtensions
y.hasMultipleDifferentValues || y.hasMultipleDifferentValues ||
z.hasMultipleDifferentValues; z.hasMultipleDifferentValues;
} }
// Add temporary ParticleSystem for preview if enabled.
if (UIParticleProjectSettings.previewOnSelect)
{
_gameObjects = targets.OfType<UIParticle>().Select(x => x.gameObject).ToArray();
ParticleSystemPreviewSystem.Register(_gameObjects);
}
} }
/// <summary> /// <summary>
@@ -191,13 +200,16 @@ namespace Coffee.UIExtensions
public override void OnInspectorGUI() public override void OnInspectorGUI()
{ {
var current = target as UIParticle; var current = target as UIParticle;
if (!current) return; if (current == null) return;
Profiler.BeginSample("(UIP:E) OnInspectorGUI"); Profiler.BeginSample("(UIP:E) OnInspectorGUI");
serializedObject.Update(); serializedObject.Update();
// Maskable // Maskable
EditorGUILayout.PropertyField(_maskable); if (_maskable != null)
{
EditorGUILayout.PropertyField(_maskable);
}
// Scale // Scale
EditorGUI.BeginDisabledGroup(!_meshSharing.hasMultipleDifferentValues && _meshSharing.intValue == 4); EditorGUI.BeginDisabledGroup(!_meshSharing.hasMultipleDifferentValues && _meshSharing.intValue == 4);
@@ -235,16 +247,24 @@ namespace Coffee.UIExtensions
// Custom View Size // Custom View Size
EditorGUILayout.PropertyField(_useCustomView); EditorGUILayout.PropertyField(_useCustomView);
EditorGUI.BeginChangeCheck();
EditorGUI.BeginDisabledGroup(!_useCustomView.boolValue);
EditorGUI.indentLevel++; EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_customViewSize); if (_useCustomView.boolValue)
EditorGUI.indentLevel--;
EditorGUI.EndDisabledGroup();
if (EditorGUI.EndChangeCheck())
{ {
_customViewSize.floatValue = Mathf.Max(0.1f, _customViewSize.floatValue); EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(_customViewSize);
if (EditorGUI.EndChangeCheck())
{
_customViewSize.floatValue = Mathf.Max(0.1f, _customViewSize.floatValue);
}
} }
else
{
EditorGUI.BeginDisabledGroup(!_useCustomView.boolValue);
EditorGUILayout.FloatField(s_ViewSize, UIParticleProjectSettings.defaultViewSizeForBaking);
EditorGUI.EndDisabledGroup();
}
EditorGUI.indentLevel--;
// Time Scale Multiplier // Time Scale Multiplier
EditorGUILayout.PropertyField(_timeScaleMultiplier); EditorGUILayout.PropertyField(_timeScaleMultiplier);
@@ -267,7 +287,7 @@ namespace Coffee.UIExtensions
Profiler.BeginSample("(UIP:E) Non-UI built-in shader is not supported."); Profiler.BeginSample("(UIP:E) Non-UI built-in shader is not supported.");
foreach (var mat in s_TempMaterials) foreach (var mat in s_TempMaterials)
{ {
if (!mat || !mat.shader) continue; if (mat == null || mat.shader == null) continue;
var shader = mat.shader; var shader = mat.shader;
if (IsBuiltInObject(shader) && !shader.name.StartsWith("UI/")) if (IsBuiltInObject(shader) && !shader.name.StartsWith("UI/"))
{ {
@@ -286,7 +306,7 @@ namespace Coffee.UIExtensions
{ {
foreach (var mat in s_TempMaterials) foreach (var mat in s_TempMaterials)
{ {
if (!mat || !mat.shader) continue; if (mat == null || mat.shader == null) continue;
var shader = mat.shader; var shader = mat.shader;
if (!s_Shaders.Add(shader)) continue; if (!s_Shaders.Add(shader)) continue;
@@ -344,6 +364,10 @@ namespace Coffee.UIExtensions
} }
} }
#endif #endif
// Remove the temporary ParticleSystem.
ParticleSystemPreviewSystem.DrawWarningForTemporary(_gameObjects);
Profiler.EndSample(); Profiler.EndSample();
} }
@@ -482,7 +506,7 @@ namespace Coffee.UIExtensions
#endif #endif
} }
private static bool FixButton(bool show, string text) private static bool FixButton(bool show, string text, GUIContent buttonText = null)
{ {
if (!show) return false; if (!show) return false;
using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(true))) using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(true)))
@@ -490,7 +514,7 @@ namespace Coffee.UIExtensions
EditorGUILayout.HelpBox(text, MessageType.Warning, true); EditorGUILayout.HelpBox(text, MessageType.Warning, true);
using (new EditorGUILayout.VerticalScope()) using (new EditorGUILayout.VerticalScope())
{ {
return GUILayout.Button(s_ContentFix, GUILayout.Width(30)); return GUILayout.Button(buttonText ?? s_ContentFix, GUILayout.ExpandWidth(false));
} }
} }
} }

View File

@@ -6,11 +6,22 @@ namespace Coffee.UIExtensions
{ {
internal class UIParticleMenu internal class UIParticleMenu
{ {
[MenuItem("GameObject/UI/Particle System (Empty)", false, 2018)] #if UNITY_6000_5_OR_NEWER
private const string k_MenuPathToCreateParticleSystem = "GameObject/Visual Effects/Particle System";
#else
private const string k_MenuPathToCreateParticleSystem = "GameObject/Effects/Particle System";
#endif
#if UNITY_6000_3_OR_NEWER
private const string k_MenuPathForUgui = "GameObject/UI (Canvas)";
#else
private const string k_MenuPathForUgui = "GameObject/UI";
#endif
[MenuItem(k_MenuPathForUgui + "/Particle System (Empty)", false, 2018)]
private static void AddParticleEmpty(MenuCommand menuCommand) private static void AddParticleEmpty(MenuCommand menuCommand)
{ {
// Create empty UI element. // Create empty UI element.
EditorApplication.ExecuteMenuItem("GameObject/UI/Image"); EditorApplication.ExecuteMenuItem(k_MenuPathForUgui + "/Image");
var ui = Selection.activeGameObject; var ui = Selection.activeGameObject;
Object.DestroyImmediate(ui.GetComponent<Image>()); Object.DestroyImmediate(ui.GetComponent<Image>());
@@ -21,7 +32,7 @@ namespace Coffee.UIExtensions
uiParticle.rectTransform.sizeDelta = Vector2.zero; uiParticle.rectTransform.sizeDelta = Vector2.zero;
} }
[MenuItem("GameObject/UI/Particle System", false, 2019)] [MenuItem(k_MenuPathForUgui + "/Particle System", false, 2019)]
private static void AddParticle(MenuCommand menuCommand) private static void AddParticle(MenuCommand menuCommand)
{ {
// Create empty UIEffect. // Create empty UIEffect.
@@ -29,7 +40,7 @@ namespace Coffee.UIExtensions
var uiParticle = Selection.activeGameObject.GetComponent<UIParticle>(); var uiParticle = Selection.activeGameObject.GetComponent<UIParticle>();
// Create ParticleSystem. // Create ParticleSystem.
EditorApplication.ExecuteMenuItem("GameObject/Effects/Particle System"); EditorApplication.ExecuteMenuItem(k_MenuPathToCreateParticleSystem);
var ps = Selection.activeGameObject; var ps = Selection.activeGameObject;
ps.transform.SetParent(uiParticle.transform, false); ps.transform.SetParent(uiParticle.transform, false);
ps.transform.localPosition = Vector3.zero; ps.transform.localPosition = Vector3.zero;

View File

@@ -1,4 +1,4 @@
Copyright 2018-2024 mob-sakai Copyright 2018-2026 mob-sakai
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@@ -34,6 +34,7 @@ You can render, mask, and sort your `ParticleSystems` for UI without the need fo
- [Usage with `Mask` or `RectMask2D` Component](#usage-with-mask-or-rectmask2d-component) - [Usage with `Mask` or `RectMask2D` Component](#usage-with-mask-or-rectmask2d-component)
- [Usage with Script](#usage-with-script) - [Usage with Script](#usage-with-script)
- [Component: UIParticleAttractor](#component-uiparticleattractor) - [Component: UIParticleAttractor](#component-uiparticleattractor)
- [Component: ParticleSystemPreviewer](#component-particlesystempreviewer)
- [Project Settings](#project-settings) - [Project Settings](#project-settings)
- [🛠 Development Note](#-development-note) - [🛠 Development Note](#-development-note)
- [Compares the Baking mesh approach with the conventional approach](#compares-the-baking-mesh-approach-with-the-conventional-approach) - [Compares the Baking mesh approach with the conventional approach](#compares-the-baking-mesh-approach-with-the-conventional-approach)
@@ -116,7 +117,7 @@ _This package requires **Unity 2018.3 or later**._
``` ```
- To update the package, use Package Manager UI (`Window > Package Manager`) or run the following command with `@{version}`: - 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 openupm add com.coffee.ui-particle@4.13.0
``` ```
#### Install via UPM (with Package Manager UI) #### Install via UPM (with Package Manager UI)
@@ -125,7 +126,7 @@ _This package requires **Unity 2018.3 or later**._
- Click `+ > Add package from git URL...` and input the repository URL: `https://github.com/mob-sakai/ParticleEffectForUGUI.git` - 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) ![](https://github.com/user-attachments/assets/f88f47ad-c606-44bd-9e86-ee3f72eac548)
- To update the package, change suffix `#{version}` to the target version. - To update the package, change suffix `#{version}` to the target version.
- e.g. `https://github.com/mob-sakai/ParticleEffectForUGUI.git#4.9.0` - e.g. `https://github.com/mob-sakai/ParticleEffectForUGUI.git#4.13.0`
#### Install via UPM (Manually) #### Install via UPM (Manually)
@@ -140,7 +141,7 @@ _This package requires **Unity 2018.3 or later**._
``` ```
- 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.13.0",`
#### Install as Embedded Package #### Install as Embedded Package
@@ -164,6 +165,12 @@ _This package requires **Unity 2018.3 or later**._
- **Scale**: Scale the rendering particles. 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 as animatable. use this to mark as animatable.
> [!TIPS]
> (Unity 2021.1 or later) **Custom Animatable Properties**
> From the `Add Custom...` menu in `Animatable Properties`, you can add fields located outside the `Properties` block in the shader as `Animatable Properties`.
> This allows `Matrix`, `Matrix Array`, `Float Array`, and `Vector Array` values modified by the `ParticleSystemRenderer.SetPropertyBlock` method to be automatically reflected in UIParticle.
- **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. - **None:** Disable mesh sharing.
@@ -183,9 +190,14 @@ _This package requires **Unity 2018.3 or later**._
- **Time Scale Multiplier:** Time scale multiplier. - **Time Scale Multiplier:** Time scale multiplier.
- **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 > [!TIPS]
> Press the `Refresh` button to reconstruct the rendering order based on children ParticleSystem's sorting order
and z-position. and z-position.
> [!TIPS]
> When a `GameObject` with this component is selected in the editor, a temporary `ParticleSystem` is added if needed so you can preview the effect in the Scene view.
> The generated `ParticleSystem` is marked with `HideFlags.DontSave`, so it is neither saved nor included in builds.
<br><br> <br><br>
### Basic Usage ### Basic Usage
@@ -195,6 +207,10 @@ and z-position.
2. Adjust the ParticleSystem as you like. 2. Adjust the ParticleSystem as you like.
![particle1](https://user-images.githubusercontent.com/12690315/95007359-ca385200-0649-11eb-8383-627c9750bda8.png) ![particle1](https://user-images.githubusercontent.com/12690315/95007359-ca385200-0649-11eb-8383-627c9750bda8.png)
> [!Tips]
> Adding a `UIParticle` to the parent is the recommended setup rather than attaching it directly to the `ParticleSystem`.
> When using `ParticleSystem.emission.rateOverDistance`, it is recommended to move the transform of `UIParticle` rather than the `ParticleSystem`.
<br> <br>
### Usage with Your Existing ParticleSystem Prefab ### Usage with Your Existing ParticleSystem Prefab
@@ -258,11 +274,34 @@ uiParticle.Stop();
<br><br> <br><br>
### Component: ParticleSystemPreviewer
`ParticleSystemPreviewer` is used to preview a `ParticleSystem` in the editor.
![](https://github.com/mob-sakai/mob-sakai/releases/download/docs/1782441157549.png)
- When a `GameObject` with this component is selected in the editor, a temporary `ParticleSystem` is added if needed so you can preview the effect in the Scene view.
- The generated `ParticleSystem` is marked with `HideFlags.DontSave`, so it is neither saved nor included in builds.
- This component will be removed in builds.
<br><br>
### Project Settings ### Project Settings
![](https://github.com/user-attachments/assets/befc7f34-fb47-4006-831a-eba79fda11ca) You can adjust the project-wide settings for `UI Particle`. (`Edit > Project Settings > UI > UI Particle`)
- Click `Edit > Project Settings` to open the Project Settings window and then select `UI > UI Particle` category. ![](https://github.com/mob-sakai/mob-sakai/releases/download/docs/1782360274340.png)
#### Settings
- **Enable Linear To Gamma**: Automatically correct the color space of the mesh.
- **Default View Size For Baking**: Default view size for baking particle systems.
#### Editor
- **Hide Generated Component**: Automatically hide the generated `UIParticleRenderer` component and `UIParticle BakingCamera`.
- **Preview On Select**: When selecting UIParticle, a temporary ParticleSystem is generated for preview.
<br><br> <br><br>
@@ -327,33 +366,39 @@ uiParticle.Stop();
### 🔍 FAQ: Why Are My UIParticles Not Displayed Correctly? ### 🔍 FAQ: Why Are My UIParticles Not Displayed Correctly?
See [this issue](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/270).
If `ParticleSystem` alone displays particles correctly but `UIParticle` does not, please check the following points: If `ParticleSystem` alone displays particles correctly but `UIParticle` does not, please check the following points:
- [Shader Limitation](#shader-limitation) - [Shader Limitation](https://github.com/mob-sakai/ParticleEffectForUGUI#shader-limitation)
- `UIParticle` does not support all built-in shaders except for `UI/Default`.
- Most cases can be solved by using `UI/Additive` or `UI/Default`. - Most cases can be solved by using `UI/Additive` or `UI/Default`.
- Particles are not masked - Particles are not masked
- `UIParticle` is maskable. - `UIParticle` is maskable.
- Use maskable/clipable shader (such as `UI/Additive` or `UI/Default`)
- Set `Mask` or `RectMask2D` component properly. - Set `Mask` or `RectMask2D` component properly.
- [Use maskable/clipable shader](#how-to-make-a-custom-shader-to-support-maskrectmask2d-component) (such
as `UI/Additive` or `UI/Default`)
- Particles are too small - Particles are too small
- If particles are small enough, they will not appear on the screen. - If particles are small enough, they will not appear on the screen.
- Increase the `Scale` value. - Increase the `Scale` value.
- If you don't want to change the apparent size depending on the resolution, try the `Auto Scaling` option. - If you don't want to change the apparent size depending on the resolution, try the `Auto Scaling` option.
- Particles are too many - Particles are too many
- No more than 65535 vertices can be displayed (for mesh combination limitations). - No more than 65535 vertices can be displayed (for mesh combination limitations).
- Please set `Emission` module and `Max Particles` of ParticleSystem properly. - Please set `Emission` module and `Max Particles` of `ParticleSystem` properly.
- Particles are emitted off-screen. - Particles are emitted off-screen.
- When `Position Mode = Relative`, particles are emitted from the scaled position of the ParticleSystem, not from - When `Position Mode = Relative`, particles are emitted from the scaled position of the `ParticleSystem`, not from
the screen point of the ParticleSystem. the screen point of the `ParticleSystem`.
- Place the ParticleSystem in the proper position or try `Position Mode = Absolute`. - Place the `ParticleSystem` in the proper position or try `Position Mode = Absolute`.
- Attaching `UIParticle` to the same object as `ParticleSystem` - Attaching `UIParticle` to the same object as `ParticleSystem`
- `Transform.localScale` will be overridden by the `Auto Scaling` option. - `Transform.localScale` will be overridden by the `Auto Scaling` option.
- It is recommended to place `ParticleSystem` under `UIParticle`. - It is recommended to place `ParticleSystem` under `UIParticle`.
- If `Transform.localScale` contains 0, rendering will be skipped. - If `Transform.localScale` contains 0, rendering will be skipped.
- If you are using `Shape` module, please check whether the scale includes 0.
- Displayed particles are in the correct position but too large/too small - Displayed particles are in the correct position but too large/too small
- Adjust `ParticleSystem.renderer.Min/MaxParticleSize`. - Adjust `ParticleSystem.renderer.Min/MaxParticleSize`.
- If `Trails.RibbonCount` is greater than 1, the following error occurs frequently.
- `Failed setting triangles. Some indices are referencing out of bounds vertices. IndexCount: 660, VertexCount: 222`
- Adding a `UIParticle` to the parent is the recommended setup rather than attaching it directly to the `ParticleSystem`.
- When using `ParticleSystem.emission.rateOverDistance`, it is recommended to move the transform of `UIParticle` rather than the `ParticleSystem`.
- (v4.13.0, v5.0.0-preview.18) Try changing the `Default View Size For Baking` setting under `ProjectSettings > UI > UI Particle` to a value such as 100 or 1000.
<br> <br>

View File

@@ -8,11 +8,18 @@ namespace Coffee.UIExtensions
{ {
public enum ShaderPropertyType public enum ShaderPropertyType
{ {
None = -1,
Color, Color,
Vector, Vector,
Float, Float,
Range, Range,
Texture Texture,
Int,
Matrix = 100,
MatrixArray = 101,
FloatArray = 102,
VectorArray = 103,
} }
[SerializeField] private string m_Name = ""; [SerializeField] private string m_Name = "";
@@ -32,42 +39,41 @@ namespace Coffee.UIExtensions
public void UpdateMaterialProperties(Material material, MaterialPropertyBlock mpb) public void UpdateMaterialProperties(Material material, MaterialPropertyBlock mpb)
{ {
#if UNITY_2021_1_OR_NEWER
if (!mpb.HasProperty(id)) return;
#else
if (!material.HasProperty(id)) return; if (!material.HasProperty(id)) return;
#endif
switch (type) switch (type)
{ {
case ShaderPropertyType.Color: case ShaderPropertyType.Color:
var color = mpb.GetColor(id); material.SetColor(id, mpb.GetColor(id));
if (color != default)
{
material.SetColor(id, color);
}
break; break;
case ShaderPropertyType.Vector: case ShaderPropertyType.Vector:
var vector = mpb.GetVector(id); material.SetVector(id, mpb.GetVector(id));
if (vector != default)
{
material.SetVector(id, vector);
}
break; break;
case ShaderPropertyType.Float: case ShaderPropertyType.Float:
case ShaderPropertyType.Range: case ShaderPropertyType.Range:
var value = mpb.GetFloat(id); material.SetFloat(id, mpb.GetFloat(id));
if (!Mathf.Approximately(value, 0))
{
material.SetFloat(id, value);
}
break; break;
case ShaderPropertyType.Texture: case ShaderPropertyType.Texture:
var tex = mpb.GetTexture(id); material.SetTexture(id, mpb.GetTexture(id));
if (tex != default(Texture)) break;
{ case ShaderPropertyType.Int:
material.SetTexture(id, tex); material.SetInt(id, mpb.GetInt(id));
} break;
case ShaderPropertyType.Matrix:
material.SetMatrix(id, mpb.GetMatrix(id));
break;
case ShaderPropertyType.MatrixArray:
material.SetMatrixArray(id, mpb.GetMatrixArray(id));
break;
case ShaderPropertyType.FloatArray:
material.SetFloatArray(id, mpb.GetFloatArray(id));
break;
case ShaderPropertyType.VectorArray:
material.SetVectorArray(id, mpb.GetVectorArray(id));
break; break;
} }
} }

View File

@@ -98,7 +98,7 @@ namespace Coffee.UIParticleInternal
Profiler.BeginSample("(COF)[CanvasExt] GetViewProjectionMatrix"); Profiler.BeginSample("(COF)[CanvasExt] GetViewProjectionMatrix");
var rootCanvas = canvas.rootCanvas; var rootCanvas = canvas.rootCanvas;
var cam = rootCanvas.worldCamera; var cam = rootCanvas.worldCamera;
if (rootCanvas && rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay && cam) if (rootCanvas != null && rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay && cam != null)
{ {
if (eye == Camera.MonoOrStereoscopicEye.Mono) if (eye == Camera.MonoOrStereoscopicEye.Mono)
{ {

View File

@@ -12,6 +12,33 @@ namespace Coffee.UIParticleInternal
/// </summary> /// </summary>
internal static class ComponentExtensions internal static class ComponentExtensions
{ {
#if !UNITY_2019_2_OR_NEWER
public static bool TryGetComponent<T>(this GameObject self, out T component)
where T : Component
{
if (self == null)
{
component = null;
return false;
}
component = self.GetComponent<T>();
return component != null;
}
public static bool TryGetComponent<T>(this Component self, out T component)
where T : Component
{
if (self == null)
{
component = null;
return false;
}
return self.gameObject.TryGetComponent(out component);
}
#endif
/// <summary> /// <summary>
/// Get components in children of a specific type in the hierarchy of a GameObject. /// Get components in children of a specific type in the hierarchy of a GameObject.
/// </summary> /// </summary>
@@ -38,7 +65,7 @@ namespace Coffee.UIParticleInternal
private static void GetComponentsInChildren_Internal<T>(this Component self, List<T> results, int depth) private static void GetComponentsInChildren_Internal<T>(this Component self, List<T> results, int depth)
where T : Component where T : Component
{ {
if (!self || results == null || depth < 0) return; if (self == null || results == null || depth < 0) return;
var tr = self.transform; var tr = self.transform;
if (tr.TryGetComponent<T>(out var t)) if (tr.TryGetComponent<T>(out var t))
@@ -59,7 +86,7 @@ namespace Coffee.UIParticleInternal
/// </summary> /// </summary>
public static T GetOrAddComponent<T>(this Component self) where T : Component public static T GetOrAddComponent<T>(this Component self) where T : Component
{ {
if (!self) return null; if (self == null) return null;
return self.TryGetComponent<T>(out var component) return self.TryGetComponent<T>(out var component)
? component ? component
: self.gameObject.AddComponent<T>(); : self.gameObject.AddComponent<T>();
@@ -72,7 +99,7 @@ namespace Coffee.UIParticleInternal
{ {
T component = null; T component = null;
var transform = self.transform; var transform = self.transform;
while (transform) while (transform != null)
{ {
if (transform.TryGetComponent<T>(out var c)) if (transform.TryGetComponent<T>(out var c))
{ {
@@ -93,7 +120,7 @@ namespace Coffee.UIParticleInternal
where T : Component where T : Component
{ {
var tr = includeSelf ? self.transform : self.transform.parent; var tr = includeSelf ? self.transform : self.transform.parent;
while (tr) while (tr != null)
{ {
if (tr.TryGetComponent<T>(out var c) && valid(c)) return c; if (tr.TryGetComponent<T>(out var c) && valid(c)) return c;
if (tr == stopAfter) return null; if (tr == stopAfter) return null;
@@ -166,11 +193,11 @@ namespace Coffee.UIParticleInternal
#if !UNITY_2021_2_OR_NEWER && !UNITY_2020_3_45 && !UNITY_2020_3_46 && !UNITY_2020_3_47 && !UNITY_2020_3_48 #if !UNITY_2021_2_OR_NEWER && !UNITY_2020_3_45 && !UNITY_2020_3_46 && !UNITY_2020_3_47 && !UNITY_2020_3_48
public static T GetComponentInParent<T>(this Component self, bool includeInactive) where T : Component public static T GetComponentInParent<T>(this Component self, bool includeInactive) where T : Component
{ {
if (!self) return null; if (self == null) return null;
if (!includeInactive) return self.GetComponentInParent<T>(); if (!includeInactive) return self.GetComponentInParent<T>();
var current = self.transform; var current = self.transform;
while (current) while (current != null)
{ {
if (current.TryGetComponent<T>(out var c)) return c; if (current.TryGetComponent<T>(out var c)) return c;
current = current.parent; current = current.parent;
@@ -186,7 +213,7 @@ namespace Coffee.UIParticleInternal
/// </summary> /// </summary>
internal static bool CanConvertTo<T>(this Object context) where T : MonoBehaviour internal static bool CanConvertTo<T>(this Object context) where T : MonoBehaviour
{ {
return context && context.GetType() != typeof(T); return context != null && context.GetType() != typeof(T);
} }
/// <summary> /// <summary>

View File

@@ -0,0 +1,153 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace Coffee.UIParticleInternal
{
internal static class MeshExtensions
{
internal static readonly InternalObjectPool<Mesh> s_MeshPool = new InternalObjectPool<Mesh>(
() =>
{
var mesh = new Mesh
{
hideFlags = HideFlags.DontSave | HideFlags.NotEditable
};
mesh.MarkDynamic();
return mesh;
},
mesh => mesh,
mesh =>
{
if (mesh != null)
{
mesh.Clear();
}
});
public static Mesh Rent()
{
return s_MeshPool.Rent();
}
public static void Return(ref Mesh mesh)
{
s_MeshPool.Return(ref mesh);
}
public static void CopyTo(this Mesh self, Mesh dst)
{
if (self == null || dst == null) return;
var vector3List = InternalListPool<Vector3>.Rent();
var vector4List = InternalListPool<Vector4>.Rent();
var color32List = InternalListPool<Color32>.Rent();
var intList = InternalListPool<int>.Rent();
dst.Clear(false);
self.GetVertices(vector3List);
dst.SetVertices(vector3List);
self.GetTriangles(intList, 0);
dst.SetTriangles(intList, 0);
self.GetNormals(vector3List);
dst.SetNormals(vector3List);
self.GetTangents(vector4List);
dst.SetTangents(vector4List);
self.GetColors(color32List);
dst.SetColors(color32List);
self.GetUVs(0, vector4List);
dst.SetUVs(0, vector4List);
self.GetUVs(1, vector4List);
dst.SetUVs(1, vector4List);
self.GetUVs(2, vector4List);
dst.SetUVs(2, vector4List);
self.GetUVs(3, vector4List);
dst.SetUVs(3, vector4List);
dst.RecalculateBounds();
InternalListPool<Vector3>.Return(ref vector3List);
InternalListPool<Vector4>.Return(ref vector4List);
InternalListPool<Color32>.Return(ref color32List);
InternalListPool<int>.Return(ref intList);
}
public static void CopyTo(this VertexHelper self, Mesh dst)
{
if (self == null || dst == null) return;
self.FillMesh(dst);
}
public static void CopyTo(this Mesh self, VertexHelper dst)
{
if (self == null || dst == null) return;
var vertexCount = self.vertexCount;
var indexCount = self.triangles.Length;
self.CopyTo(dst, vertexCount, indexCount);
}
public static void CopyTo(this Mesh self, VertexHelper dst, int vertexCount, int indexCount)
{
if (self == null || dst == null) return;
var positions = InternalListPool<Vector3>.Rent();
var normals = InternalListPool<Vector3>.Rent();
var uv0 = InternalListPool<Vector4>.Rent();
var uv1 = InternalListPool<Vector4>.Rent();
var uv2 = InternalListPool<Vector4>.Rent();
var uv3 = InternalListPool<Vector4>.Rent();
var tangents = InternalListPool<Vector4>.Rent();
var colors = InternalListPool<Color32>.Rent();
var indices = InternalListPool<int>.Rent();
self.GetVertices(positions);
self.GetColors(colors);
self.GetUVs(0, uv0);
self.GetUVs(1, uv1);
self.GetUVs(2, uv1);
self.GetUVs(3, uv1);
self.GetNormals(normals);
self.GetTangents(tangents);
self.GetIndices(indices, 0);
dst.Clear();
for (var i = 0; i < vertexCount; i++)
{
dst.AddVert(positions.GetOrDefault(i), colors.GetOrDefault(i), uv0.GetOrDefault(i), uv1.GetOrDefault(i),
uv2.GetOrDefault(i), uv3.GetOrDefault(i), normals.GetOrDefault(i), tangents.GetOrDefault(i));
}
var count = Mathf.Clamp(indexCount, 0, indices.Count);
for (var i = 0; i < count - 2; i += 3)
{
dst.AddTriangle(indices[i], indices[i + 1], indices[i + 2]);
}
InternalListPool<Vector3>.Return(ref positions);
InternalListPool<Vector3>.Return(ref normals);
InternalListPool<Vector4>.Return(ref uv0);
InternalListPool<Vector4>.Return(ref uv1);
InternalListPool<Vector4>.Return(ref uv2);
InternalListPool<Vector4>.Return(ref uv3);
InternalListPool<Vector4>.Return(ref tangents);
InternalListPool<Color32>.Return(ref colors);
InternalListPool<int>.Return(ref indices);
}
private static T GetOrDefault<T>(this List<T> self, int index)
{
return 0 <= index && index < self.Count
? self[index]
: default;
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 8255313895da84e7cbdc876be3795334 guid: 8c949d47f5c6d4d22bd7baebc60d48f3
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -32,10 +32,10 @@ namespace Coffee.UIParticleInternal
/// </summary> /// </summary>
public static Texture2D GetActualTexture(this Sprite self) public static Texture2D GetActualTexture(this Sprite self)
{ {
if (!self) return null; if (self == null) return null;
var ret = s_GetActiveAtlasTextureMethod(self); var ret = s_GetActiveAtlasTextureMethod(self);
return ret ? ret : self.texture; return ret != null ? ret : self.texture;
} }
/// <summary> /// <summary>
@@ -43,7 +43,7 @@ namespace Coffee.UIParticleInternal
/// </summary> /// </summary>
public static SpriteAtlas GetActiveAtlas(this Sprite self) public static SpriteAtlas GetActiveAtlas(this Sprite self)
{ {
if (!self) return null; if (self == null) return null;
return s_GetActiveAtlasMethod(self); return s_GetActiveAtlasMethod(self);
} }
@@ -53,7 +53,7 @@ namespace Coffee.UIParticleInternal
/// </summary> /// </summary>
internal static Texture2D GetActualTexture(this Sprite self) internal static Texture2D GetActualTexture(this Sprite self)
{ {
return self ? self.texture : null; return self != null ? self.texture : null;
} }
#endif #endif
} }

View File

@@ -14,22 +14,66 @@ namespace Coffee.UIParticleInternal
public abstract class PreloadedProjectSettings : ScriptableObject public abstract class PreloadedProjectSettings : ScriptableObject
#if UNITY_EDITOR #if UNITY_EDITOR
{ {
private class Postprocessor : AssetPostprocessor [Tooltip("When enabled, this settings asset will be added to PlayerSettings.preloadedAssets in build.\n\n" +
"When disable, you should load this settings via Resources, AssetBundles or Addressables to use.")]
[SerializeField]
[Header("Advanced")]
[HideInInspector]
private bool m_PreLoadSettingsInBuild = true;
protected static bool s_BuildingPlayer;
private class EditorEvents : AssetPostprocessor, IPreprocessBuildWithReport, IPostprocessBuildWithReport
{ {
int IOrderedCallback.callbackOrder => 0;
private static void OnPostprocessAllAssets(string[] _, string[] __, string[] ___, string[] ____) private static void OnPostprocessAllAssets(string[] _, string[] __, string[] ___, string[] ____)
{ {
Initialize(); Initialize();
} }
}
private class PreprocessBuildWithReport : IPreprocessBuildWithReport
{
int IOrderedCallback.callbackOrder => 0;
void IPreprocessBuildWithReport.OnPreprocessBuild(BuildReport report) void IPreprocessBuildWithReport.OnPreprocessBuild(BuildReport report)
{ {
AssetDatabase.Refresh();
Initialize();
s_BuildingPlayer = true;
foreach (var t in TypeCache.GetTypesDerivedFrom(typeof(PreloadedProjectSettings<>)))
{
var settings = GetDefaultSettings(t);
if (settings == null || settings.m_PreLoadSettingsInBuild) continue;
PlayerSettings.SetPreloadedAssets(
PlayerSettings.GetPreloadedAssets()
.Where(x => x != null && x.GetType() != t)
.ToArray());
Debug.Log($"[PreloadedProjectSettings] Build started: removed '{settings.name}' " +
$"({t.Name}) from PreloadedAssets. " +
$"It will be restored after build completes.");
}
}
void IPostprocessBuildWithReport.OnPostprocessBuild(BuildReport report)
{
s_BuildingPlayer = false;
Initialize(); Initialize();
} }
#if UNITY_2019_3_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void OnDomainReload()
{
foreach (var t in TypeCache.GetTypesDerivedFrom(typeof(PreloadedProjectSettings<>)))
{
var defaultSettings = GetDefaultSettings(t);
if (defaultSettings != null)
{
defaultSettings.OnDomainReload();
}
}
}
#endif
} }
private static void Initialize() private static void Initialize()
@@ -37,18 +81,21 @@ namespace Coffee.UIParticleInternal
foreach (var t in TypeCache.GetTypesDerivedFrom(typeof(PreloadedProjectSettings<>))) foreach (var t in TypeCache.GetTypesDerivedFrom(typeof(PreloadedProjectSettings<>)))
{ {
var defaultSettings = GetDefaultSettings(t); var defaultSettings = GetDefaultSettings(t);
if (!defaultSettings) if (defaultSettings == null)
{ {
// When create a new instance, automatically set it as default settings. if (!s_BuildingPlayer)
defaultSettings = CreateInstance(t) as PreloadedProjectSettings; {
SetDefaultSettings(defaultSettings); // When create a new instance, automatically set it as default settings.
defaultSettings = CreateInstance(t) as PreloadedProjectSettings;
SetDefaultSettings(defaultSettings);
}
} }
else if (GetPreloadedSettings(t).Length != 1) else if (GetPreloadedSettings(t).Length != 1)
{ {
SetDefaultSettings(defaultSettings); if (!s_BuildingPlayer) SetDefaultSettings(defaultSettings);
} }
if (defaultSettings) if (defaultSettings != null)
{ {
defaultSettings.OnInitialize(); defaultSettings.OnInitialize();
} }
@@ -66,22 +113,22 @@ namespace Coffee.UIParticleInternal
private static Object[] GetPreloadedSettings(Type type) private static Object[] GetPreloadedSettings(Type type)
{ {
return PlayerSettings.GetPreloadedAssets() return PlayerSettings.GetPreloadedAssets()
.Where(x => x && x.GetType() == type) .Where(x => x != null && x.GetType() == type)
.ToArray(); .ToArray();
} }
protected static PreloadedProjectSettings GetDefaultSettings(Type type) protected static PreloadedProjectSettings GetDefaultSettings(Type type)
{ {
return GetPreloadedSettings(type).FirstOrDefault() as PreloadedProjectSettings return GetPreloadedSettings(type).FirstOrDefault() as PreloadedProjectSettings
?? AssetDatabase.FindAssets($"t:{nameof(PreloadedProjectSettings)}") ?? AssetDatabase.FindAssets($"t:{type.Name}")
.Select(AssetDatabase.GUIDToAssetPath) .Select(AssetDatabase.GUIDToAssetPath)
.Select(AssetDatabase.LoadAssetAtPath<PreloadedProjectSettings>) .Select(AssetDatabase.LoadAssetAtPath<PreloadedProjectSettings>)
.FirstOrDefault(x => x && x.GetType() == type); .FirstOrDefault(x => x != null && x.GetType() == type);
} }
protected static void SetDefaultSettings(PreloadedProjectSettings asset) protected static void SetDefaultSettings(PreloadedProjectSettings asset)
{ {
if (!asset) return; if (asset == null) return;
var type = asset.GetType(); var type = asset.GetType();
if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(asset))) if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(asset)))
@@ -103,7 +150,7 @@ namespace Coffee.UIParticleInternal
var preloadedAssets = PlayerSettings.GetPreloadedAssets(); var preloadedAssets = PlayerSettings.GetPreloadedAssets();
var projectSettings = GetPreloadedSettings(type); var projectSettings = GetPreloadedSettings(type);
PlayerSettings.SetPreloadedAssets(preloadedAssets PlayerSettings.SetPreloadedAssets(preloadedAssets
.Where(x => x) .Where(x => x != null)
.Except(projectSettings.Except(new[] { asset })) .Except(projectSettings.Except(new[] { asset }))
.Append(asset) .Append(asset)
.Distinct() .Distinct()
@@ -119,6 +166,39 @@ namespace Coffee.UIParticleInternal
protected virtual void OnInitialize() protected virtual void OnInitialize()
{ {
} }
protected virtual void OnDomainReload()
{
}
}
internal abstract class PreloadedProjectSettingsEditor : Editor
{
private SerializedProperty _preLoadSettingsInBuild;
protected virtual void OnEnable()
{
_preLoadSettingsInBuild = serializedObject.FindProperty("m_PreLoadSettingsInBuild");
}
protected void DrawPreLoadSettingsInBuild(string packageName)
{
EditorGUILayout.PropertyField(_preLoadSettingsInBuild);
if (!_preLoadSettingsInBuild.boolValue)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.HelpBox(
$"{target.GetType().Name} asset will not be built in.\n" +
$"please load manually from Resources, AssetBundle, or Addressables before using {packageName}.",
MessageType.Warning);
if (GUILayout.Button("Ping"))
{
EditorGUIUtility.PingObject(target);
}
EditorGUILayout.EndHorizontal();
}
}
} }
#else #else
{ {
@@ -133,31 +213,33 @@ namespace Coffee.UIParticleInternal
#if UNITY_EDITOR #if UNITY_EDITOR
private string _jsonText; private string _jsonText;
public static bool hasInstance => s_Instance; public static bool hasInstance => s_Instance != null;
public static T instance public static T instance
{ {
get get
{ {
if (s_Instance) return s_Instance; if (s_Instance != null) return s_Instance;
s_Instance = GetDefaultSettings(typeof(T)) as T; s_Instance = GetDefaultSettings(typeof(T)) as T;
if (s_Instance) return s_Instance; if (s_Instance != null) return s_Instance;
s_Instance = CreateInstance<T>(); s_Instance = CreateInstance<T>();
if (!s_Instance) if (s_Instance == null)
{ {
s_Instance = null; s_Instance = null;
return s_Instance; return s_Instance;
} }
SetDefaultSettings(s_Instance); if (!s_BuildingPlayer) SetDefaultSettings(s_Instance);
return s_Instance; return s_Instance;
} }
} }
private void OnPlayModeStateChanged(PlayModeStateChange state) private void OnPlayModeStateChanged(PlayModeStateChange state)
{ {
if (this == null) return;
switch (state) switch (state)
{ {
case PlayModeStateChange.ExitingEditMode: case PlayModeStateChange.ExitingEditMode:
@@ -173,8 +255,13 @@ namespace Coffee.UIParticleInternal
break; break;
} }
} }
protected override void OnDomainReload()
{
s_Instance = null;
}
#else #else
public static T instance => s_Instance ? s_Instance : s_Instance = CreateInstance<T>(); public static T instance => s_Instance != null ? s_Instance : s_Instance = CreateInstance<T>();
#endif #endif
/// <summary> /// <summary>
@@ -183,17 +270,21 @@ namespace Coffee.UIParticleInternal
protected virtual void OnEnable() protected virtual void OnEnable()
{ {
#if UNITY_EDITOR #if UNITY_EDITOR
var isDefaultSettings = !s_Instance || s_Instance == this || GetDefaultSettings(typeof(T)) == this; var isDefaultSettings = s_Instance == null || s_Instance == this || GetDefaultSettings(typeof(T)) == this;
if (!isDefaultSettings) if (!isDefaultSettings)
{ {
DestroyImmediate(this, true); DestroyImmediate(this, true);
return; return;
} }
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged; EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
#else
if (s_Instance != null && s_Instance != this)
{
Destroy(s_Instance);
}
#endif #endif
if (s_Instance) return;
s_Instance = this as T; s_Instance = this as T;
} }
@@ -222,9 +313,9 @@ namespace Coffee.UIParticleInternal
public override void OnGUI(string searchContext) public override void OnGUI(string searchContext)
{ {
if (!_target) if (_target == null)
{ {
if (_editor) if (_editor != null)
{ {
DestroyImmediate(_editor); DestroyImmediate(_editor);
_editor = null; _editor = null;

View File

@@ -66,11 +66,6 @@ namespace Coffee.UIParticleInternal
node = node.Next; node = node.Next;
} }
} }
public void Clear()
{
_delegates.Clear();
}
} }
/// <summary> /// <summary>

View File

@@ -8,19 +8,19 @@ namespace Coffee.UIParticleInternal
{ {
private static readonly Dictionary<Type, IFrameCache> s_Caches = new Dictionary<Type, IFrameCache>(); private static readonly Dictionary<Type, IFrameCache> s_Caches = new Dictionary<Type, IFrameCache>();
static FrameCache() #if UNITY_EDITOR && UNITY_2019_3_OR_NEWER
{
s_Caches.Clear();
UIExtraCallbacks.onLateAfterCanvasRebuild += ClearAllCache;
}
#if UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
#elif UNITY_EDITOR
[InitializeOnLoadMethod]
#else
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
#endif
private static void Clear() private static void Clear()
{ {
s_Caches.Clear(); s_Caches.Clear();
UIExtraCallbacks.onLateAfterCanvasRebuild -= ClearAllCache;
UIExtraCallbacks.onLateAfterCanvasRebuild += ClearAllCache;
} }
#endif
/// <summary> /// <summary>
/// Tries to retrieve a value from the frame cache with a specified key. /// Tries to retrieve a value from the frame cache with a specified key.

View File

@@ -11,7 +11,7 @@ using Conditional = System.Diagnostics.ConditionalAttribute;
namespace Coffee.UIParticleInternal namespace Coffee.UIParticleInternal
{ {
internal static class Logging internal static class Logger
{ {
#if !ENABLE_COFFEE_LOGGER #if !ENABLE_COFFEE_LOGGER
private const string k_DisableSymbol = "DISABLE_COFFEE_LOGGER"; private const string k_DisableSymbol = "DISABLE_COFFEE_LOGGER";
@@ -48,7 +48,7 @@ namespace Coffee.UIParticleInternal
public static void LogIf(bool enable, object tag, object message, Object context = null) public static void LogIf(bool enable, object tag, object message, Object context = null)
{ {
if (!enable) return; if (!enable) return;
Log_Internal(LogType.Log, tag, message, context ? context : tag as Object); Log_Internal(LogType.Log, tag, message, context != null ? context : tag as Object);
} }
#if !ENABLE_COFFEE_LOGGER #if !ENABLE_COFFEE_LOGGER
@@ -56,7 +56,7 @@ namespace Coffee.UIParticleInternal
#endif #endif
public static void Log(object tag, object message, Object context = null) public static void Log(object tag, object message, Object context = null)
{ {
Log_Internal(LogType.Log, tag, message, context ? context : tag as Object); Log_Internal(LogType.Log, tag, message, context != null ? context : tag as Object);
} }
#if !ENABLE_COFFEE_LOGGER #if !ENABLE_COFFEE_LOGGER
@@ -64,13 +64,13 @@ namespace Coffee.UIParticleInternal
#endif #endif
public static void LogWarning(object tag, object message, Object context = null) public static void LogWarning(object tag, object message, Object context = null)
{ {
Log_Internal(LogType.Warning, tag, message, context ? context : tag as Object); Log_Internal(LogType.Warning, tag, message, context != null ? context : tag as Object);
} }
public static void LogError(object tag, object message, Object context = null) public static void LogError(object tag, object message, Object context = null)
{ {
#if ENABLE_COFFEE_LOGGER #if ENABLE_COFFEE_LOGGER
Log_Internal(LogType.Error, tag, message, context ? context : tag as Object); Log_Internal(LogType.Error, tag, message, context != null ? context : tag as Object);
#else #else
Debug.LogError($"{tag}: {message}", context); Debug.LogError($"{tag}: {message}", context);
#endif #endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4f9f22bb079324476b1473030ad9fec3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -13,7 +13,9 @@ namespace Coffee.UIParticleInternal
public static int count => s_Repository.count; public static int count => s_Repository.count;
#if UNITY_EDITOR public static Func<string, Shader> onShaderFind = Shader.Find;
#if UNITY_EDITOR && UNITY_2019_3_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
public static void Clear() public static void Clear()
{ {
@@ -48,7 +50,7 @@ namespace Coffee.UIParticleInternal
public static void Get(Hash128 hash, ref Material material, string shaderName) public static void Get(Hash128 hash, ref Material material, string shaderName)
{ {
Profiler.BeginSample("(COF)[MaterialRepository] Get"); Profiler.BeginSample("(COF)[MaterialRepository] Get");
s_Repository.Get(hash, ref material, x => new Material(Shader.Find(x)) s_Repository.Get(hash, ref material, x => new Material(onShaderFind(x))
{ {
hideFlags = HideFlags.DontSave | HideFlags.NotEditable hideFlags = HideFlags.DontSave | HideFlags.NotEditable
}, shaderName); }, shaderName);
@@ -61,7 +63,7 @@ namespace Coffee.UIParticleInternal
public static void Get(Hash128 hash, ref Material material, string shaderName, string[] keywords) public static void Get(Hash128 hash, ref Material material, string shaderName, string[] keywords)
{ {
Profiler.BeginSample("(COF)[MaterialRepository] Get"); Profiler.BeginSample("(COF)[MaterialRepository] Get");
s_Repository.Get(hash, ref material, x => new Material(Shader.Find(x.shaderName)) s_Repository.Get(hash, ref material, x => new Material(onShaderFind(x.shaderName))
{ {
hideFlags = HideFlags.DontSave | HideFlags.NotEditable, hideFlags = HideFlags.DontSave | HideFlags.NotEditable,
shaderKeywords = x.keywords shaderKeywords = x.keywords

View File

@@ -20,7 +20,9 @@ namespace Coffee.UIParticleInternal
{ {
public static T[] FindObjectsOfType<T>() where T : Object public static T[] FindObjectsOfType<T>() where T : Object
{ {
#if UNITY_2023_1_OR_NEWER #if UNITY_6000_4_OR_NEWER
return Object.FindObjectsByType<T>(FindObjectsInactive.Include);
#elif UNITY_2023_1_OR_NEWER
return Object.FindObjectsByType<T>(FindObjectsInactive.Include, FindObjectsSortMode.None); return Object.FindObjectsByType<T>(FindObjectsInactive.Include, FindObjectsSortMode.None);
#else #else
return Object.FindObjectsOfType<T>(); return Object.FindObjectsOfType<T>();
@@ -29,7 +31,7 @@ namespace Coffee.UIParticleInternal
public static void Destroy(Object obj) public static void Destroy(Object obj)
{ {
if (!obj) return; if (obj == null) return;
#if UNITY_EDITOR #if UNITY_EDITOR
if (!Application.isPlaying) if (!Application.isPlaying)
{ {
@@ -44,24 +46,19 @@ namespace Coffee.UIParticleInternal
public static void DestroyImmediate(Object obj) public static void DestroyImmediate(Object obj)
{ {
if (!obj) return; if (obj == null) return;
#if UNITY_EDITOR #if UNITY_EDITOR
if (Application.isEditor) Object.DestroyImmediate(obj, true);
{ #else
Object.DestroyImmediate(obj); Object.Destroy(obj);
}
else
#endif #endif
{
Object.Destroy(obj);
}
} }
[Conditional("UNITY_EDITOR")] [Conditional("UNITY_EDITOR")]
public static void SetDirty(Object obj) public static void SetDirty(Object obj)
{ {
#if UNITY_EDITOR #if UNITY_EDITOR
if (!obj) return; if (obj == null) return;
EditorUtility.SetDirty(obj); EditorUtility.SetDirty(obj);
#endif #endif
} }
@@ -112,16 +109,16 @@ namespace Coffee.UIParticleInternal
{ {
if (Misc.isBatchOrBuilding) return; if (Misc.isBatchOrBuilding) return;
var types = TypeCache.GetTypesWithAttribute<IconAttribute>(); var types = TypeCache.GetTypesWithAttribute(typeof(IconAttribute));
var scripts = MonoImporter.GetAllRuntimeMonoScripts(); var scripts = MonoImporter.GetAllRuntimeMonoScripts();
foreach (var type in types) foreach (var type in types)
{ {
var script = scripts.FirstOrDefault(x => x.GetClass() == type); var script = scripts.FirstOrDefault(x => x.GetClass() == type);
if (!script) continue; if (script == null) continue;
var path = type.GetCustomAttribute<IconAttribute>()?._path; var path = type.GetCustomAttribute<IconAttribute>()?._path;
var icon = AssetDatabase.LoadAssetAtPath<Texture2D>(path); var icon = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
if (!icon) continue; if (icon == null) continue;
s_SetIconForObject(script, icon); s_SetIconForObject(script, icon);
} }

View File

@@ -34,7 +34,7 @@ namespace Coffee.UIParticleInternal
} }
// If there are no instances in the pool, create a new one. // If there are no instances in the pool, create a new one.
Logging.Log(this, $"A new instance is created (pooled: {_pool.CountInactive}, created: {_pool.CountAll})."); Logger.Log(this, $"A new instance is created (pooled: {_pool.CountInactive}, created: {_pool.CountAll}).");
return _pool.Get(); return _pool.Get();
} }
@@ -47,7 +47,7 @@ namespace Coffee.UIParticleInternal
if (instance == null) return; // Ignore if already pooled or null. if (instance == null) return; // Ignore if already pooled or null.
_pool.Release(instance); _pool.Release(instance);
Logging.Log(this, $"An instance is released (pooled: {_pool.CountInactive}, created: {_pool.CountAll})."); Logger.Log(this, $"An instance is released (pooled: {_pool.CountInactive}, created: {_pool.CountAll}).");
instance = default; // Set the reference to null. instance = default; // Set the reference to null.
} }
#else #else
@@ -80,7 +80,7 @@ namespace Coffee.UIParticleInternal
} }
// If there are no instances in the pool, create a new one. // If there are no instances in the pool, create a new one.
Logging.Log(this, $"A new instance is created (pooled: {_pool.Count}, created: {++_count})."); Logger.Log(this, $"A new instance is created (pooled: {_pool.Count}, created: {++_count}).");
return _onCreate(); return _onCreate();
} }
@@ -94,7 +94,7 @@ namespace Coffee.UIParticleInternal
_onReturn(instance); // Return the instance to the pool. _onReturn(instance); // Return the instance to the pool.
_pool.Push(instance); _pool.Push(instance);
Logging.Log(this, $"An instance is released (pooled: {_pool.Count}, created: {_count})."); Logger.Log(this, $"An instance is released (pooled: {_pool.Count}, created: {_count}).");
instance = default; // Set the reference to null. instance = default; // Set the reference to null.
} }
#endif #endif

View File

@@ -90,7 +90,7 @@ namespace Coffee.UIParticleInternal
Profiler.BeginSample("(COF)[ObjectRepository] GetFromCache"); Profiler.BeginSample("(COF)[ObjectRepository] GetFromCache");
if (_cache.TryGetValue(hash, out var entry)) if (_cache.TryGetValue(hash, out var entry))
{ {
if (!entry.storedObject) if (entry.storedObject == null)
{ {
Release(ref entry.storedObject); Release(ref entry.storedObject);
Profiler.EndSample(); Profiler.EndSample();
@@ -103,7 +103,7 @@ namespace Coffee.UIParticleInternal
Release(ref obj); Release(ref obj);
++entry.reference; ++entry.reference;
obj = entry.storedObject; obj = entry.storedObject;
Logging.Log(_name, $"Get(total#{count}): {entry}"); Logger.Log(_name, $"Get(total#{count}): {entry}");
} }
Profiler.EndSample(); Profiler.EndSample();
@@ -116,7 +116,7 @@ namespace Coffee.UIParticleInternal
private void Add(Hash128 hash, ref T obj, T newObject) private void Add(Hash128 hash, ref T obj, T newObject)
{ {
if (!newObject) if (newObject == null)
{ {
Release(ref obj); Release(ref obj);
obj = newObject; obj = newObject;
@@ -130,8 +130,8 @@ namespace Coffee.UIParticleInternal
newEntry.hash = hash; newEntry.hash = hash;
newEntry.reference = 1; newEntry.reference = 1;
_cache[hash] = newEntry; _cache[hash] = newEntry;
_objectKey[newObject.GetInstanceID()] = hash; _objectKey[newObject.GetHashCode()] = hash;
Logging.Log(_name, $"<color=#03c700>Add</color>(total#{count}): {newEntry}"); Logger.Log(_name, $"<color=#03c700>Add</color>(total#{count}): {newEntry}");
Release(ref obj); Release(ref obj);
obj = newObject; obj = newObject;
Profiler.EndSample(); Profiler.EndSample();
@@ -146,23 +146,23 @@ namespace Coffee.UIParticleInternal
// Find and release the entry. // Find and release the entry.
Profiler.BeginSample("(COF)[ObjectRepository] Release"); Profiler.BeginSample("(COF)[ObjectRepository] Release");
var id = obj.GetInstanceID(); var id = obj.GetHashCode();
if (_objectKey.TryGetValue(id, out var hash) if (_objectKey.TryGetValue(id, out var hash)
&& _cache.TryGetValue(hash, out var entry)) && _cache.TryGetValue(hash, out var entry))
{ {
entry.reference--; entry.reference--;
if (entry.reference <= 0 || !entry.storedObject) if (entry.reference <= 0 || entry.storedObject == null)
{ {
Remove(entry); Remove(entry);
} }
else else
{ {
Logging.Log(_name, $"Release(total#{_cache.Count}): {entry}"); Logger.Log(_name, $"Release(total#{_cache.Count}): {entry}");
} }
} }
else else
{ {
Logging.Log(_name, $"Release(total#{_cache.Count}): <color=red>Already released: {obj}</color>"); Logger.Log(_name, $"Release(total#{_cache.Count}): <color=red>Already released: {obj}</color>");
} }
obj = null; obj = null;
@@ -175,10 +175,10 @@ namespace Coffee.UIParticleInternal
Profiler.BeginSample("(COF)[ObjectRepository] Remove"); Profiler.BeginSample("(COF)[ObjectRepository] Remove");
_cache.Remove(entry.hash); _cache.Remove(entry.hash);
_objectKey.Remove(entry.storedObject.GetInstanceID()); _objectKey.Remove(entry.storedObject.GetHashCode());
_pool.Push(entry); _pool.Push(entry);
entry.reference = 0; entry.reference = 0;
Logging.Log(_name, $"<color=#f29e03>Remove</color>(total#{_cache.Count}): {entry}"); Logger.Log(_name, $"<color=#f29e03>Remove</color>(total#{_cache.Count}): {entry}");
entry.Release(_onRelease); entry.Release(_onRelease);
Profiler.EndSample(); Profiler.EndSample();
} }
@@ -192,7 +192,7 @@ namespace Coffee.UIParticleInternal
public void Release(Action<T> onRelease) public void Release(Action<T> onRelease)
{ {
reference = 0; reference = 0;
if (storedObject) if (storedObject != null)
{ {
onRelease?.Invoke(storedObject); onRelease?.Invoke(storedObject);
} }

View File

@@ -0,0 +1,88 @@
#if !UNITY_2019_2_OR_NEWER
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Coffee.UIParticleInternal
{
public static class TypeCache
{
private static readonly object s_Lock = new object();
private static readonly Dictionary<Type, Type[]> s_DerivedTypesCache = new Dictionary<Type, Type[]>();
private static readonly Dictionary<Type, Type[]> s_AttributeTypesCache = new Dictionary<Type, Type[]>();
public static IEnumerable<Type> GetTypesDerivedFrom(Type baseType)
{
lock (s_Lock)
{
if (s_DerivedTypesCache.TryGetValue(baseType, out var cached))
{
return cached;
}
var types = new List<Type>();
foreach (var t in GetAllLoadableTypes())
{
if (t != baseType && baseType.IsAssignableFrom(t))
{
types.Add(t);
}
}
return s_DerivedTypesCache[baseType] = types.ToArray();
}
}
public static IEnumerable<Type> GetTypesWithAttribute(Type attr)
{
lock (s_Lock)
{
if (s_AttributeTypesCache.TryGetValue(attr, out var cached))
{
return cached;
}
var types = new List<Type>();
foreach (var t in GetAllLoadableTypes())
{
if (t.GetCustomAttributes(attr, inherit: true).Length > 0)
{
types.Add(t);
}
}
return s_AttributeTypesCache[attr] = types.ToArray();
}
}
private static IEnumerable<Type> GetAllLoadableTypes()
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
Type[] types;
try
{
types = assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
types = ex.Types;
}
if (types == null)
{
continue;
}
foreach (var t in types)
{
if (t != null)
{
yield return t;
}
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bc1207b657ed74ec19e459664d06925f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Reflection;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
@@ -20,7 +21,7 @@ namespace Coffee.UIParticleInternal
static UIExtraCallbacks() static UIExtraCallbacks()
{ {
Canvas.willRenderCanvases += OnBeforeCanvasRebuild; Canvas.willRenderCanvases += OnBeforeCanvasRebuild;
Logging.LogMulticast(typeof(Canvas), "willRenderCanvases", message: "ctor"); Logger.LogMulticast(typeof(Canvas), "willRenderCanvases", message: "ctor");
} }
/// <summary> /// <summary>
@@ -67,20 +68,32 @@ namespace Coffee.UIParticleInternal
if (s_IsInitializedAfterCanvasRebuild) return; if (s_IsInitializedAfterCanvasRebuild) return;
s_IsInitializedAfterCanvasRebuild = true; s_IsInitializedAfterCanvasRebuild = true;
// Explicitly set `Canvas.willRenderCanvases += CanvasUpdateRegistry.PerformUpdate`.
CanvasUpdateRegistry.IsRebuildingLayout(); CanvasUpdateRegistry.IsRebuildingLayout();
#if TMP_ENABLE
// Explicitly set `Canvas.willRenderCanvases += TMP_UpdateManager.DoRebuilds`.
typeof(TMPro.TMP_UpdateManager)
.GetProperty("instance", BindingFlags.NonPublic | BindingFlags.Static)
.GetValue(null);
#endif
Canvas.willRenderCanvases -= OnAfterCanvasRebuild;
Canvas.willRenderCanvases += OnAfterCanvasRebuild; Canvas.willRenderCanvases += OnAfterCanvasRebuild;
Logging.LogMulticast(typeof(Canvas), "willRenderCanvases", Logger.LogMulticast(typeof(Canvas), "willRenderCanvases",
message: "InitializeAfterCanvasRebuild"); message: "InitializeAfterCanvasRebuild");
} }
#if UNITY_EDITOR #if UNITY_EDITOR && UNITY_2019_3_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
#elif UNITY_EDITOR
[InitializeOnLoadMethod] [InitializeOnLoadMethod]
#endif #else
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
#endif
private static void InitializeOnLoad() private static void InitializeOnLoad()
{ {
Canvas.willRenderCanvases -= OnAfterCanvasRebuild; Canvas.willRenderCanvases -= OnAfterCanvasRebuild;
s_IsInitializedAfterCanvasRebuild = false; s_IsInitializedAfterCanvasRebuild = false;
s_LastScreenSize = default;
} }
/// <summary> /// <summary>

View File

@@ -0,0 +1,242 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using System.Linq;
using Coffee.UIParticleInternal;
using UnityEditor;
using UnityEngine;
namespace Coffee.UIExtensions
{
[Icon("Packages/com.coffee.ui-particle/Editor/UIParticleIcon.png")]
[ExecuteAlways]
internal class ParticleSystemPreviewer : MonoBehaviour
{
// Do nothing.
}
[CustomEditor(typeof(ParticleSystemPreviewer))]
[CanEditMultipleObjects]
internal class ParticleSystemPreviewerEditor : Editor
{
private GameObject[] _gameObjects;
private void OnEnable()
{
_gameObjects = targets.OfType<ParticleSystemPreviewer>().Select(x => x.gameObject).ToArray();
ParticleSystemPreviewSystem.Register(_gameObjects);
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
EditorGUILayout.HelpBox("ParticleSystemPreviewer will be removed in build.", MessageType.Warning);
ParticleSystemPreviewSystem.DrawWarningForTemporary(_gameObjects);
ParticleSystemPreviewSystem.DrawWarningForPermanent(_gameObjects);
}
}
/// <summary>
/// This class manages temporary ParticleSystems for preview purposes.
/// When previewing in the editor, it is common to place an empty ParticleSystem as the root, but it consumes memory at runtime if included in the build.
/// The temporary ParticleSystems created by this class only exist when the specified GameObject is selected, and are automatically deleted when the selection is cleared.
/// </summary>
internal class ParticleSystemPreviewSystem : ScriptableSingleton<ParticleSystemPreviewSystem>
{
private const HideFlags k_TemporaryHideFlags = HideFlags.DontSave | HideFlags.NotEditable;
private const string k_TemporaryMesssage = "The temporary ParticleSystem for preview is attached.\n" +
"It will be removed when exiting edit mode.";
private const string k_PermanentMesssage = "The permanent ParticleSystem is attached.\n" +
"It will be included in build.";
[SerializeField]
private List<GameObject> m_PreviewObjects = new List<GameObject>();
#if UNITY_2019_3_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
#endif
[InitializeOnLoadMethod]
public static void Initialize()
{
instance.OnSelectionChanged();
Selection.selectionChanged -= instance.OnSelectionChanged;
Selection.selectionChanged += instance.OnSelectionChanged;
}
/// <summary>
/// Adds a temporary ParticleSystem to the specified GameObject for preview purposes.
/// </summary>
public static void Register(GameObject[] targets)
{
foreach (var target in targets)
{
Register(target);
}
}
/// <summary>
/// Adds a temporary ParticleSystem to the specified GameObject for preview purposes.
/// </summary>
public static void Register(GameObject target)
{
if (!target) return;
if (EditorApplication.isPlaying) return;
if (instance.m_PreviewObjects.Contains(target)) return;
if (target.TryGetComponent<ParticleSystem>(out var ps))
{
if (ps.hideFlags == k_TemporaryHideFlags)
{
RegisterParticleSystem(ps);
}
return;
}
// Create temporary ParticleSystem for preview.
RegisterParticleSystem(target.AddComponent<ParticleSystem>());
}
/// <summary>
/// Removes the temporary ParticleSystem associated with the specified GameObject.
/// </summary>
/// <param name="target"></param>
public static void Unregister(GameObject target)
{
if (!target) return;
var index = instance.m_PreviewObjects.IndexOf(target);
if (index < 0) return;
instance.m_PreviewObjects.RemoveAt(index);
if (HasTemporaryParticleSystem(target))
{
RemoveParticleSystem(target);
}
}
private static void RegisterParticleSystem(ParticleSystem ps)
{
if (!ps) return;
if (EditorApplication.isPlaying) return;
ps.hideFlags = k_TemporaryHideFlags;
var emission = ps.emission;
emission.enabled = false;
var shape = ps.shape;
shape.enabled = false;
if (ps.TryGetComponent<ParticleSystemRenderer>(out var psr))
{
psr.enabled = false;
psr.hideFlags = k_TemporaryHideFlags;
}
instance.m_PreviewObjects.Add(ps.gameObject);
EditorUtility.SetDirty(ps.gameObject);
}
/// <summary>
/// Removes the temporary ParticleSystem associated with the specified GameObject.
/// </summary>
/// <param name="target"></param>
private static void RemoveParticleSystem(GameObject target)
{
if (target.TryGetComponent<ParticleSystem>(out var ps))
{
Misc.DestroyImmediate(ps);
EditorUtility.SetDirty(target);
}
if (target.TryGetComponent<ParticleSystem>(out var psr))
{
Misc.DestroyImmediate(psr);
EditorUtility.SetDirty(target);
}
}
/// <summary>
/// Checks if the specified GameObject has a temporary ParticleSystem.
/// </summary>
private static bool HasTemporaryParticleSystem(GameObject target)
{
return target
&& instance.m_PreviewObjects.Contains(target)
&& target.TryGetComponent<ParticleSystem>(out var ps)
&& ps.hideFlags == k_TemporaryHideFlags;
}
/// <summary>
/// Checks if the specified GameObject has a permanent ParticleSystem.
/// </summary>
private static bool HasPermanentParticleSystem(GameObject target)
{
return target
&& target.TryGetComponent<ParticleSystem>(out var ps)
&& ps.hideFlags != k_TemporaryHideFlags;
}
private void OnSelectionChanged()
{
var selectedGameObjects = Selection.gameObjects;
for (var i = m_PreviewObjects.Count - 1; 0 <= i; i--)
{
var go = m_PreviewObjects[i];
if (!go)
{
m_PreviewObjects.RemoveAt(i);
}
else if (EditorApplication.isPlaying && !selectedGameObjects.Contains(go))
{
Unregister(go);
}
}
}
public static void DrawWarningForTemporary(GameObject[] gameObjects)
{
if (gameObjects == null || gameObjects.Length == 0 || !gameObjects.Any(HasTemporaryParticleSystem)) return;
if (HelpBoxButton(MessageType.Warning, k_TemporaryMesssage, "Remove"))
{
foreach (var go in gameObjects)
{
if (HasTemporaryParticleSystem(go))
{
RemoveParticleSystem(go);
}
}
}
}
public static void DrawWarningForPermanent(GameObject[] gameObjects)
{
if (gameObjects == null || gameObjects.Length == 0 || !gameObjects.Any(HasPermanentParticleSystem)) return;
if (HelpBoxButton(MessageType.Info, k_PermanentMesssage, "Remove"))
{
foreach (var go in gameObjects)
{
if (HasPermanentParticleSystem(go))
{
RemoveParticleSystem(go);
Unregister(go);
Register(go);
}
}
}
}
private static bool HelpBoxButton(MessageType messageType, string message, string buttonText)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.HelpBox(message, messageType, true);
var clicked = GUILayout.Button(EditorGUIUtility.TrTempContent(buttonText));
EditorGUILayout.EndHorizontal();
return clicked;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b171deb49fb7b471291108ad7e1c9baa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -9,6 +9,7 @@ using UnityEngine.UI;
using Random = UnityEngine.Random; using Random = UnityEngine.Random;
[assembly: InternalsVisibleTo("Coffee.UIParticle.Editor")] [assembly: InternalsVisibleTo("Coffee.UIParticle.Editor")]
[assembly: InternalsVisibleTo("Coffee.UIParticle.Editor.Tests")]
[assembly: InternalsVisibleTo("Coffee.UIParticle.PerformanceDemo")] [assembly: InternalsVisibleTo("Coffee.UIParticle.PerformanceDemo")]
[assembly: InternalsVisibleTo("Coffee.UIParticle.Demo")] [assembly: InternalsVisibleTo("Coffee.UIParticle.Demo")]
@@ -262,6 +263,13 @@ namespace Coffee.UIExtensions
set => m_CustomViewSize = Mathf.Max(0.1f, value); set => m_CustomViewSize = Mathf.Max(0.1f, value);
} }
/// <summary>
/// View size for Baking.
/// </summary>
public float viewSizeForBaking => useCustomView
? m_CustomViewSize
: UIParticleProjectSettings.defaultViewSizeForBaking;
/// <summary> /// <summary>
/// Time scale multiplier. /// Time scale multiplier.
/// </summary> /// </summary>
@@ -357,6 +365,7 @@ namespace Coffee.UIExtensions
_isScaleStored = false; _isScaleStored = false;
UIParticleUpdater.Unregister(this); UIParticleUpdater.Unregister(this);
_renderers.RemoveAll(r => r == null);
_renderers.ForEach(r => r.Reset()); _renderers.ForEach(r => r.Reset());
UnregisterDirtyMaterialCallback(UpdateRendererMaterial); UnregisterDirtyMaterialCallback(UpdateRendererMaterial);
@@ -470,7 +479,7 @@ 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 || !r.material) continue; if (r == null || r.material == null) continue;
result.Add(r.material); result.Add(r.material);
} }
} }
@@ -488,7 +497,7 @@ namespace Coffee.UIExtensions
/// </summary> /// </summary>
public void SetParticleSystemInstance(GameObject instance, bool destroyOldParticles) public void SetParticleSystemInstance(GameObject instance, bool destroyOldParticles)
{ {
if (!instance) return; if (instance == null) return;
var childCount = transform.childCount; var childCount = transform.childCount;
for (var i = 0; i < childCount; i++) for (var i = 0; i < childCount; i++)
@@ -517,7 +526,7 @@ namespace Coffee.UIExtensions
/// </summary> /// </summary>
public void SetParticleSystemPrefab(GameObject prefab) public void SetParticleSystemPrefab(GameObject prefab)
{ {
if (!prefab) return; if (prefab == null) return;
SetParticleSystemInstance(Instantiate(prefab.gameObject), true); SetParticleSystemInstance(Instantiate(prefab.gameObject), true);
} }
@@ -537,12 +546,17 @@ namespace Coffee.UIExtensions
/// </summary> /// </summary>
private void RefreshParticles(GameObject root) private void RefreshParticles(GameObject root)
{ {
if (!root) return; if (root == null) return;
root.GetComponentsInChildren(true, particles); root.GetComponentsInChildren(true, particles);
for (var i = particles.Count - 1; 0 <= i; i--) for (var i = particles.Count - 1; 0 <= i; i--)
{ {
var ps = particles[i]; var ps = particles[i];
if (!ps || ps.GetComponentInParent<UIParticle>(true) != this) if (!ps
#if UNITY_EDITOR
|| (ps.hideFlags & HideFlags.DontSave) != 0 // Dummy ParticleSystems for preview.
|| ps.gameObject.CompareTag("EditorOnly") // Ignore "EditorOnly" tagged ParticleSystems.
#endif
|| ps.GetComponentInParent<UIParticle>(true) != this) // Ignore ParticleSystems that are not under this UIParticle.
{ {
particles.RemoveAt(i); particles.RemoveAt(i);
} }
@@ -590,7 +604,7 @@ namespace Coffee.UIExtensions
for (var i = 0; i < particleSystems.Count; i++) for (var i = 0; i < particleSystems.Count; i++)
{ {
var ps = particleSystems[i]; var ps = particleSystems[i];
if (!ps) continue; if (ps == null) continue;
var mainEmitter = ps.GetMainEmitter(particleSystems); var mainEmitter = ps.GetMainEmitter(particleSystems);
GetRenderer(j++).Set(this, ps, false, mainEmitter); GetRenderer(j++).Set(this, ps, false, mainEmitter);
@@ -641,7 +655,7 @@ 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) continue; if (r != null) continue;
RefreshParticles(particles); RefreshParticles(particles);
break; break;
@@ -651,7 +665,7 @@ 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) continue; if (r == null) continue;
r.UpdateMesh(bakeCamera); r.UpdateMesh(bakeCamera);
} }
@@ -680,7 +694,7 @@ 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) continue; if (r == null) continue;
r.maskable = maskable; r.maskable = maskable;
r.SetMaterialDirty(); r.SetMaterialDirty();
} }
@@ -693,7 +707,7 @@ namespace Coffee.UIExtensions
_renderers.Add(UIParticleRenderer.AddRenderer(this, index)); _renderers.Add(UIParticleRenderer.AddRenderer(this, index));
} }
if (!_renderers[index]) if (_renderers[index] == null)
{ {
_renderers[index] = UIParticleRenderer.AddRenderer(this, index); _renderers[index] = UIParticleRenderer.AddRenderer(this, index);
} }
@@ -703,15 +717,15 @@ namespace Coffee.UIExtensions
private Camera GetBakeCamera() private Camera GetBakeCamera()
{ {
if (!canvas) return Camera.main; if (canvas == null) return Camera.main;
if (!useCustomView && canvas.renderMode != RenderMode.ScreenSpaceOverlay && canvas.rootCanvas.worldCamera) if (!useCustomView && canvas.renderMode != RenderMode.ScreenSpaceOverlay && canvas.rootCanvas.worldCamera)
{ {
return canvas.rootCanvas.worldCamera; return canvas.rootCanvas.worldCamera;
} }
if (_bakeCamera) if (_bakeCamera != null)
{ {
_bakeCamera.orthographicSize = useCustomView ? customViewSize : 10; _bakeCamera.orthographicSize = viewSizeForBaking;
return _bakeCamera; return _bakeCamera;
} }
@@ -728,7 +742,7 @@ namespace Coffee.UIExtensions
} }
// Create baking camera. // Create baking camera.
if (!_bakeCamera) if (_bakeCamera == null)
{ {
var go = new GameObject("[generated] UIParticle BakingCamera"); var go = new GameObject("[generated] UIParticle BakingCamera");
go.SetActive(false); go.SetActive(false);
@@ -738,7 +752,7 @@ namespace Coffee.UIExtensions
// Setup baking camera. // Setup baking camera.
_bakeCamera.enabled = false; _bakeCamera.enabled = false;
_bakeCamera.orthographicSize = useCustomView ? customViewSize : 10; _bakeCamera.orthographicSize = viewSizeForBaking;
_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;

View File

@@ -208,7 +208,7 @@ namespace Coffee.UIExtensions
private Vector3 GetDestinationPosition(UIParticle uiParticle, ParticleSystem particleSystem) private Vector3 GetDestinationPosition(UIParticle uiParticle, ParticleSystem particleSystem)
{ {
var isUI = uiParticle && uiParticle.enabled; var isUI = uiParticle != null && uiParticle.enabled;
var psPos = particleSystem.transform.position; var psPos = particleSystem.transform.position;
var attractorPos = transform.position; var attractorPos = transform.position;
var dstPos = attractorPos; var dstPos = attractorPos;

View File

@@ -2,6 +2,7 @@
using Coffee.UIParticleInternal; using Coffee.UIParticleInternal;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityEngine.Serialization;
namespace Coffee.UIExtensions namespace Coffee.UIExtensions
{ {
@@ -9,14 +10,25 @@ namespace Coffee.UIExtensions
{ {
[Header("Setting")] [Header("Setting")]
[SerializeField] [SerializeField]
internal bool m_EnableLinearToGamma = true; [Tooltip("Automatically correct the color space of the mesh.")]
[FormerlySerializedAs("m_EnableLinearToGamma")]
private bool m_AutoColorCorrection = true;
public static bool enableLinearToGamma public static bool autoColorCorrection
{ {
get => instance.m_EnableLinearToGamma; get => instance.m_AutoColorCorrection;
set => instance.m_EnableLinearToGamma = value; set => instance.m_AutoColorCorrection = value;
} }
[SerializeField]
[Tooltip("Default view size for baking particle systems.")]
private float m_DefaultViewSizeForBaking = 10;
public static float defaultViewSizeForBaking
{
get => instance.m_DefaultViewSizeForBaking;
set => instance.m_DefaultViewSizeForBaking = value;
}
[Header("Editor")] [Header("Editor")]
[Tooltip("Hide the automatically generated objects.\n" + [Tooltip("Hide the automatically generated objects.\n" +
@@ -25,16 +37,32 @@ namespace Coffee.UIExtensions
[SerializeField] [SerializeField]
private bool m_HideGeneratedObjects = true; private bool m_HideGeneratedObjects = true;
public static HideFlags globalHideFlags => instance.m_HideGeneratedObjects [Tooltip("When selecting UIParticle, a temporary ParticleSystem is generated for preview.")]
[SerializeField]
private bool m_PreviewOnSelect = true;
internal static HideFlags globalHideFlags => instance.m_HideGeneratedObjects
? HideFlags.DontSave | HideFlags.NotEditable | HideFlags.HideInHierarchy | HideFlags.HideInInspector ? HideFlags.DontSave | HideFlags.NotEditable | HideFlags.HideInHierarchy | HideFlags.HideInInspector
: HideFlags.DontSave | HideFlags.NotEditable; : HideFlags.DontSave | HideFlags.NotEditable;
internal static bool previewOnSelect => instance.m_PreviewOnSelect;
#if UNITY_EDITOR #if UNITY_EDITOR
[SettingsProvider] [SettingsProvider]
private static SettingsProvider CreateSettingsProvider() private static SettingsProvider CreateSettingsProvider()
{ {
return new PreloadedProjectSettingsProvider("Project/UI/UI Particle"); return new PreloadedProjectSettingsProvider("Project/UI/UI Particle");
} }
[CustomEditor(typeof(UIParticleProjectSettings))]
private class UIParticleProjectSettingsEditor : Editor
{
public override void OnInspectorGUI()
{
EditorGUIUtility.labelWidth = 180;
base.OnInspectorGUI();
}
}
#endif #endif
} }
} }

View File

@@ -26,10 +26,12 @@ namespace Coffee.UIExtensions
private static readonly List<Material> s_Materials = new List<Material>(2); private static readonly List<Material> s_Materials = new List<Material>(2);
private static MaterialPropertyBlock s_Mpb; private static MaterialPropertyBlock s_Mpb;
private static readonly Vector3[] s_Corners = new Vector3[4]; private static readonly Vector3[] s_Corners = new Vector3[4];
private static readonly VertexHelper s_VertexHelper = new VertexHelper();
private bool _delay; private bool _delay;
private int _index; private int _index;
private bool _isPrevStored; private bool _isPrevStored;
private bool _isTrail; private bool _isTrail;
private bool _meshCleared;
private Bounds _lastBounds; private Bounds _lastBounds;
private Material _materialForRendering; private Material _materialForRendering;
private Material _modifiedMaterial; private Material _modifiedMaterial;
@@ -55,7 +57,7 @@ namespace Coffee.UIExtensions
s_Corners[1] = transform.TransformPoint(_lastBounds.min.x, _lastBounds.max.y, 0); s_Corners[1] = transform.TransformPoint(_lastBounds.min.x, _lastBounds.max.y, 0);
s_Corners[2] = transform.TransformPoint(_lastBounds.max.x, _lastBounds.max.y, 0); s_Corners[2] = transform.TransformPoint(_lastBounds.max.x, _lastBounds.max.y, 0);
s_Corners[3] = transform.TransformPoint(_lastBounds.max.x, _lastBounds.min.y, 0); s_Corners[3] = transform.TransformPoint(_lastBounds.max.x, _lastBounds.min.y, 0);
if (canvas) if (canvas != null)
{ {
var worldToLocalMatrix = canvas.rootCanvas.transform.worldToLocalMatrix; var worldToLocalMatrix = canvas.rootCanvas.transform.worldToLocalMatrix;
for (var i = 0; i < 4; ++i) for (var i = 0; i < 4; ++i)
@@ -95,7 +97,7 @@ namespace Coffee.UIExtensions
{ {
get get
{ {
if (!_materialForRendering) if (_materialForRendering == null)
{ {
_materialForRendering = base.materialForRendering; _materialForRendering = base.materialForRendering;
} }
@@ -106,7 +108,7 @@ namespace Coffee.UIExtensions
public void Reset(int index = -1) public void Reset(int index = -1)
{ {
if (_renderer) if (_renderer != null)
{ {
_renderer.enabled = true; _renderer.enabled = true;
} }
@@ -121,7 +123,7 @@ namespace Coffee.UIExtensions
} }
//_emitter = null; //_emitter = null;
if (this && isActiveAndEnabled) if (isActiveAndEnabled)
{ {
material = null; material = null;
canvasRenderer.Clear(); canvasRenderer.Clear();
@@ -140,7 +142,7 @@ namespace Coffee.UIExtensions
base.OnEnable(); base.OnEnable();
hideFlags = UIParticleProjectSettings.globalHideFlags; hideFlags = UIParticleProjectSettings.globalHideFlags;
if (!s_CombineInstances[0].mesh) if (s_CombineInstances[0].mesh == null)
{ {
s_CombineInstances[0].mesh = new Mesh s_CombineInstances[0].mesh = new Mesh
{ {
@@ -205,9 +207,9 @@ namespace Coffee.UIExtensions
} }
var hash = new Hash128( var hash = new Hash128(
modifiedMaterial ? (uint)modifiedMaterial.GetInstanceID() : 0, modifiedMaterial ? (uint)modifiedMaterial.GetHashCode() : 0,
texture ? (uint)texture.GetInstanceID() : 0, texture ? (uint)texture.GetHashCode() : 0,
0 < _parent.m_AnimatableProperties.Length ? (uint)GetInstanceID() : 0, 0 < _parent.m_AnimatableProperties.Length ? (uint)GetHashCode() : 0,
#if UNITY_EDITOR #if UNITY_EDITOR
(uint)EditorJsonUtility.ToJson(modifiedMaterial).GetHashCode() (uint)EditorJsonUtility.ToJson(modifiedMaterial).GetHashCode()
#else #else
@@ -285,15 +287,38 @@ namespace Coffee.UIExtensions
|| (_isTrail && !_particleSystem.trails.enabled) // Trail, but it is not enabled. || (_isTrail && !_particleSystem.trails.enabled) // Trail, but it is not enabled.
) )
{ {
// Skip clearing the mesh if it's already cleared.
if (_meshCleared) return;
Profiler.BeginSample("[UIParticleRenderer] Clear Mesh"); Profiler.BeginSample("[UIParticleRenderer] Clear Mesh");
workerMesh.Clear(); workerMesh.Clear();
canvasRenderer.SetMesh(workerMesh); canvasRenderer.SetMesh(workerMesh);
_lastBounds = new Bounds(); _lastBounds = new Bounds();
_meshCleared = true;
Profiler.EndSample(); Profiler.EndSample();
return; return;
} }
// Reset custom data.
// var customData = _particleSystem.customData;
// if (!customData.enabled || customData.GetMode(ParticleSystemCustomData.Custom1) == ParticleSystemCustomDataMode.Disabled)
// {
// customData.SetVector(ParticleSystemCustomData.Custom1, 0, 0);
// customData.SetVector(ParticleSystemCustomData.Custom1, 1, 0);
// customData.SetVector(ParticleSystemCustomData.Custom1, 2, 0);
// customData.SetVector(ParticleSystemCustomData.Custom1, 3, 0);
// }
//
// if (!customData.enabled || customData.GetMode(ParticleSystemCustomData.Custom2) == ParticleSystemCustomDataMode.Disabled)
// {
// customData.SetVector(ParticleSystemCustomData.Custom2, 0, 0);
// customData.SetVector(ParticleSystemCustomData.Custom2, 1, 0);
// customData.SetVector(ParticleSystemCustomData.Custom2, 2, 0);
// customData.SetVector(ParticleSystemCustomData.Custom2, 3, 0);
// }
_meshCleared = false;
var main = _particleSystem.main; var main = _particleSystem.main;
var scale = GetWorldScale(); var scale = GetWorldScale();
var psPos = _particleSystem.transform.position; var psPos = _particleSystem.transform.position;
@@ -420,18 +445,22 @@ namespace Coffee.UIExtensions
_lastBounds = bounds; _lastBounds = bounds;
// Convert linear color to gamma color. // Convert linear color to gamma color.
if (UIParticleProjectSettings.enableLinearToGamma && canvas.ShouldGammaToLinearInMesh()) if (UIParticleProjectSettings.autoColorCorrection && canvas.ShouldGammaToLinearInMesh())
{ {
workerMesh.LinearToGamma(); workerMesh.LinearToGamma();
} }
var components = InternalListPool<Component>.Rent(); var components = InternalListPool<Component>.Rent();
GetComponents(typeof(IMeshModifier), components); GetComponents(typeof(IMeshModifier), components);
for (var i = 0; i < components.Count; i++) if (0 < components.Count)
{ {
#pragma warning disable CS0618 // Type or member is obsolete workerMesh.CopyTo(s_VertexHelper);
((IMeshModifier)components[i]).ModifyMesh(workerMesh); for (var i = 0; i < components.Count; i++)
#pragma warning restore CS0618 // Type or member is obsolete {
((IMeshModifier)components[i]).ModifyMesh(s_VertexHelper);
}
s_VertexHelper.FillMesh(workerMesh);
} }
InternalListPool<Component>.Return(ref components); InternalListPool<Component>.Return(ref components);
@@ -713,7 +742,7 @@ namespace Coffee.UIExtensions
if (s_Mpb.isEmpty) return; if (s_Mpb.isEmpty) return;
// #41: Copy the value from MaterialPropertyBlock to CanvasRenderer // #41: Copy the value from MaterialPropertyBlock to CanvasRenderer
if (!materialForRendering) return; if (materialForRendering == null) return;
for (var i = 0; i < _parent.m_AnimatableProperties.Length; i++) for (var i = 0; i < _parent.m_AnimatableProperties.Length; i++)
{ {

View File

@@ -16,29 +16,40 @@ namespace Coffee.UIExtensions
public static void Register(UIParticle particle) public static void Register(UIParticle particle)
{ {
if (!particle) return; if (particle == null) return;
s_ActiveParticles.Add(particle); s_ActiveParticles.Add(particle);
} }
public static void Unregister(UIParticle particle) public static void Unregister(UIParticle particle)
{ {
if (!particle) return; if (particle == null) return;
s_ActiveParticles.Remove(particle); s_ActiveParticles.Remove(particle);
} }
public static void Register(UIParticleAttractor attractor) public static void Register(UIParticleAttractor attractor)
{ {
if (!attractor) return; if (attractor == null) return;
s_ActiveAttractors.Add(attractor); s_ActiveAttractors.Add(attractor);
} }
public static void Unregister(UIParticleAttractor attractor) public static void Unregister(UIParticleAttractor attractor)
{ {
if (!attractor) return; if (attractor == null) return;
s_ActiveAttractors.Remove(attractor); s_ActiveAttractors.Remove(attractor);
} }
#if UNITY_EDITOR #if UNITY_EDITOR
#if UNITY_2019_3_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void OnDomainReload()
{
s_ActiveParticles.Clear();
s_ActiveAttractors.Clear();
s_UpdatedGroupIds.Clear();
s_FrameCount = 0;
}
#endif
[InitializeOnLoadMethod] [InitializeOnLoadMethod]
private static void InitializeOnLoad() private static void InitializeOnLoad()
{ {
@@ -71,7 +82,7 @@ namespace Coffee.UIExtensions
for (var i = 0; i < s_ActiveParticles.Count; i++) for (var i = 0; i < s_ActiveParticles.Count; i++)
{ {
var uip = s_ActiveParticles[i]; var uip = s_ActiveParticles[i];
if (!uip || !uip.canvas || !uip.isPrimary || !s_UpdatedGroupIds.Add(uip.groupId)) continue; if (uip == null || uip.canvas == null || !uip.isPrimary || !s_UpdatedGroupIds.Add(uip.groupId)) continue;
uip.UpdateTransformScale(); uip.UpdateTransformScale();
uip.UpdateRenderers(); uip.UpdateRenderers();
@@ -81,7 +92,7 @@ namespace Coffee.UIExtensions
for (var i = 0; i < s_ActiveParticles.Count; i++) for (var i = 0; i < s_ActiveParticles.Count; i++)
{ {
var uip = s_ActiveParticles[i]; var uip = s_ActiveParticles[i];
if (!uip || !uip.canvas) continue; if (uip == null || uip.canvas == null) continue;
uip.UpdateTransformScale(); uip.UpdateTransformScale();
@@ -125,7 +136,7 @@ namespace Coffee.UIExtensions
var uip = s_ActiveParticles[i]; var uip = s_ActiveParticles[i];
if (!uip.useMeshSharing || uip.groupId != groupId) continue; if (!uip.useMeshSharing || uip.groupId != groupId) continue;
if (uip.isPrimary) return uip; if (uip.isPrimary) return uip;
if (!primary && uip.canSimulate) primary = uip; if (primary == null && uip.canSimulate) primary = uip;
} }
return primary; return primary;

View File

@@ -13,11 +13,7 @@ namespace Coffee.UIParticleInternal
{ {
if (s_TmpParticles.Length < size) if (s_TmpParticles.Length < size)
{ {
while (s_TmpParticles.Length < size) size = Mathf.NextPowerOfTwo(size);
{
size = Mathf.NextPowerOfTwo(size);
}
s_TmpParticles = new ParticleSystem.Particle[size]; s_TmpParticles = new ParticleSystem.Particle[size];
} }
@@ -88,15 +84,15 @@ namespace Coffee.UIParticleInternal
var bRenderer = b.GetComponent<ParticleSystemRenderer>(); var bRenderer = b.GetComponent<ParticleSystemRenderer>();
// Render queue: ascending // Render queue: ascending
var aMat = aRenderer.sharedMaterial ? aRenderer.sharedMaterial : aRenderer.trailMaterial; var aMat = aRenderer.sharedMaterial != null ? aRenderer.sharedMaterial : aRenderer.trailMaterial;
var bMat = bRenderer.sharedMaterial ? bRenderer.sharedMaterial : bRenderer.trailMaterial; var bMat = bRenderer.sharedMaterial != null ? bRenderer.sharedMaterial : bRenderer.trailMaterial;
if (!aMat && !bMat) return 0; if (aMat == null && bMat == null) return 0;
if (!aMat) return -1; if (aMat == null) return -1;
if (!bMat) return 1; if (bMat == null) return 1;
if (sortByMaterial) if (sortByMaterial)
{ {
return aMat.GetInstanceID() - bMat.GetInstanceID(); return aMat.GetHashCode() - bMat.GetHashCode();
} }
if (aMat.renderQueue != bMat.renderQueue) if (aMat.renderQueue != bMat.renderQueue)
@@ -135,7 +131,7 @@ namespace Coffee.UIParticleInternal
{ {
for (var i = 0; i < list.Count; i++) for (var i = 0; i < list.Count; i++)
{ {
if (list[i].GetInstanceID() == ps.GetInstanceID()) if (list[i].GetHashCode() == ps.GetHashCode())
{ {
return i; return i;
} }
@@ -146,7 +142,7 @@ namespace Coffee.UIParticleInternal
public static Texture2D GetTextureForSprite(this ParticleSystem self) public static Texture2D GetTextureForSprite(this ParticleSystem self)
{ {
if (!self) return null; if (self == null) return null;
// Get sprite's texture. // Get sprite's texture.
var tsaModule = self.textureSheetAnimation; var tsaModule = self.textureSheetAnimation;
@@ -155,7 +151,7 @@ namespace Coffee.UIParticleInternal
for (var i = 0; i < tsaModule.spriteCount; i++) for (var i = 0; i < tsaModule.spriteCount; i++)
{ {
var sprite = tsaModule.GetSprite(i); var sprite = tsaModule.GetSprite(i);
if (!sprite) continue; if (sprite == null) continue;
return sprite.GetActualTexture(); return sprite.GetActualTexture();
} }
@@ -167,14 +163,14 @@ namespace Coffee.UIParticleInternal
{ {
foreach (var p in self) foreach (var p in self)
{ {
if (!p) continue; if (p == null) continue;
action.Invoke(p); action.Invoke(p);
} }
} }
public static ParticleSystem GetMainEmitter(this ParticleSystem self, List<ParticleSystem> list) public static ParticleSystem GetMainEmitter(this ParticleSystem self, List<ParticleSystem> list)
{ {
if (!self || list == null || list.Count == 0) return null; if (self == null || list == null || list.Count == 0) return null;
for (var i = 0; i < list.Count; i++) for (var i = 0; i < list.Count; i++)
{ {
@@ -187,9 +183,11 @@ namespace Coffee.UIParticleInternal
public static bool IsSubEmitterOf(this ParticleSystem self, ParticleSystem parent) public static bool IsSubEmitterOf(this ParticleSystem self, ParticleSystem parent)
{ {
if (!self || !parent) return false; if (self == null || parent == null) return false;
var subEmitters = parent.subEmitters; var subEmitters = parent.subEmitters;
if (!subEmitters.enabled) return false; // No sub emitters.
var count = subEmitters.subEmittersCount; var count = subEmitters.subEmittersCount;
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
{ {

View File

@@ -0,0 +1,32 @@
using System.Linq;
using UnityEngine;
[RequireComponent(typeof(ParticleSystemRenderer))]
public class ColorArrayInjection : MonoBehaviour
{
[SerializeField] private string m_PropertyName = "_Color2";
[SerializeField] private Color[] m_Colors = new Color[4]
{
Color.red,
Color.green,
Color.blue,
Color.yellow
};
private void OnEnable()
{
if (TryGetComponent<ParticleSystemRenderer>(out var psr))
{
var mpb = new MaterialPropertyBlock();
psr.GetPropertyBlock(mpb);
mpb.SetVectorArray(m_PropertyName, m_Colors.Select(x => new Vector4(x.r, x.g, x.b, 1.0f)).ToArray());
psr.SetPropertyBlock(mpb);
}
}
private void OnValidate()
{
OnEnable();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: da4c2e90ecd3d4dd4a520099c08cf493
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,160 @@
Shader "UI/Additive And Color"
{
Properties
{
_MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Fog { Mode Off }
Blend One One
ColorMask [_ColorMask]
Pass
{
Name "Default"
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#pragma multi_compile __ UNITY_UI_CLIP_RECT
#pragma multi_compile __ UNITY_UI_ALPHACLIP
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
float4 mask : TEXCOORD2;
UNITY_VERTEX_OUTPUT_STEREO
};
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _TextureSampleAdd;
float4 _ClipRect;
float _UIMaskSoftnessX;
float _UIMaskSoftnessY;
int _UIVertexColorAlwaysGammaSpace;
#define LENGTH 4
float4 _Color2[4];
half3 _UIGammaToLinear(half3 value)
{
half3 low = 0.0849710 * value - 0.000163029;
half3 high = value * (value * (value * 0.265885 + 0.736584) - 0.00980184) + 0.00319697;
// We should be 0.5 away from any actual gamma value stored in an 8 bit channel
const half3 split = (half3)0.0725490; // Equals 18.5 / 255
return (value < split) ? low : high;
}
v2f vert(appdata_t v)
{
v2f OUT;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
float4 vPosition = UnityObjectToClipPos(v.vertex);
OUT.worldPosition = v.vertex;
OUT.vertex = vPosition;
float2 pixelSize = vPosition.w;
pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));
float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
OUT.mask = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));
if (_UIVertexColorAlwaysGammaSpace)
{
#ifndef UNITY_COLORSPACE_GAMMA
v.color.rgb = _UIGammaToLinear(v.color.rgb);
#endif
}
OUT.color = v.color * _Color;
return OUT;
}
fixed4 frag(v2f IN) : SV_Target
{
//Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
//The incoming alpha could have numerical instability, which makes it very sensible to
//HDR color transparency blend, when it blends with the world's texture.
const half alphaPrecision = half(0xff);
const half invAlphaPrecision = half(1.0 / alphaPrecision);
IN.color.a = round(IN.color.a * alphaPrecision) * invAlphaPrecision;
half4 color = IN.color * (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd);
#ifdef UNITY_UI_CLIP_RECT
half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
color.a *= m.x * m.y;
#endif
#ifdef UNITY_UI_ALPHACLIP
clip(color.a - 0.001);
#endif
fixed3 fc = _Color2[floor(color.r*LENGTH-0.01)].rgb;
if (0.1 < fc.r || 0.1 < fc.g || 0.1 < fc.b)
{
color.rgb = fc;
}
color.rgb *= color.a;
return color;
}
ENDCG
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 9cc9ed37ff19d40e684526abbc1d44a6
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,90 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: UIParticle_Demo_Animatable+Color
m_Shader: {fileID: 4800000, guid: 9cc9ed37ff19d40e684526abbc1d44a6, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 10300, guid: 0000000000000000f000000000000000, type: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _BumpScale: 1
- _ColorMask: 15
- _Cutoff: 0.5
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _Stencil: 0
- _StencilComp: 8
- _StencilOp: 0
- _StencilReadMask: 255
- _StencilWriteMask: 255
- _UVSec: 0
- _UseUIAlphaClip: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 81f29a831022a4756b17daa366d67e10
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -8,8 +8,8 @@ Material:
m_PrefabInstance: {fileID: 0} m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_Name: UIParticle_Demo_Animatable m_Name: UIParticle_Demo_Animatable
m_Shader: {fileID: 4800000, guid: ecfa8f5732b504ef98fba10aa18d0326, type: 3} m_Shader: {fileID: 4800000, guid: 9cc9ed37ff19d40e684526abbc1d44a6, type: 3}
m_ShaderKeywords: m_Parent: {fileID: 0}
m_LightmapFlags: 4 m_LightmapFlags: 4
m_EnableInstancingVariants: 0 m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0 m_DoubleSidedGI: 0

View File

@@ -69,6 +69,8 @@ MonoBehaviour:
m_AnimatableProperties: m_AnimatableProperties:
- m_Name: _MainTex_ST - m_Name: _MainTex_ST
m_Type: 1 m_Type: 1
- m_Name: _Color2
m_Type: 103
m_Particles: m_Particles:
- {fileID: 198637387440798640} - {fileID: 198637387440798640}
m_MeshSharing: 0 m_MeshSharing: 0
@@ -88,6 +90,7 @@ GameObject:
- component: {fileID: 199176884810573912} - component: {fileID: 199176884810573912}
- component: {fileID: 222047182059782320} - component: {fileID: 222047182059782320}
- component: {fileID: 95475093951880840} - component: {fileID: 95475093951880840}
- component: {fileID: 3543952012814938654}
m_Layer: 0 m_Layer: 0
m_Name: UvAnimParticle m_Name: UvAnimParticle
m_TagString: Untagged m_TagString: Untagged
@@ -4864,3 +4867,21 @@ Animator:
m_HasTransformHierarchy: 1 m_HasTransformHierarchy: 1
m_AllowConstantClipSamplingOptimization: 1 m_AllowConstantClipSamplingOptimization: 1
m_KeepAnimatorControllerStateOnDisable: 0 m_KeepAnimatorControllerStateOnDisable: 0
--- !u!114 &3543952012814938654
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1349193913114882}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: da4c2e90ecd3d4dd4a520099c08cf493, type: 3}
m_Name:
m_EditorClassIdentifier:
m_PropertyName: _Color2
m_Colors:
- {r: 1, g: 0, b: 0, a: 1}
- {r: 0, g: 1, b: 0, a: 1}
- {r: 0, g: 0, b: 1, a: 1}
- {r: 1, g: 0.92156863, b: 0.015686275, a: 1}

View File

@@ -15,7 +15,7 @@ namespace Coffee.UIExtensions.Demo
private void Start() private void Start()
{ {
if (!m_Origin) return; if (m_Origin == null) return;
m_Origin.SetActive(false); m_Origin.SetActive(false);
var parent = m_Origin.transform.parent; var parent = m_Origin.transform.parent;

View File

@@ -42,7 +42,7 @@ public class UIElementDragger : MonoBehaviour, IBeginDragHandler, IDragHandler,
break; break;
case Target.Custom: case Target.Custom:
_rectTransform.localPosition += delta; _rectTransform.localPosition += delta;
if (m_CustomTarget) if (m_CustomTarget != null)
{ {
if (m_UseCanvasScale) if (m_UseCanvasScale)
{ {

View File

@@ -40,12 +40,8 @@
Lighting Off Lighting Off
ZWrite Off ZWrite Off
ZTest [unity_GUIZTestMode] ZTest [unity_GUIZTestMode]
Fog Fog { Mode Off }
{
Mode Off
}
Blend One One Blend One One
ColorMask [_ColorMask] ColorMask [_ColorMask]
Pass Pass
@@ -76,6 +72,7 @@
fixed4 color : COLOR; fixed4 color : COLOR;
float2 texcoord : TEXCOORD0; float2 texcoord : TEXCOORD0;
float4 worldPosition : TEXCOORD1; float4 worldPosition : TEXCOORD1;
float4 mask : TEXCOORD2;
UNITY_VERTEX_OUTPUT_STEREO UNITY_VERTEX_OUTPUT_STEREO
}; };
@@ -84,16 +81,43 @@
float4 _MainTex_ST; float4 _MainTex_ST;
fixed4 _TextureSampleAdd; fixed4 _TextureSampleAdd;
float4 _ClipRect; float4 _ClipRect;
float _UIMaskSoftnessX;
float _UIMaskSoftnessY;
int _UIVertexColorAlwaysGammaSpace;
half3 _UIGammaToLinear(half3 value)
{
half3 low = 0.0849710 * value - 0.000163029;
half3 high = value * (value * (value * 0.265885 + 0.736584) - 0.00980184) + 0.00319697;
// We should be 0.5 away from any actual gamma value stored in an 8 bit channel
const half3 split = (half3)0.0725490; // Equals 18.5 / 255
return (value < split) ? low : high;
}
v2f vert(appdata_t v) v2f vert(appdata_t v)
{ {
v2f OUT; v2f OUT;
UNITY_SETUP_INSTANCE_ID(v); UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
float4 vPosition = UnityObjectToClipPos(v.vertex);
OUT.worldPosition = v.vertex; OUT.worldPosition = v.vertex;
OUT.vertex = UnityObjectToClipPos(OUT.worldPosition); OUT.vertex = vPosition;
OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); float2 pixelSize = vPosition.w;
pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));
float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
OUT.mask = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));
if (_UIVertexColorAlwaysGammaSpace)
{
#ifndef UNITY_COLORSPACE_GAMMA
v.color.rgb = _UIGammaToLinear(v.color.rgb);
#endif
}
OUT.color = v.color * _Color; OUT.color = v.color * _Color;
return OUT; return OUT;
@@ -101,17 +125,26 @@
fixed4 frag(v2f IN) : SV_Target fixed4 frag(v2f IN) : SV_Target
{ {
half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color; //Round up the alpha color coming from the interpolator (to 1.0/256.0 steps)
//The incoming alpha could have numerical instability, which makes it very sensible to
//HDR color transparency blend, when it blends with the world's texture.
const half alphaPrecision = half(0xff);
const half invAlphaPrecision = half(1.0 / alphaPrecision);
IN.color.a = round(IN.color.a * alphaPrecision) * invAlphaPrecision;
half4 color = IN.color * (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd);
#ifdef UNITY_UI_CLIP_RECT #ifdef UNITY_UI_CLIP_RECT
color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect); half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
color.a *= m.x * m.y;
#endif #endif
#ifdef UNITY_UI_ALPHACLIP #ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001); clip(color.a - 0.001);
#endif #endif
color.rgb *= color.a; color.rgb *= color.a;
return color; return color;
} }
ENDCG ENDCG

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.11.3", "version": "4.13.1",
"unity": "2018.2", "unity": "2018.2",
"license": "MIT", "license": "MIT",
"repository": { "repository": {