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.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -8,6 +9,7 @@ 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
|
||||
@@ -66,6 +68,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 +186,13 @@ namespace Coffee.UIExtensions
|
||||
y.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>
|
||||
@@ -344,6 +354,10 @@ namespace Coffee.UIExtensions
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Remove the temporary ParticleSystem.
|
||||
ParticleSystemPreviewSystem.DrawWarningForTemporary(_gameObjects);
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
@@ -482,7 +496,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 +504,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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,15 +48,10 @@ namespace Coffee.UIParticleInternal
|
||||
{
|
||||
if (obj == null) return;
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor)
|
||||
{
|
||||
Object.DestroyImmediate(obj);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
Object.DestroyImmediate(obj, true);
|
||||
#else
|
||||
Object.Destroy(obj);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[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];
|
||||
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);
|
||||
|
||||
@@ -25,10 +25,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 previewOnSelect => instance.m_PreviewOnSelect;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[SettingsProvider]
|
||||
private static SettingsProvider CreateSettingsProvider()
|
||||
|
||||
Reference in New Issue
Block a user