You've already forked ParticleEffectForUGUI
mirror of
https://github.com/mob-sakai/ParticleEffectForUGUI.git
synced 2026-06-29 02:33:43 +00:00
Compare commits
6 Commits
5.0.0-prev
...
0441238cc5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0441238cc5 | ||
|
|
639bc9c342 | ||
|
|
b3d42a2478 | ||
|
|
418b257ae2 | ||
|
|
664c7963ea | ||
|
|
7363f7497c |
@@ -3,7 +3,7 @@
|
||||
"com.coffee.development": "https://github.com/mob-sakai/Coffee.Internal.git?path=Packages/Development",
|
||||
"com.coffee.minimal-resource": "https://github.com/mob-sakai/Coffee.Internal.git?path=Packages/MinimalResource",
|
||||
"com.coffee.nano-monitor": "https://github.com/mob-sakai/Coffee.Internal.git?path=Packages/NanoMonitor",
|
||||
"com.unity.ide.rider": "3.0.31",
|
||||
"com.unity.ide.rider": "3.0.36",
|
||||
"com.unity.test-framework": "1.1.33",
|
||||
"com.unity.modules.animation": "1.0.0",
|
||||
"com.unity.modules.physics": "1.0.0"
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.ide.rider": {
|
||||
"version": "3.0.31",
|
||||
"version": "3.0.36",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -8,12 +9,11 @@ using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using UnityEngine.UI;
|
||||
using Coffee.UIParticleInternal;
|
||||
using Object = UnityEngine.Object;
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
using UnityEditor.Overlays;
|
||||
#else
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Object = UnityEngine.Object;
|
||||
#endif
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
using UnityEditor.SceneManagement;
|
||||
@@ -49,6 +49,7 @@ namespace Coffee.UIExtensions
|
||||
private static readonly GUIContent s_ContentRandom = new GUIContent("Random");
|
||||
private static readonly GUIContent s_ContentScale = new GUIContent("Scale");
|
||||
private static readonly GUIContent s_ContentPrimary = new GUIContent("Primary");
|
||||
private static readonly GUIContent s_Remove = new GUIContent("Remove");
|
||||
private static readonly Regex s_RegexBuiltInGuid = new Regex(@"^0{16}.0{15}$", RegexOptions.Compiled);
|
||||
private static readonly List<Material> s_TempMaterials = new List<Material>();
|
||||
|
||||
@@ -66,6 +67,7 @@ namespace Coffee.UIExtensions
|
||||
private ReorderableList _ro;
|
||||
private bool _showMax;
|
||||
private bool _is3DScaleMode;
|
||||
private GameObject[] _gameObjects;
|
||||
|
||||
private static readonly HashSet<Shader> s_Shaders = new HashSet<Shader>();
|
||||
#if UNITY_2018 || UNITY_2019
|
||||
@@ -183,6 +185,13 @@ namespace Coffee.UIExtensions
|
||||
y.hasMultipleDifferentValues ||
|
||||
z.hasMultipleDifferentValues;
|
||||
}
|
||||
|
||||
// Add temporary ParticleSystem for preview if enabled.
|
||||
if (UIParticleProjectSettings.s_PreviewOnSelectOnSelect)
|
||||
{
|
||||
_gameObjects = targets.OfType<UIParticle>().Select(x => x.gameObject).ToArray();
|
||||
ParticleSystemPreviewSystem.Register(_gameObjects);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -197,7 +206,10 @@ namespace Coffee.UIExtensions
|
||||
serializedObject.Update();
|
||||
|
||||
// Maskable
|
||||
EditorGUILayout.PropertyField(_maskable);
|
||||
if (_maskable != null)
|
||||
{
|
||||
EditorGUILayout.PropertyField(_maskable);
|
||||
}
|
||||
|
||||
// Scale
|
||||
EditorGUI.BeginDisabledGroup(!_meshSharing.hasMultipleDifferentValues && _meshSharing.intValue == 4);
|
||||
@@ -344,6 +356,10 @@ namespace Coffee.UIExtensions
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Remove the temporary ParticleSystem.
|
||||
ParticleSystemPreviewSystem.DrawWarningForTemporary(_gameObjects);
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
@@ -482,7 +498,7 @@ namespace Coffee.UIExtensions
|
||||
#endif
|
||||
}
|
||||
|
||||
private static bool FixButton(bool show, string text)
|
||||
private static bool FixButton(bool show, string text, GUIContent buttonText = null)
|
||||
{
|
||||
if (!show) return false;
|
||||
using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(true)))
|
||||
@@ -490,7 +506,7 @@ namespace Coffee.UIExtensions
|
||||
EditorGUILayout.HelpBox(text, MessageType.Warning, true);
|
||||
using (new EditorGUILayout.VerticalScope())
|
||||
{
|
||||
return GUILayout.Button(s_ContentFix, GUILayout.Width(30));
|
||||
return GUILayout.Button(buttonText ?? s_ContentFix, GUILayout.ExpandWidth(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,9 +183,14 @@ _This package requires **Unity 2018.3 or later**._
|
||||
- **Time Scale Multiplier:** Time scale multiplier.
|
||||
- **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.
|
||||
|
||||
> [!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>
|
||||
|
||||
### Basic Usage
|
||||
@@ -195,6 +200,10 @@ and z-position.
|
||||
2. Adjust the ParticleSystem as you like.
|
||||

|
||||
|
||||
> [!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>
|
||||
|
||||
### Usage with Your Existing ParticleSystem Prefab
|
||||
@@ -256,13 +265,34 @@ uiParticle.Stop();
|
||||
- **Unscaled Time:** Update with unscaled delta time.
|
||||
- **OnAttracted**: An event called when attracting is complete (per particle).
|
||||
|
||||
|
||||
<br><br>
|
||||
|
||||
### Component: ParticleSystemPreviewer
|
||||
|
||||
`ParticleSystemPreviewer` is used to preview a ParticleSystem in the editor.
|
||||
|
||||
- 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>
|
||||
|
||||
### Project Settings
|
||||
|
||||

|
||||
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.
|
||||

|
||||
|
||||
#### Settings
|
||||
|
||||
- **Enable Linear To Gamma**: Automatically correct the color space of the mesh.
|
||||
|
||||
#### 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>
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace Coffee.UIExtensions
|
||||
break;
|
||||
case ShaderPropertyType.Float:
|
||||
case ShaderPropertyType.Range:
|
||||
material.SetFloat(id, mpb.GetFloat(id));
|
||||
material.SetFloat(id, mpb.GetFloat(id));
|
||||
break;
|
||||
case ShaderPropertyType.Texture:
|
||||
material.SetTexture(id, mpb.GetTexture(id));
|
||||
|
||||
@@ -12,6 +12,33 @@ namespace Coffee.UIParticleInternal
|
||||
/// </summary>
|
||||
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>
|
||||
/// Get components in children of a specific type in the hierarchy of a GameObject.
|
||||
/// </summary>
|
||||
|
||||
@@ -14,6 +14,15 @@ namespace Coffee.UIParticleInternal
|
||||
public abstract class PreloadedProjectSettings : ScriptableObject
|
||||
#if UNITY_EDITOR
|
||||
{
|
||||
[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 Postprocessor : AssetPostprocessor
|
||||
{
|
||||
private static void OnPostprocessAllAssets(string[] _, string[] __, string[] ___, string[] ____)
|
||||
@@ -22,12 +31,35 @@ namespace Coffee.UIParticleInternal
|
||||
}
|
||||
}
|
||||
|
||||
private class PreprocessBuildWithReport : IPreprocessBuildWithReport
|
||||
private class ExcludeFromBuild : IPreprocessBuildWithReport, IPostprocessBuildWithReport
|
||||
{
|
||||
int IOrderedCallback.callbackOrder => 0;
|
||||
|
||||
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 || settings.m_PreLoadSettingsInBuild) continue;
|
||||
|
||||
PlayerSettings.SetPreloadedAssets(
|
||||
PlayerSettings.GetPreloadedAssets()
|
||||
.Where(x => x && 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();
|
||||
}
|
||||
}
|
||||
@@ -39,13 +71,16 @@ namespace Coffee.UIParticleInternal
|
||||
var defaultSettings = GetDefaultSettings(t);
|
||||
if (defaultSettings == null)
|
||||
{
|
||||
// When create a new instance, automatically set it as default settings.
|
||||
defaultSettings = CreateInstance(t) as PreloadedProjectSettings;
|
||||
SetDefaultSettings(defaultSettings);
|
||||
if (!s_BuildingPlayer)
|
||||
{
|
||||
// When create a new instance, automatically set it as default settings.
|
||||
defaultSettings = CreateInstance(t) as PreloadedProjectSettings;
|
||||
SetDefaultSettings(defaultSettings);
|
||||
}
|
||||
}
|
||||
else if (GetPreloadedSettings(t).Length != 1)
|
||||
{
|
||||
SetDefaultSettings(defaultSettings);
|
||||
if (!s_BuildingPlayer) SetDefaultSettings(defaultSettings);
|
||||
}
|
||||
|
||||
if (defaultSettings != null)
|
||||
@@ -73,7 +108,7 @@ namespace Coffee.UIParticleInternal
|
||||
protected static PreloadedProjectSettings GetDefaultSettings(Type type)
|
||||
{
|
||||
return GetPreloadedSettings(type).FirstOrDefault() as PreloadedProjectSettings
|
||||
?? AssetDatabase.FindAssets($"t:{nameof(PreloadedProjectSettings)}")
|
||||
?? AssetDatabase.FindAssets($"t:{type.Name}")
|
||||
.Select(AssetDatabase.GUIDToAssetPath)
|
||||
.Select(AssetDatabase.LoadAssetAtPath<PreloadedProjectSettings>)
|
||||
.FirstOrDefault(x => x != null && x.GetType() == type);
|
||||
@@ -120,6 +155,35 @@ namespace Coffee.UIParticleInternal
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
}
|
||||
@@ -151,7 +215,7 @@ namespace Coffee.UIParticleInternal
|
||||
return s_Instance;
|
||||
}
|
||||
|
||||
SetDefaultSettings(s_Instance);
|
||||
if (!s_BuildingPlayer) SetDefaultSettings(s_Instance);
|
||||
return s_Instance;
|
||||
}
|
||||
}
|
||||
@@ -193,9 +257,12 @@ namespace Coffee.UIParticleInternal
|
||||
}
|
||||
|
||||
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
||||
#else
|
||||
if (s_Instance && s_Instance != this)
|
||||
{
|
||||
Destroy(s_Instance);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (s_Instance != null) return;
|
||||
s_Instance = this as T;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Coffee.UIParticleInternal
|
||||
UIExtraCallbacks.onLateAfterCanvasRebuild += ClearAllCache;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
#if UNITY_EDITOR && UNITY_2019_2_OR_NEWER
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void Clear()
|
||||
{
|
||||
|
||||
@@ -1,2 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f9f22bb079324476b1473030ad9fec3
|
||||
guid: 4f9f22bb079324476b1473030ad9fec3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -13,7 +13,9 @@ namespace Coffee.UIParticleInternal
|
||||
|
||||
public static int count => s_Repository.count;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static Func<string, Shader> onShaderFind = Shader.Find;
|
||||
|
||||
#if UNITY_EDITOR && UNITY_2019_2_OR_NEWER
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
public static void Clear()
|
||||
{
|
||||
@@ -48,7 +50,7 @@ namespace Coffee.UIParticleInternal
|
||||
public static void Get(Hash128 hash, ref Material material, string shaderName)
|
||||
{
|
||||
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
|
||||
}, shaderName);
|
||||
@@ -61,7 +63,7 @@ namespace Coffee.UIParticleInternal
|
||||
public static void Get(Hash128 hash, ref Material material, string shaderName, string[] keywords)
|
||||
{
|
||||
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,
|
||||
shaderKeywords = x.keywords
|
||||
|
||||
@@ -48,15 +48,10 @@ namespace Coffee.UIParticleInternal
|
||||
{
|
||||
if (obj == null) return;
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor)
|
||||
{
|
||||
Object.DestroyImmediate(obj);
|
||||
}
|
||||
else
|
||||
Object.DestroyImmediate(obj, true);
|
||||
#else
|
||||
Object.Destroy(obj);
|
||||
#endif
|
||||
{
|
||||
Object.Destroy(obj);
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
@@ -114,7 +109,7 @@ namespace Coffee.UIParticleInternal
|
||||
{
|
||||
if (Misc.isBatchOrBuilding) return;
|
||||
|
||||
var types = TypeCache.GetTypesWithAttribute<IconAttribute>();
|
||||
var types = TypeCache.GetTypesWithAttribute(typeof(IconAttribute));
|
||||
var scripts = MonoImporter.GetAllRuntimeMonoScripts();
|
||||
foreach (var type in types)
|
||||
{
|
||||
|
||||
88
Packages/src/Runtime/Internal/Utilities/TypeCache.cs
Normal file
88
Packages/src/Runtime/Internal/Utilities/TypeCache.cs
Normal 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
|
||||
11
Packages/src/Runtime/Internal/Utilities/TypeCache.cs.meta
Normal file
11
Packages/src/Runtime/Internal/Utilities/TypeCache.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc1207b657ed74ec19e459664d06925f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
222
Packages/src/Runtime/ParticleSystemPreviewer.cs
Normal file
222
Packages/src/Runtime/ParticleSystemPreviewer.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
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.
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[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();
|
||||
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;
|
||||
|
||||
[SerializeField]
|
||||
private List<GameObject> m_PreviewObjects = new List<GameObject>();
|
||||
|
||||
/// <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 _)) return;
|
||||
|
||||
// Create temporary ParticleSystem for preview.
|
||||
var ps = target.AddComponent<ParticleSystem>();
|
||||
ps.hideFlags = k_TemporaryHideFlags;
|
||||
|
||||
var emission = ps.emission;
|
||||
emission.enabled = false;
|
||||
var shape = ps.shape;
|
||||
shape.enabled = false;
|
||||
|
||||
if (target.TryGetComponent<ParticleSystemRenderer>(out var psr))
|
||||
{
|
||||
psr.enabled = false;
|
||||
psr.hideFlags = k_TemporaryHideFlags;
|
||||
}
|
||||
|
||||
instance.m_PreviewObjects.Add(target);
|
||||
EditorUtility.SetDirty(target);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 OnEnable()
|
||||
{
|
||||
Selection.selectionChanged -= OnSelectionChanged;
|
||||
Selection.selectionChanged += OnSelectionChanged;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
Selection.selectionChanged -= OnSelectionChanged;
|
||||
}
|
||||
|
||||
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 (!selectedGameObjects.Contains(go))
|
||||
{
|
||||
Unregister(go);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawWarningForTemporary(GameObject[] gameObjects)
|
||||
{
|
||||
if (gameObjects == null || gameObjects.Length == 0 || !gameObjects.Any(HasTemporaryParticleSystem)) return;
|
||||
|
||||
if (WarningButton("The temporary ParticleSystem for preview is attached.\n" +
|
||||
"It will be removed when exiting edit mode.", "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 (WarningButton("The permanent ParticleSystem is attached.\n" +
|
||||
"It will be included in build.", "Remove"))
|
||||
{
|
||||
foreach (var go in gameObjects)
|
||||
{
|
||||
if (HasPermanentParticleSystem(go))
|
||||
{
|
||||
RemoveParticleSystem(go);
|
||||
Unregister(go);
|
||||
Register(go);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool WarningButton(string message, string buttonText)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.HelpBox(message, MessageType.Warning, true);
|
||||
var clicked = GUILayout.Button(EditorGUIUtility.TrTempContent(buttonText));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
return clicked;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
11
Packages/src/Runtime/ParticleSystemPreviewer.cs.meta
Normal file
11
Packages/src/Runtime/ParticleSystemPreviewer.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b171deb49fb7b471291108ad7e1c9baa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -545,7 +545,10 @@ namespace Coffee.UIExtensions
|
||||
{
|
||||
var ps = particles[i];
|
||||
if (!ps
|
||||
#if UNITY_EDITOR
|
||||
|| ps.hideFlags == HideFlags.HideAndDontSave // 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);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Coffee.UIParticleInternal;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Coffee.UIExtensions
|
||||
{
|
||||
@@ -9,12 +10,14 @@ namespace Coffee.UIExtensions
|
||||
{
|
||||
[Header("Setting")]
|
||||
[SerializeField]
|
||||
internal bool m_EnableLinearToGamma = true;
|
||||
[Tooltip("Automatically correct the color space of the mesh.")]
|
||||
[FormerlySerializedAs("m_EnableLinearToGamma")]
|
||||
internal bool m_AutoColorCorrection = true;
|
||||
|
||||
public static bool enableLinearToGamma
|
||||
public static bool autoColorCorrection
|
||||
{
|
||||
get => instance.m_EnableLinearToGamma;
|
||||
set => instance.m_EnableLinearToGamma = value;
|
||||
get => instance.m_AutoColorCorrection;
|
||||
set => instance.m_AutoColorCorrection = value;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,10 +28,16 @@ namespace Coffee.UIExtensions
|
||||
[SerializeField]
|
||||
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;
|
||||
|
||||
internal static bool s_PreviewOnSelectOnSelect => instance.m_PreviewOnSelect;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[SettingsProvider]
|
||||
private static SettingsProvider CreateSettingsProvider()
|
||||
|
||||
@@ -444,7 +444,7 @@ namespace Coffee.UIExtensions
|
||||
_lastBounds = bounds;
|
||||
|
||||
// Convert linear color to gamma color.
|
||||
if (UIParticleProjectSettings.enableLinearToGamma && canvas.ShouldGammaToLinearInMesh())
|
||||
if (UIParticleProjectSettings.autoColorCorrection && canvas.ShouldGammaToLinearInMesh())
|
||||
{
|
||||
workerMesh.LinearToGamma();
|
||||
}
|
||||
|
||||
@@ -40,12 +40,8 @@
|
||||
Lighting Off
|
||||
ZWrite Off
|
||||
ZTest [unity_GUIZTestMode]
|
||||
Fog
|
||||
{
|
||||
Mode Off
|
||||
}
|
||||
Fog { Mode Off }
|
||||
Blend One One
|
||||
|
||||
ColorMask [_ColorMask]
|
||||
|
||||
Pass
|
||||
@@ -76,6 +72,7 @@
|
||||
fixed4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
float4 worldPosition : TEXCOORD1;
|
||||
float4 mask : TEXCOORD2;
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
||||
@@ -84,16 +81,43 @@
|
||||
float4 _MainTex_ST;
|
||||
fixed4 _TextureSampleAdd;
|
||||
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 OUT;
|
||||
UNITY_SETUP_INSTANCE_ID(v);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
|
||||
float4 vPosition = UnityObjectToClipPos(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;
|
||||
return OUT;
|
||||
@@ -101,17 +125,26 @@
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
#ifdef UNITY_UI_ALPHACLIP
|
||||
clip (color.a - 0.001);
|
||||
clip(color.a - 0.001);
|
||||
#endif
|
||||
|
||||
color.rgb *= color.a;
|
||||
|
||||
return color;
|
||||
}
|
||||
ENDCG
|
||||
|
||||
Reference in New Issue
Block a user