From 0d4a5875d130c74a573f713b46e109b7affefb21 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 28 Aug 2020 05:38:13 +0000 Subject: [PATCH] 3.0.0-preview.19 # [3.0.0-preview.19](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v3.0.0-preview.18...v3.0.0-preview.19) (2020-08-28) ### Bug Fixes * baking camera settings for camera space ([436c5e4](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/436c5e47f75c3e167dcd77c188847e9d7d6ea68d)) * fix local simulation ([7add9de](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/7add9defb70be29ddbe536d854591c2e0d9e83fa)) ### Features * add menu to create UIParticle ([2fa1843](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/2fa18431f0c8c4aeadfdd1cb98eeeef5ac6970a0)) * add play/pause/stop api ([f09a386](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/f09a386bc59fbab8143f7f0b814c8684aea7f27c)) * support for changing rendering orders ([745d4a5](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/745d4a598846b3e77d1071433079fdd5140921a8)) * Support for child ParticleSystem rendering ([4ee90be](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/4ee90be17c68bf405f81f432615a3eebaa022366)) * UIParticle for trail is no longer needed ([466e43c](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/466e43cf931d211907419f804a90776a0d9f4906)) ### BREAKING CHANGES * The child UIParticle is no longer needed. --- CHANGELOG.md | 22 + Scripts/BakingCamera.cs | 14 +- Scripts/Editor/UIParticleEditor.cs | 148 ++----- Scripts/Editor/UIParticleMenu.cs | 45 +-- Scripts/MeshHelper.cs | 35 +- Scripts/SpriteExtensions.cs | 31 -- Scripts/UIParticle.cs | 376 ++++++++---------- Scripts/UIParticleUpdater.cs | 227 ++++++----- Scripts/Utils.cs | 95 +++++ ...SpriteExtensions.cs.meta => Utils.cs.meta} | 0 package.json | 2 +- 11 files changed, 500 insertions(+), 495 deletions(-) delete mode 100644 Scripts/SpriteExtensions.cs create mode 100644 Scripts/Utils.cs rename Scripts/{SpriteExtensions.cs.meta => Utils.cs.meta} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45913a1..0b48a02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +# [3.0.0-preview.19](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v3.0.0-preview.18...v3.0.0-preview.19) (2020-08-28) + + +### Bug Fixes + +* baking camera settings for camera space ([436c5e4](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/436c5e47f75c3e167dcd77c188847e9d7d6ea68d)) +* fix local simulation ([7add9de](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/7add9defb70be29ddbe536d854591c2e0d9e83fa)) + + +### Features + +* add menu to create UIParticle ([2fa1843](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/2fa18431f0c8c4aeadfdd1cb98eeeef5ac6970a0)) +* add play/pause/stop api ([f09a386](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/f09a386bc59fbab8143f7f0b814c8684aea7f27c)) +* support for changing rendering orders ([745d4a5](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/745d4a598846b3e77d1071433079fdd5140921a8)) +* Support for child ParticleSystem rendering ([4ee90be](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/4ee90be17c68bf405f81f432615a3eebaa022366)) +* UIParticle for trail is no longer needed ([466e43c](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/466e43cf931d211907419f804a90776a0d9f4906)) + + +### BREAKING CHANGES + +* The child UIParticle is no longer needed. + # [3.0.0-preview.18](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v3.0.0-preview.17...v3.0.0-preview.18) (2020-08-19) diff --git a/Scripts/BakingCamera.cs b/Scripts/BakingCamera.cs index b848d3e..70130df 100644 --- a/Scripts/BakingCamera.cs +++ b/Scripts/BakingCamera.cs @@ -1,11 +1,12 @@ -using System; -using UnityEngine; +using UnityEngine; namespace Coffee.UIExtensions { internal class BakingCamera : MonoBehaviour { static BakingCamera s_Instance; + private static readonly Vector3 s_OrthoPosition = new Vector3(0, 0, -1000); + private static readonly Quaternion s_OrthoRotation = Quaternion.identity; #if UNITY_2018_3_OR_NEWER && UNITY_EDITOR static BakingCamera s_InstanceForPrefab; @@ -106,11 +107,18 @@ namespace Coffee.UIExtensions { var cameraTr = camera.transform; transform.SetPositionAndRotation(cameraTr.position, cameraTr.rotation); + + Instance._camera.orthographic = camera.orthographic; + Instance._camera.orthographicSize = camera.orthographicSize; + Instance._camera.fieldOfView = camera.fieldOfView; + Instance._camera.nearClipPlane = camera.nearClipPlane; + Instance._camera.farClipPlane = camera.farClipPlane; + Instance._camera.rect = camera.rect; } else { Instance._camera.orthographic = true; - transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); + transform.SetPositionAndRotation(canvas.transform.position + s_OrthoPosition, s_OrthoRotation); } return Instance._camera; diff --git a/Scripts/Editor/UIParticleEditor.cs b/Scripts/Editor/UIParticleEditor.cs index 7a774cc..8619d1e 100644 --- a/Scripts/Editor/UIParticleEditor.cs +++ b/Scripts/Editor/UIParticleEditor.cs @@ -2,7 +2,8 @@ using UnityEditor; using UnityEditor.UI; using UnityEngine; using System.Collections.Generic; -using System.Linq; +using UnityEditorInternal; +using UnityEngine.UI; namespace Coffee.UIExtensions { @@ -13,17 +14,13 @@ namespace Coffee.UIExtensions //################################ // Constant or Static Members. //################################ - private static readonly GUIContent s_ContentParticleMaterial = new GUIContent("Particle Material", "The material for rendering particles"); - private static readonly GUIContent s_ContentTrailMaterial = new GUIContent("Trail Material", "The material for rendering particle trails"); private static readonly GUIContent s_ContentAdvancedOptions = new GUIContent("Advanced Options"); - private static readonly GUIContent s_Content3D = new GUIContent("3D"); - private static readonly GUIContent s_ContentScale = new GUIContent("Scale"); - private static readonly List s_ParticleSystems = new List(); - private SerializedProperty _spParticleSystem; - private SerializedProperty _spScale3D; + private SerializedProperty _spScale; private SerializedProperty _spIgnoreCanvasScaler; private SerializedProperty _spAnimatableProperties; + + private ReorderableList _ro; private bool _xyzMode; private static readonly List s_MaskablePropertyNames = new List @@ -46,10 +43,30 @@ namespace Coffee.UIExtensions protected override void OnEnable() { base.OnEnable(); - _spParticleSystem = serializedObject.FindProperty("m_ParticleSystem"); - _spScale3D = serializedObject.FindProperty("m_Scale3D"); + _spScale = serializedObject.FindProperty("m_Scale"); _spIgnoreCanvasScaler = serializedObject.FindProperty("m_IgnoreCanvasScaler"); _spAnimatableProperties = serializedObject.FindProperty("m_AnimatableProperties"); + + var sp = serializedObject.FindProperty("m_Particles"); + _ro = new ReorderableList(sp.serializedObject, sp, true, true, false, false); + _ro.elementHeight = EditorGUIUtility.singleLineHeight + 4; + _ro.drawElementCallback = (rect, index, active, focused) => + { + rect.y += 1; + rect.height = EditorGUIUtility.singleLineHeight; + EditorGUI.PropertyField(rect, sp.GetArrayElementAtIndex(index), GUIContent.none); + }; + _ro.drawHeaderCallback += rect => + { + EditorGUI.LabelField(new Rect(rect.x, rect.y, 150, rect.height), "Rendering Order"); + + if (!GUI.Button(new Rect(rect.width - 80, rect.y - 1, 80, rect.height), "Reset", EditorStyles.miniButton)) return; + + foreach (UIParticle t in targets) + { + t.RefreshParticles(); + } + }; } /// @@ -62,119 +79,40 @@ namespace Coffee.UIExtensions serializedObject.Update(); - EditorGUI.BeginDisabledGroup(true); - EditorGUILayout.PropertyField(_spParticleSystem); - EditorGUI.EndDisabledGroup(); - - // Draw materials. - EditorGUI.indentLevel++; - var ps = _spParticleSystem.objectReferenceValue as ParticleSystem; - if (ps != null) - { - var pr = ps.GetComponent(); - var sp = new SerializedObject(pr).FindProperty("m_Materials"); - - EditorGUI.BeginChangeCheck(); - { - EditorGUILayout.PropertyField(sp.GetArrayElementAtIndex(0), s_ContentParticleMaterial); - if (2 <= sp.arraySize) - { - EditorGUILayout.PropertyField(sp.GetArrayElementAtIndex(1), s_ContentTrailMaterial); - } - } - if (EditorGUI.EndChangeCheck()) - { - sp.serializedObject.ApplyModifiedProperties(); - } - - if (!Application.isPlaying && pr.enabled) - { - EditorGUILayout.HelpBox("UIParticles disable the RendererModule in ParticleSystem at runtime to prevent double rendering.", MessageType.Warning); - } - } - - EditorGUI.indentLevel--; - // Advanced Options EditorGUILayout.Space(); EditorGUILayout.LabelField(s_ContentAdvancedOptions, EditorStyles.boldLabel); - _xyzMode = DrawFloatOrVector3Field(_spScale3D, _xyzMode); - + // IgnoreCanvasScaler EditorGUILayout.PropertyField(_spIgnoreCanvasScaler); + // Scale + EditorGUILayout.PropertyField(_spScale); + // AnimatableProperties AnimatedPropertiesEditor.DrawAnimatableProperties(_spAnimatableProperties, current.material); - // Fix - current.GetComponentsInChildren(true, s_ParticleSystems); - if (s_ParticleSystems.Any(x => x.GetComponent() == null)) + _ro.DoLayoutList(); + + // Does the shader support UI masks? + if (current.maskable && current.GetComponentInParent()) { - GUILayout.BeginHorizontal(); - EditorGUILayout.HelpBox("There are child ParticleSystems that does not have a UIParticle component.\nAdd UIParticle component to them.", MessageType.Warning); - GUILayout.BeginVertical(); - if (GUILayout.Button("Fix")) + foreach (var mat in current.materials) { - foreach (var p in s_ParticleSystems.Where(x => !x.GetComponent())) + if (!mat || !mat.shader) continue; + var shader = mat.shader; + foreach (var propName in s_MaskablePropertyNames) { - p.gameObject.AddComponent(); + if (mat.HasProperty(propName)) continue; + + EditorGUILayout.HelpBox(string.Format("Shader '{0}' doesn't have '{1}' property. This graphic cannot be masked.", shader.name, propName), MessageType.Warning); + break; } } - GUILayout.EndVertical(); - GUILayout.EndHorizontal(); - } - - s_ParticleSystems.Clear(); - - // Does the shader support UI masks? - if (current.maskable && current.material && current.material.shader) - { - var mat = current.material; - var shader = mat.shader; - foreach (var propName in s_MaskablePropertyNames) - { - if (mat.HasProperty(propName)) continue; - - EditorGUILayout.HelpBox(string.Format("Shader '{0}' doesn't have '{1}' property. This graphic cannot be masked.", shader.name, propName), MessageType.Warning); - break; - } } serializedObject.ApplyModifiedProperties(); } - - private static bool DrawFloatOrVector3Field(SerializedProperty sp, bool showXyz) - { - var x = sp.FindPropertyRelative("x"); - var y = sp.FindPropertyRelative("y"); - var z = sp.FindPropertyRelative("z"); - - showXyz |= !Mathf.Approximately(x.floatValue, y.floatValue) || - !Mathf.Approximately(y.floatValue, z.floatValue) || - y.hasMultipleDifferentValues || - z.hasMultipleDifferentValues; - - EditorGUILayout.BeginHorizontal(); - if (showXyz) - { - EditorGUILayout.PropertyField(sp); - } - else - { - EditorGUI.BeginChangeCheck(); - EditorGUILayout.PropertyField(x, s_ContentScale); - if (EditorGUI.EndChangeCheck()) - z.floatValue = y.floatValue = x.floatValue; - } - - EditorGUI.BeginChangeCheck(); - showXyz = GUILayout.Toggle(showXyz, s_Content3D, EditorStyles.miniButton, GUILayout.Width(30)); - if (EditorGUI.EndChangeCheck() && !showXyz) - z.floatValue = y.floatValue = x.floatValue; - EditorGUILayout.EndHorizontal(); - - return showXyz; - } } } diff --git a/Scripts/Editor/UIParticleMenu.cs b/Scripts/Editor/UIParticleMenu.cs index f30cb96..eb465f1 100644 --- a/Scripts/Editor/UIParticleMenu.cs +++ b/Scripts/Editor/UIParticleMenu.cs @@ -2,6 +2,7 @@ using System.IO; using System.Text.RegularExpressions; using UnityEditor; using UnityEngine; +using UnityEngine.UI; namespace Coffee.UIExtensions { @@ -66,43 +67,41 @@ namespace Coffee.UIExtensions } #endif + [MenuItem("GameObject/UI/Particle System (Empty)", false, 2018)] + public static void AddParticleEmpty(MenuCommand menuCommand) + { + // Create empty UI element. + EditorApplication.ExecuteMenuItem("GameObject/UI/Image"); + var ui = Selection.activeGameObject; + Object.DestroyImmediate(ui.GetComponent()); + + // Add UIParticle. + var uiParticle = ui.AddComponent(); + uiParticle.name = "UIParticle"; + uiParticle.scale = 10; + uiParticle.rectTransform.sizeDelta = Vector2.zero; + } [MenuItem("GameObject/UI/Particle System", false, 2019)] public static void AddParticle(MenuCommand menuCommand) { - // Create UI element. - EditorApplication.ExecuteMenuItem("GameObject/UI/Image"); - var ui = Selection.activeGameObject; + // Create empty UIEffect. + AddParticleEmpty(menuCommand); + var uiParticle = Selection.activeGameObject.GetComponent(); // Create ParticleSystem. EditorApplication.ExecuteMenuItem("GameObject/Effects/Particle System"); var ps = Selection.activeGameObject; - var transform = ps.transform; - var localRotation = transform.localRotation; - - transform.SetParent(ui.transform.parent, true); - var pos = transform.localPosition; - pos.z = 0; - ps.transform.localPosition = pos; - ps.transform.localRotation = localRotation; - - // Destroy UI elemant - Object.DestroyImmediate(ui); + ps.transform.SetParent(uiParticle.transform, false); + ps.transform.localPosition = Vector3.zero; // Assign default material. var renderer = ps.GetComponent(); var defaultMat = AssetDatabase.GetBuiltinExtraResource("Default-Particle.mat"); renderer.material = defaultMat ? defaultMat : renderer.material; - // Set to hierarchy mode - var particleSystem = ps.GetComponent(); - var main = particleSystem.main; - main.scalingMode = ParticleSystemScalingMode.Hierarchy; - - // Add UIParticle. - var uiParticle = ps.AddComponent(); - uiParticle.ignoreCanvasScaler = true; - uiParticle.scale = 10; + // Refresh particles. + uiParticle.RefreshParticles(); } } } diff --git a/Scripts/MeshHelper.cs b/Scripts/MeshHelper.cs index e9efec3..bf3a7e4 100644 --- a/Scripts/MeshHelper.cs +++ b/Scripts/MeshHelper.cs @@ -6,14 +6,17 @@ namespace Coffee.UIExtensions internal static class MeshHelper { private static CombineInstance[] s_CombineInstances; + private static int s_TempIndex; private static int s_CurrentIndex; static readonly List s_Colors = new List(); private static int s_RefCount; + private static Matrix4x4 s_Transform; + public static uint activeMeshIndices { get; private set; } public static void Register() { if (0 < s_RefCount++) return; - s_CombineInstances = new CombineInstance[2]; + s_CombineInstances = new CombineInstance[8]; } public static void Unregister() @@ -37,14 +40,33 @@ namespace Coffee.UIExtensions s_CombineInstances = null; } - public static Mesh GetTemporaryMesh() + public static Mesh GetTemporaryMesh(int index) { - return s_CombineInstances[s_CurrentIndex++].mesh; + if (s_CombineInstances.Length <= s_TempIndex) s_TempIndex = s_CombineInstances.Length - 1; + s_CurrentIndex = index; + activeMeshIndices += (uint)(1 << s_CurrentIndex); + s_CombineInstances[s_TempIndex].transform = s_Transform; + return s_CombineInstances[s_TempIndex++].mesh; + } + + public static void DiscardTemporaryMesh() + { + if (s_TempIndex == 0) return; + s_TempIndex--; + activeMeshIndices -= (uint)(1 << s_CurrentIndex); + } + + public static void SetTransform(Matrix4x4 transform) + { + s_Transform = transform; } public static void Clear() { + if (s_CombineInstances == null) return; s_CurrentIndex = 0; + activeMeshIndices = 0; + s_TempIndex = 0; for (var i = 0; i < s_CombineInstances.Length; i++) { if (!s_CombineInstances[i].mesh) @@ -60,12 +82,9 @@ namespace Coffee.UIExtensions } } - public static void CombineMesh(Mesh result, Matrix4x4 transform) + public static void CombineMesh(Mesh result) { - if (!result || s_CurrentIndex == 0) return; - - for (var i = 0; i < 2; i++) - s_CombineInstances[i].transform = transform; + if (!result || s_TempIndex == 0) return; result.CombineMeshes(s_CombineInstances, false, true); result.RecalculateBounds(); diff --git a/Scripts/SpriteExtensions.cs b/Scripts/SpriteExtensions.cs deleted file mode 100644 index b1c5f13..0000000 --- a/Scripts/SpriteExtensions.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Reflection; -using UnityEngine; - -namespace Coffee.UIExtensions -{ - internal static class SpriteExtensions - { -#if UNITY_EDITOR - private static Type tSpriteEditorExtension = Type.GetType("UnityEditor.Experimental.U2D.SpriteEditorExtension, UnityEditor") - ?? Type.GetType("UnityEditor.U2D.SpriteEditorExtension, UnityEditor"); - - private static MethodInfo miGetActiveAtlasTexture = tSpriteEditorExtension - .GetMethod("GetActiveAtlasTexture", BindingFlags.Static | BindingFlags.NonPublic); - - internal static Texture2D GetActualTexture(this Sprite self) - { - if (!self) return null; - - if (Application.isPlaying) return self.texture; - var ret = miGetActiveAtlasTexture.Invoke(null, new[] {self}) as Texture2D; - return ret ? ret : self.texture; - } -#else - internal static Texture2D GetActualTexture(this Sprite self) - { - return self ? self.texture : null; - } -#endif - } -} diff --git a/Scripts/UIParticle.cs b/Scripts/UIParticle.cs index 263abcf..c06c61a 100755 --- a/Scripts/UIParticle.cs +++ b/Scripts/UIParticle.cs @@ -1,76 +1,51 @@ +using System.Collections.Generic; +using System.Runtime.CompilerServices; using UnityEngine; using UnityEngine.Rendering; +using UnityEngine.Serialization; using UnityEngine.UI; +[assembly: InternalsVisibleTo("Coffee.UIParticle.Editor")] + namespace Coffee.UIExtensions { /// /// Render maskable and sortable particle effect ,without Camera, RenderTexture or Canvas. /// [ExecuteInEditMode] + [RequireComponent(typeof(RectTransform))] [RequireComponent(typeof(CanvasRenderer))] public class UIParticle : MaskableGraphic { - //################################ - // Serialize Members. - //################################ - [Tooltip("The ParticleSystem rendered by CanvasRenderer")] [SerializeField] - ParticleSystem m_ParticleSystem; - - [Tooltip("The UIParticle to render trail effect")] [SerializeField] - internal UIParticle m_TrailParticle; - [HideInInspector] [SerializeField] bool m_IsTrail = false; - [Tooltip("Ignore canvas scaler")] [SerializeField] - bool m_IgnoreCanvasScaler = false; - - [Tooltip("Ignore parent scale")] [SerializeField] - bool m_IgnoreParent = false; + [Tooltip("Ignore canvas scaler")] [SerializeField] [FormerlySerializedAs("m_IgnoreParent")] + bool m_IgnoreCanvasScaler = true; [Tooltip("Particle effect scale")] [SerializeField] - float m_Scale = 0; + float m_Scale = 100; [Tooltip("Animatable material properties. If you want to change the material properties of the ParticleSystem in Animation, enable it.")] [SerializeField] internal AnimatableProperty[] m_AnimatableProperties = new AnimatableProperty[0]; - [Tooltip("Particle effect scale")] [SerializeField] - internal Vector3 m_Scale3D = Vector3.one; + [Tooltip("Particles")] [SerializeField] + private List m_Particles = new List(); - private readonly Material[] _maskMaterials = new Material[2]; private DrivenRectTransformTracker _tracker; private Mesh _bakedMesh; - private ParticleSystemRenderer _renderer; - private int _cachedSharedMaterialId; - private int _cachedTrailMaterialId; - private bool _cachedSpritesModeAndHasTrail; + private readonly List _modifiedMaterials = new List(); + private uint _activeMeshIndices; + private Vector3 _cachedPosition; + private static readonly List s_TempMaterials = new List(2); + - //################################ - // Public/Protected Members. - //################################ /// /// Should this graphic be considered a target for raycasting? /// public override bool raycastTarget { get { return false; } - set { base.raycastTarget = value; } - } - - /// - /// Cached ParticleSystem. - /// - public ParticleSystem cachedParticleSystem - { - get { return m_ParticleSystem ? m_ParticleSystem : (m_ParticleSystem = GetComponent()); } - } - - /// - /// Cached ParticleSystem. - /// - internal ParticleSystemRenderer cachedRenderer - { - get { return _renderer; } + set { } } public bool ignoreCanvasScaler @@ -84,116 +59,143 @@ namespace Coffee.UIExtensions /// public float scale { - get { return m_Scale3D.x; } - set { m_Scale3D.Set(value, value, value); } + get { return m_Scale; } + set { m_Scale = Mathf.Max(0.001f, value); } } - /// - /// Particle effect scale. - /// - public Vector3 scale3D - { - get { return m_Scale3D; } - set { m_Scale3D = value; } - } - - internal bool isTrailParticle - { - get { return m_IsTrail; } - } - - internal bool isSpritesMode - { - get { return textureSheetAnimationModule.enabled && textureSheetAnimationModule.mode == ParticleSystemAnimationMode.Sprites; } - } - - private bool isSpritesModeAndHasTrail - { - get { return isSpritesMode && trailModule.enabled; } - } - - private ParticleSystem.TextureSheetAnimationModule textureSheetAnimationModule - { - get { return cachedParticleSystem.textureSheetAnimation; } - } - - internal ParticleSystem.TrailModule trailModule - { - get { return cachedParticleSystem.trails; } - } - - internal ParticleSystem.MainModule mainModule - { - get { return cachedParticleSystem.main; } - } - - public bool isValid - { - get { return m_ParticleSystem && _renderer && canvas; } - } - - public Mesh bakedMesh + internal Mesh bakedMesh { get { return _bakedMesh; } } - protected override void UpdateMaterial() + public List particles { - if (!_renderer) return; + get { return m_Particles; } + } - if (!isSpritesMode) // Non sprite mode: canvas renderer has main and trail materials. + public IEnumerable materials + { + get { return _modifiedMaterials; } + } + + internal uint activeMeshIndices + { + get { return _activeMeshIndices; } + set { - canvasRenderer.materialCount = trailModule.enabled ? 2 : 1; - canvasRenderer.SetMaterial(GetModifiedMaterial(_renderer.sharedMaterial, 0), 0); - if (trailModule.enabled) - canvasRenderer.SetMaterial(GetModifiedMaterial(_renderer.trailMaterial, 1), 1); - } - else if (isTrailParticle) // Sprite mode (Trail): canvas renderer has trail material. - { - canvasRenderer.materialCount = 1; - canvasRenderer.SetMaterial(GetModifiedMaterial(_renderer.trailMaterial, 0), 0); - } - else // Sprite mode (Main): canvas renderer has main material. - { - canvasRenderer.materialCount = 1; - canvasRenderer.SetMaterial(GetModifiedMaterial(_renderer.sharedMaterial, 0), 0); + if (_activeMeshIndices == value) return; + _activeMeshIndices = value; + UpdateMaterial(); } } - private Material GetModifiedMaterial(Material baseMaterial, int index) + internal Vector3 cachedPosition { - if (!baseMaterial || 1 < index || !isActiveAndEnabled) return null; + get { return _cachedPosition; } + set { _cachedPosition = value; } + } - var hasAnimatableProperties = 0 < m_AnimatableProperties.Length && index == 0; - if (hasAnimatableProperties || isTrailParticle) - baseMaterial = new Material(baseMaterial); + public void Play() + { + particles.Exec(p => p.Play()); + } - var baseMat = baseMaterial; - if (m_ShouldRecalculateStencil) + public void Pause() + { + particles.Exec(p => p.Pause()); + } + + public void Stop() + { + particles.Exec(p => p.Stop()); + } + + public void RefreshParticles() + { + GetComponentsInChildren(particles); + + particles.Exec(p => p.GetComponent().enabled = !enabled); + particles.SortForRendering(transform); + + SetMaterialDirty(); + } + + protected override void UpdateMaterial() + { + // Clear modified materials. + for (var i = 0; i < _modifiedMaterials.Count; i++) { - m_ShouldRecalculateStencil = false; - - if (maskable) - { - var sortOverrideCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform); - m_StencilValue = MaskUtilities.GetStencilDepth(transform, sortOverrideCanvas) + index; - } - else - { - m_StencilValue = 0; - } + StencilMaterial.Remove(_modifiedMaterials[i]); + DestroyImmediate(_modifiedMaterials[i]); + _modifiedMaterials[i] = null; } - var component = GetComponent(); - if (m_StencilValue <= 0 || (component != null && component.IsActive())) return baseMat; + _modifiedMaterials.Clear(); - var stencilId = (1 << m_StencilValue) - 1; - var maskMaterial = StencilMaterial.Add(baseMat, stencilId, StencilOp.Keep, CompareFunction.Equal, ColorWriteMask.All, stencilId, 0); - StencilMaterial.Remove(_maskMaterials[index]); - _maskMaterials[index] = maskMaterial; - baseMat = _maskMaterials[index]; + // Recalculate stencil value. + if (m_ShouldRecalculateStencil) + { + var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform); + m_StencilValue = maskable ? MaskUtilities.GetStencilDepth(transform, rootCanvas) : 0; + m_ShouldRecalculateStencil = false; + } - return baseMat; + // No mesh to render. + if (activeMeshIndices == 0 || !isActiveAndEnabled || particles.Count == 0) + { + _activeMeshIndices = 0; + canvasRenderer.Clear(); + return; + } + + // + var materialCount = Mathf.Max(8, activeMeshIndices.BitCount()); + canvasRenderer.materialCount = materialCount; + var j = 0; + for (var i = 0; i < particles.Count; i++) + { + if (materialCount <= j) break; + var ps = particles[i]; + if (!ps) continue; + + var r = ps.GetComponent(); + r.GetSharedMaterials(s_TempMaterials); + + // Main + var bit = 1 << (i * 2); + if (0 < (activeMeshIndices & bit) && 0 < s_TempMaterials.Count) + { + var mat = GetModifiedMaterial(s_TempMaterials[0], ps.GetTextureForSprite()); + canvasRenderer.SetMaterial(mat, j++); + } + + // Trails + if (materialCount <= j) break; + bit <<= 1; + if (0 < (activeMeshIndices & bit) && 1 < s_TempMaterials.Count) + { + var mat = GetModifiedMaterial(s_TempMaterials[1], null); + canvasRenderer.SetMaterial(mat, j++); + } + } + } + + private Material GetModifiedMaterial(Material baseMaterial, Texture2D texture) + { + if (0 < m_StencilValue) + { + baseMaterial = StencilMaterial.Add(baseMaterial, (1 << m_StencilValue) - 1, StencilOp.Keep, CompareFunction.Equal, ColorWriteMask.All, (1 << m_StencilValue) - 1, 0); + _modifiedMaterials.Add(baseMaterial); + } + + if (texture == null && m_AnimatableProperties.Length == 0) return baseMaterial; + + baseMaterial = new Material(baseMaterial); + _modifiedMaterials.Add(baseMaterial); + if (texture) + baseMaterial.mainTexture = texture; + + return baseMaterial; } /// @@ -201,25 +203,19 @@ namespace Coffee.UIExtensions /// protected override void OnEnable() { - UpdateVersionIfNeeded(); + InitializeIfNeeded(); + _cachedPosition = transform.localPosition; + _activeMeshIndices = 0; + + UIParticleUpdater.Register(this); + particles.Exec(p => p.GetComponent().enabled = false); _tracker.Add(this, rectTransform, DrivenTransformProperties.Scale); - // Initialize. - _renderer = cachedParticleSystem ? cachedParticleSystem.GetComponent() : null; - if (_renderer != null) - _renderer.enabled = false; - - CheckMaterials(); - // Create objects. _bakedMesh = new Mesh(); _bakedMesh.MarkDynamic(); - MeshHelper.Register(); - BakingCamera.Register(); - UIParticleUpdater.Register(this); - base.OnEnable(); } @@ -228,25 +224,14 @@ namespace Coffee.UIExtensions /// protected override void OnDisable() { + UIParticleUpdater.Unregister(this); + particles.Exec(p => p.GetComponent().enabled = true); _tracker.Clear(); // Destroy object. DestroyImmediate(_bakedMesh); _bakedMesh = null; - MeshHelper.Unregister(); - BakingCamera.Unregister(); - UIParticleUpdater.Unregister(this); - - CheckMaterials(); - - // Remove mask materials. - for (var i = 0; i < _maskMaterials.Length; i++) - { - StencilMaterial.Remove(_maskMaterials[i]); - _maskMaterials[i] = null; - } - base.OnDisable(); } @@ -271,75 +256,28 @@ namespace Coffee.UIExtensions { } - - //################################ - // Private Members. - //################################ - private static bool HasMaterialChanged(Material material, ref int current) + private void InitializeIfNeeded() { - var old = current; - current = material ? material.GetInstanceID() : 0; - return current != old; - } + if (0 < particles.Count) return; - internal void UpdateTrailParticle() - { - // Should have a UIParticle for trail particle? - if (isActiveAndEnabled && isValid && !isTrailParticle && isSpritesModeAndHasTrail) + if (m_IsTrail + || transform.parent && transform.parent.GetComponentInParent()) { - if (!m_TrailParticle) - { - // Create new UIParticle for trail particle - m_TrailParticle = new GameObject("[UIParticle] Trail").AddComponent(); - var trans = m_TrailParticle.transform; - trans.SetPositionAndRotation(Vector3.zero, Quaternion.identity); - trans.localScale = Vector3.one; - trans.SetParent(transform, false); - - m_TrailParticle._renderer = _renderer; - m_TrailParticle.m_ParticleSystem = m_ParticleSystem; - m_TrailParticle.m_IsTrail = true; - } - - m_TrailParticle.gameObject.hideFlags = HideFlags.DontSave; - } - else if (m_TrailParticle) - { - // Destroy a UIParticle for trail particle. -#if UNITY_EDITOR - if (!Application.isPlaying) - DestroyImmediate(m_TrailParticle.gameObject); + gameObject.SetActive(false); + if (Application.isPlaying) + Destroy(gameObject); else -#endif - Destroy(m_TrailParticle.gameObject); - - m_TrailParticle = null; + DestroyImmediate(gameObject); + return; } - } - internal void CheckMaterials() - { - if (!_renderer) return; - - var matChanged = HasMaterialChanged(_renderer.sharedMaterial, ref _cachedSharedMaterialId); - var matChanged2 = HasMaterialChanged(_renderer.trailMaterial, ref _cachedTrailMaterialId); - var modeChanged = _cachedSpritesModeAndHasTrail != isSpritesModeAndHasTrail; - _cachedSpritesModeAndHasTrail = isSpritesModeAndHasTrail; - - if (matChanged || matChanged2 || modeChanged) - SetMaterialDirty(); - } - - private void UpdateVersionIfNeeded() - { - if (Mathf.Approximately(m_Scale, 0)) return; - - var parent = GetComponentInParent(); - if (m_IgnoreParent || !parent) - scale3D = m_Scale * transform.localScale; + // refresh. +#if UNITY_EDITOR + if (!Application.isPlaying) + UnityEditor.EditorApplication.delayCall += RefreshParticles; else - scale3D = transform.localScale; - m_Scale = 0; +#endif + RefreshParticles(); } } } diff --git a/Scripts/UIParticleUpdater.cs b/Scripts/UIParticleUpdater.cs index e262bf9..f14803a 100755 --- a/Scripts/UIParticleUpdater.cs +++ b/Scripts/UIParticleUpdater.cs @@ -9,17 +9,25 @@ namespace Coffee.UIExtensions { static readonly List s_ActiveParticles = new List(); static MaterialPropertyBlock s_Mpb; + static ParticleSystem.Particle[] s_Particles = new ParticleSystem.Particle[2048]; + public static void Register(UIParticle particle) { if (!particle) return; s_ActiveParticles.Add(particle); + + MeshHelper.Register(); + BakingCamera.Register(); } public static void Unregister(UIParticle particle) { if (!particle) return; s_ActiveParticles.Remove(particle); + + MeshHelper.Unregister(); + BakingCamera.Unregister(); } #if UNITY_EDITOR @@ -55,22 +63,8 @@ namespace Coffee.UIExtensions ModifyScale(particle); Profiler.EndSample(); - if (!particle.isValid) return; - - Profiler.BeginSample("Update trail particle"); - particle.UpdateTrailParticle(); - Profiler.EndSample(); - - Profiler.BeginSample("Check materials"); - particle.CheckMaterials(); - Profiler.EndSample(); - - Profiler.BeginSample("Make matrix"); - var scaledMatrix = GetScaledMatrix(particle); - Profiler.EndSample(); - Profiler.BeginSample("Bake mesh"); - BakeMesh(particle, scaledMatrix); + BakeMesh(particle); Profiler.EndSample(); if (QualitySettings.activeColorSpace == ColorSpace.Linear) @@ -80,78 +74,37 @@ namespace Coffee.UIExtensions Profiler.EndSample(); } - Profiler.BeginSample("Set mesh and texture to CanvasRenderer"); - UpdateMeshAndTexture(particle); + Profiler.BeginSample("Set mesh to CanvasRenderer"); + particle.canvasRenderer.SetMesh(particle.bakedMesh); Profiler.EndSample(); Profiler.BeginSample("Update Animatable Material Properties"); - UpdateAnimatableMaterialProperties(particle); + // UpdateAnimatableMaterialProperties(particle); Profiler.EndSample(); } private static void ModifyScale(UIParticle particle) { - if (particle.isTrailParticle) return; - - var modifiedScale = particle.m_Scale3D; + if (!particle.ignoreCanvasScaler || !particle.canvas) return; // Ignore Canvas scaling. - if (particle.ignoreCanvasScaler && particle.canvas) - { - var s = particle.canvas.rootCanvas.transform.localScale; - var sInv = new Vector3( - Mathf.Approximately(s.x, 0) ? 1 : 1 / s.x, - Mathf.Approximately(s.y, 0) ? 1 : 1 / s.y, - Mathf.Approximately(s.z, 0) ? 1 : 1 / s.z); - modifiedScale = Vector3.Scale(modifiedScale, sInv); - } + var s = particle.canvas.rootCanvas.transform.localScale; + var modifiedScale = new Vector3( + Mathf.Approximately(s.x, 0) ? 1 : 1 / s.x, + Mathf.Approximately(s.y, 0) ? 1 : 1 / s.y, + Mathf.Approximately(s.z, 0) ? 1 : 1 / s.z); // Scale is already modified. - var tr = particle.transform; - if (Mathf.Approximately((tr.localScale - modifiedScale).sqrMagnitude, 0)) return; + var transform = particle.transform; + if (Mathf.Approximately((transform.localScale - modifiedScale).sqrMagnitude, 0)) return; - tr.localScale = modifiedScale; + transform.localScale = modifiedScale; } - private static void UpdateMeshAndTexture(UIParticle particle) - { - // Update mesh. - particle.canvasRenderer.SetMesh(particle.bakedMesh); - - // Non sprite mode: external texture is not used. - if (!particle.isSpritesMode || particle.isTrailParticle) - { - particle.canvasRenderer.SetTexture(null); - return; - } - - // Sprite mode: get sprite's texture. - Texture tex = null; - Profiler.BeginSample("Check TextureSheetAnimation module"); - var tsaModule = particle.cachedParticleSystem.textureSheetAnimation; - if (tsaModule.enabled && tsaModule.mode == ParticleSystemAnimationMode.Sprites) - { - var count = tsaModule.spriteCount; - for (var i = 0; i < count; i++) - { - var sprite = tsaModule.GetSprite(i); - if (!sprite) continue; - - tex = sprite.GetActualTexture(); - break; - } - } - - Profiler.EndSample(); - - particle.canvasRenderer.SetTexture(tex); - } - - private static Matrix4x4 GetScaledMatrix(UIParticle particle) + private static Matrix4x4 GetScaledMatrix(ParticleSystem particle) { var transform = particle.transform; - var tr = particle.isTrailParticle ? transform.parent : transform; - var main = particle.mainModule; + var main = particle.main; var space = main.simulationSpace; if (space == ParticleSystemSimulationSpace.Custom && !main.customSimulationSpace) space = ParticleSystemSimulationSpace.Local; @@ -159,11 +112,8 @@ namespace Coffee.UIExtensions switch (space) { case ParticleSystemSimulationSpace.Local: - var canvasTr = particle.canvas.rootCanvas.transform; - return Matrix4x4.Rotate(tr.localRotation).inverse - * Matrix4x4.Rotate(canvasTr.localRotation).inverse - * Matrix4x4.Scale(tr.localScale).inverse - * Matrix4x4.Scale(canvasTr.localScale).inverse; + return Matrix4x4.Rotate(transform.rotation).inverse + * Matrix4x4.Scale(transform.lossyScale).inverse; case ParticleSystemSimulationSpace.World: return transform.worldToLocalMatrix; case ParticleSystemSimulationSpace.Custom: @@ -175,51 +125,118 @@ namespace Coffee.UIExtensions } } - private static void BakeMesh(UIParticle particle, Matrix4x4 scaledMatrix) + private static void BakeMesh(UIParticle particle) { // Clear mesh before bake. MeshHelper.Clear(); - particle.bakedMesh.Clear(); + particle.bakedMesh.Clear(false); - // No particle to render. - if (particle.cachedParticleSystem.particleCount <= 0) return; + // if (!particle.isValid) return; // Get camera for baking mesh. - var cam = BakingCamera.GetCamera(particle.canvas); - var renderer = particle.cachedRenderer; - var trail = particle.trailModule; + var camera = BakingCamera.GetCamera(particle.canvas); + var root = particle.transform; + var rootMatrix = Matrix4x4.Rotate(root.rotation).inverse + * Matrix4x4.Scale(root.lossyScale).inverse; + var scaleMatrix = particle.ignoreCanvasScaler + ? Matrix4x4.Scale(particle.canvas.rootCanvas.transform.localScale.x * particle.scale * Vector3.one) + : Matrix4x4.Scale(particle.scale * Vector3.one); - Profiler.BeginSample("Bake mesh"); - if (!particle.isSpritesMode) // Non sprite mode: bake main particle and trail particle. - { - if (CanBakeMesh(renderer)) - renderer.BakeMesh(MeshHelper.GetTemporaryMesh(), cam, true); + // Cache position + var position = particle.transform.position; + var diff = (position - particle.cachedPosition) * (1 - 1 / particle.scale); + particle.cachedPosition = position; - if (trail.enabled) - renderer.BakeTrailsMesh(MeshHelper.GetTemporaryMesh(), cam, true); - } - else if (particle.isTrailParticle) // Sprite mode (trail): bake trail particle. + for (var i = 0; i < particle.particles.Count; i++) { - if (trail.enabled) - renderer.BakeTrailsMesh(MeshHelper.GetTemporaryMesh(), cam, true); - } - else // Sprite mode (main): bake main particle. - { - if (CanBakeMesh(renderer)) - renderer.BakeMesh(MeshHelper.GetTemporaryMesh(), cam, true); + // No particle to render. + var currentPs = particle.particles[i]; + if (!currentPs || !currentPs.IsAlive() || currentPs.particleCount == 0) continue; + + // Calc matrix. + var matrix = rootMatrix; + if (currentPs.transform != root) + { + if (currentPs.main.simulationSpace == ParticleSystemSimulationSpace.Local) + { + var relativePos = root.InverseTransformPoint(currentPs.transform.position); + matrix = Matrix4x4.Translate(relativePos) * matrix; + } + else + { + matrix = matrix * Matrix4x4.Translate(-root.position); + } + } + else + { + matrix = GetScaledMatrix(currentPs); + } + + // Set transform + MeshHelper.SetTransform(scaleMatrix * matrix); + + // Extra world simulation. + if (currentPs.main.simulationSpace == ParticleSystemSimulationSpace.World && 0 < diff.sqrMagnitude) + { + var count = currentPs.particleCount; + if (s_Particles.Length < count) + { + var size = Mathf.NextPowerOfTwo(count); + s_Particles = new ParticleSystem.Particle[size]; + } + + currentPs.GetParticles(s_Particles); + for (var j = 0; j < count; j++) + { + var p = s_Particles[j]; + p.position += diff; + s_Particles[j] = p; + } + + currentPs.SetParticles(s_Particles, count); + } + + // Bake main particles. + var r = currentPs.GetComponent(); + if (CanBakeMesh(r)) + { + var m = MeshHelper.GetTemporaryMesh(i * 2); + r.BakeMesh(m, camera, true); + + if (m.vertexCount == 0) + MeshHelper.DiscardTemporaryMesh(); + } + + // Bake trails particles. + if (currentPs.trails.enabled) + { + var m = MeshHelper.GetTemporaryMesh(i * 2 + 1); + try + { + r.BakeTrailsMesh(m, camera, true); + + if (m.vertexCount == 0) + MeshHelper.DiscardTemporaryMesh(); + } + catch + { + MeshHelper.DiscardTemporaryMesh(); + } + } } - Profiler.EndSample(); - Profiler.BeginSample("Apply matrix to position"); - MeshHelper.CombineMesh(particle.bakedMesh, scaledMatrix); - Profiler.EndSample(); + // Set active indices. + particle.activeMeshIndices = MeshHelper.activeMeshIndices; + + // Combine + MeshHelper.CombineMesh(particle.bakedMesh); } private static bool CanBakeMesh(ParticleSystemRenderer renderer) { // #69: Editor crashes when mesh is set to null when `ParticleSystem.RenderMode = Mesh` - if (renderer.renderMode == ParticleSystemRenderMode.Mesh && !renderer.mesh) return false; + if (renderer.renderMode == ParticleSystemRenderMode.Mesh && renderer.mesh == null) return false; // #61: When `ParticleSystem.RenderMode = None`, an error occurs if (renderer.renderMode == ParticleSystemRenderMode.None) return false; @@ -230,7 +247,7 @@ namespace Coffee.UIExtensions /// /// Copy the value from MaterialPropertyBlock to CanvasRenderer /// - private static void UpdateAnimatableMaterialProperties(UIParticle particle) + private static void UpdateAnimatableMaterialProperties(UIParticle particle, ParticleSystemRenderer renderer) { #if UNITY_EDITOR if (!Application.isPlaying) return; @@ -244,7 +261,7 @@ namespace Coffee.UIExtensions // #41: Copy the value from MaterialPropertyBlock to CanvasRenderer if (s_Mpb == null) s_Mpb = new MaterialPropertyBlock(); - particle.cachedRenderer.GetPropertyBlock(s_Mpb); + renderer.GetPropertyBlock(s_Mpb); foreach (var ap in particle.m_AnimatableProperties) { ap.UpdateMaterialProperties(mat, s_Mpb); diff --git a/Scripts/Utils.cs b/Scripts/Utils.cs new file mode 100644 index 0000000..6713544 --- /dev/null +++ b/Scripts/Utils.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; + +namespace Coffee.UIExtensions +{ + internal static class SpriteExtensions + { +#if UNITY_EDITOR + private static Type tSpriteEditorExtension = Type.GetType("UnityEditor.Experimental.U2D.SpriteEditorExtension, UnityEditor") + ?? Type.GetType("UnityEditor.U2D.SpriteEditorExtension, UnityEditor"); + + private static MethodInfo miGetActiveAtlasTexture = tSpriteEditorExtension + .GetMethod("GetActiveAtlasTexture", BindingFlags.Static | BindingFlags.NonPublic); + + public static Texture2D GetActualTexture(this Sprite self) + { + if (!self) return null; + + if (Application.isPlaying) return self.texture; + var ret = miGetActiveAtlasTexture.Invoke(null, new[] {self}) as Texture2D; + return ret ? ret : self.texture; + } +#else + internal static Texture2D GetActualTexture(this Sprite self) + { + return self ? self.texture : null; + } +#endif + } + + internal static class UintExtensions + { + public static int BitCount(this uint self) + { + self = (self & 0x55555555) + ((self >> 1) & 0x55555555); + self = (self & 0x33333333) + ((self >> 2) & 0x33333333); + self = (self & 0x0F0F0F0F) + ((self >> 4) & 0x0F0F0F0F); + self = (self & 0x00FF00FF) + ((self >> 8) & 0x00FF00FF); + return (int) ((self & 0x0000ffff) + (self >> 16)); + } + } + + internal static class ParticleSystemExtensions + { + public static void SortForRendering(this List self, Transform transform) + { + self.Sort((a, b) => + { + var tr = transform; + var ra = a.GetComponent(); + var rb = b.GetComponent(); + + if (!Mathf.Approximately(ra.sortingFudge, rb.sortingFudge)) + return ra.sortingFudge < rb.sortingFudge ? 1 : -1; + + var pa = tr.InverseTransformPoint(a.transform.position).z; + var pb = tr.InverseTransformPoint(b.transform.position).z; + + return Mathf.Approximately(pa, pb) + ? 0 + : pa < pb + ? 1 + : -1; + }); + } + + public static Texture2D GetTextureForSprite(this ParticleSystem self) + { + if (!self) return null; + + // Get sprite's texture. + var tsaModule = self.textureSheetAnimation; + if (!tsaModule.enabled || tsaModule.mode != ParticleSystemAnimationMode.Sprites) return null; + + for (var i = 0; i < tsaModule.spriteCount; i++) + { + var sprite = tsaModule.GetSprite(i); + if (!sprite) continue; + + return sprite.GetActualTexture(); + } + + return null; + } + + public static void Exec(this List self, Action action) + { + self.RemoveAll(p => !p); + self.ForEach(action); + } + } + +} diff --git a/Scripts/SpriteExtensions.cs.meta b/Scripts/Utils.cs.meta similarity index 100% rename from Scripts/SpriteExtensions.cs.meta rename to Scripts/Utils.cs.meta diff --git a/package.json b/package.json index 257ee8f..ca188cd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "com.coffee.ui-particle", "displayName": "UI Particle", "description": "This plugin provide a component to render particle effect for uGUI.\nThe particle rendering is maskable and sortable, without Camera, RenderTexture or Canvas.", - "version": "3.0.0-preview.18", + "version": "3.0.0-preview.19", "unity": "2018.2", "license": "MIT", "repository": {