You've already forked ParticleEffectForUGUI
mirror of
https://github.com/mob-sakai/ParticleEffectForUGUI.git
synced 2026-06-26 08:23:45 +00:00
feat: preview the ParticleSystem playback when selecting a UIParticle in the Editor
This commit is contained in:
@@ -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,6 +9,7 @@ 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
|
||||||
@@ -66,6 +68,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
|
||||||
@@ -183,6 +186,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>
|
||||||
@@ -344,6 +354,10 @@ namespace Coffee.UIExtensions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Remove the temporary ParticleSystem.
|
||||||
|
ParticleSystemPreviewSystem.DrawWarningForTemporary(_gameObjects);
|
||||||
|
|
||||||
Profiler.EndSample();
|
Profiler.EndSample();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,7 +496,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 +504,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,15 +48,10 @@ namespace Coffee.UIParticleInternal
|
|||||||
{
|
{
|
||||||
if (obj == null) 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")]
|
||||||
|
|||||||
237
Runtime/ParticleSystemPreviewer.cs
Normal file
237
Runtime/ParticleSystemPreviewer.cs
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
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>();
|
||||||
|
|
||||||
|
#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 (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
Runtime/ParticleSystemPreviewer.cs.meta
Normal file
11
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];
|
var ps = particles[i];
|
||||||
if (!ps
|
if (!ps
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
|| (ps.hideFlags & HideFlags.DontSave) != 0 // Dummy ParticleSystems for preview.
|
||||||
|| ps.gameObject.CompareTag("EditorOnly") // Ignore "EditorOnly" tagged ParticleSystems.
|
|| ps.gameObject.CompareTag("EditorOnly") // Ignore "EditorOnly" tagged ParticleSystems.
|
||||||
|
#endif
|
||||||
|| ps.GetComponentInParent<UIParticle>(true) != this) // Ignore ParticleSystems that are not under this UIParticle.
|
|| ps.GetComponentInParent<UIParticle>(true) != this) // Ignore ParticleSystems that are not under this UIParticle.
|
||||||
{
|
{
|
||||||
particles.RemoveAt(i);
|
particles.RemoveAt(i);
|
||||||
|
|||||||
@@ -25,10 +25,16 @@ 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()
|
||||||
|
|||||||
Reference in New Issue
Block a user