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](436c5e47f7))
* fix local simulation ([7add9de](7add9defb7))

### Features

* add menu to create UIParticle ([2fa1843](2fa18431f0))
* add play/pause/stop api ([f09a386](f09a386bc5))
* support for changing rendering orders ([745d4a5](745d4a5988))
* Support for child ParticleSystem rendering ([4ee90be](4ee90be17c))
* UIParticle for trail is no longer needed ([466e43c](466e43cf93))

### BREAKING CHANGES

* The child UIParticle is no longer needed.
pull/120/head
semantic-release-bot 2020-08-28 05:38:13 +00:00
parent eccb3ecb5e
commit 0d4a5875d1
11 changed files with 500 additions and 495 deletions

View File

@ -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)

View File

@ -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;

View File

@ -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<ParticleSystem> s_ParticleSystems = new List<ParticleSystem>();
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<string> s_MaskablePropertyNames = new List<string>
@ -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();
}
};
}
/// <summary>
@ -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<ParticleSystemRenderer>();
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<UIParticle>() == null))
_ro.DoLayoutList();
// Does the shader support UI masks?
if (current.maskable && current.GetComponentInParent<Mask>())
{
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<UIParticle>()))
if (!mat || !mat.shader) continue;
var shader = mat.shader;
foreach (var propName in s_MaskablePropertyNames)
{
p.gameObject.AddComponent<UIParticle>();
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;
}
}
}

View File

@ -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<Image>());
// Add UIParticle.
var uiParticle = ui.AddComponent<UIParticle>();
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<UIParticle>();
// 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<ParticleSystemRenderer>();
var defaultMat = AssetDatabase.GetBuiltinExtraResource<Material>("Default-Particle.mat");
renderer.material = defaultMat ? defaultMat : renderer.material;
// Set to hierarchy mode
var particleSystem = ps.GetComponent<ParticleSystem>();
var main = particleSystem.main;
main.scalingMode = ParticleSystemScalingMode.Hierarchy;
// Add UIParticle.
var uiParticle = ps.AddComponent<UIParticle>();
uiParticle.ignoreCanvasScaler = true;
uiParticle.scale = 10;
// Refresh particles.
uiParticle.RefreshParticles();
}
}
}

View File

@ -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<Color32> s_Colors = new List<Color32>();
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();

View File

@ -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
}
}

View File

@ -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
{
/// <summary>
/// Render maskable and sortable particle effect ,without Camera, RenderTexture or Canvas.
/// </summary>
[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<ParticleSystem> m_Particles = new List<ParticleSystem>();
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<Material> _modifiedMaterials = new List<Material>();
private uint _activeMeshIndices;
private Vector3 _cachedPosition;
private static readonly List<Material> s_TempMaterials = new List<Material>(2);
//################################
// Public/Protected Members.
//################################
/// <summary>
/// Should this graphic be considered a target for raycasting?
/// </summary>
public override bool raycastTarget
{
get { return false; }
set { base.raycastTarget = value; }
}
/// <summary>
/// Cached ParticleSystem.
/// </summary>
public ParticleSystem cachedParticleSystem
{
get { return m_ParticleSystem ? m_ParticleSystem : (m_ParticleSystem = GetComponent<ParticleSystem>()); }
}
/// <summary>
/// Cached ParticleSystem.
/// </summary>
internal ParticleSystemRenderer cachedRenderer
{
get { return _renderer; }
set { }
}
public bool ignoreCanvasScaler
@ -84,116 +59,143 @@ namespace Coffee.UIExtensions
/// </summary>
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); }
}
/// <summary>
/// Particle effect scale.
/// </summary>
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<ParticleSystem> particles
{
if (!_renderer) return;
get { return m_Particles; }
}
if (!isSpritesMode) // Non sprite mode: canvas renderer has main and trail materials.
public IEnumerable<Material> 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<ParticleSystemRenderer>().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<Mask>();
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<ParticleSystemRenderer>();
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;
}
/// <summary>
@ -201,25 +203,19 @@ namespace Coffee.UIExtensions
/// </summary>
protected override void OnEnable()
{
UpdateVersionIfNeeded();
InitializeIfNeeded();
_cachedPosition = transform.localPosition;
_activeMeshIndices = 0;
UIParticleUpdater.Register(this);
particles.Exec(p => p.GetComponent<ParticleSystemRenderer>().enabled = false);
_tracker.Add(this, rectTransform, DrivenTransformProperties.Scale);
// Initialize.
_renderer = cachedParticleSystem ? cachedParticleSystem.GetComponent<ParticleSystemRenderer>() : 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
/// </summary>
protected override void OnDisable()
{
UIParticleUpdater.Unregister(this);
particles.Exec(p => p.GetComponent<ParticleSystemRenderer>().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<UIParticle>())
{
if (!m_TrailParticle)
{
// Create new UIParticle for trail particle
m_TrailParticle = new GameObject("[UIParticle] Trail").AddComponent<UIParticle>();
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<UIParticle>();
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();
}
}
}

View File

@ -9,17 +9,25 @@ namespace Coffee.UIExtensions
{
static readonly List<UIParticle> s_ActiveParticles = new List<UIParticle>();
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<ParticleSystemRenderer>();
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
/// <summary>
/// Copy the value from MaterialPropertyBlock to CanvasRenderer
/// </summary>
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);

95
Scripts/Utils.cs Normal file
View File

@ -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<ParticleSystem> self, Transform transform)
{
self.Sort((a, b) =>
{
var tr = transform;
var ra = a.GetComponent<ParticleSystemRenderer>();
var rb = b.GetComponent<ParticleSystemRenderer>();
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<ParticleSystem> self, Action<ParticleSystem> action)
{
self.RemoveAll(p => !p);
self.ForEach(action);
}
}
}

View File

@ -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": {