using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UI;
namespace Coffee.UIExtensions
{
///
/// Render maskable and sortable particle effect ,without Camera, RenderTexture or Canvas.
///
[ExecuteInEditMode]
[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("Particle effect scale")] [SerializeField]
float m_Scale = 0;
[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;
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;
//################################
// 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; }
}
public bool ignoreCanvasScaler
{
get { return m_IgnoreCanvasScaler; }
set { m_IgnoreCanvasScaler = value; }
}
///
/// Particle effect scale.
///
public float scale
{
get { return m_Scale3D.x; }
set { m_Scale3D.Set(value, value, 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
{
get { return _bakedMesh; }
}
protected override void UpdateMaterial()
{
if (!_renderer) return;
if (!isSpritesMode) // Non sprite mode: canvas renderer has main and trail materials.
{
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);
}
}
private Material GetModifiedMaterial(Material baseMaterial, int index)
{
if (!baseMaterial || 1 < index || !isActiveAndEnabled) return null;
var hasAnimatableProperties = 0 < m_AnimatableProperties.Length && index == 0;
if (hasAnimatableProperties || isTrailParticle)
baseMaterial = new Material(baseMaterial);
var baseMat = baseMaterial;
if (m_ShouldRecalculateStencil)
{
m_ShouldRecalculateStencil = false;
if (maskable)
{
var sortOverrideCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
m_StencilValue = MaskUtilities.GetStencilDepth(transform, sortOverrideCanvas) + index;
}
else
{
m_StencilValue = 0;
}
}
var component = GetComponent();
if (m_StencilValue <= 0 || (component != null && component.IsActive())) return baseMat;
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];
return baseMat;
}
///
/// This function is called when the object becomes enabled and active.
///
protected override void OnEnable()
{
UpdateVersionIfNeeded();
_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();
}
///
/// This function is called when the behaviour becomes disabled.
///
protected override void OnDisable()
{
_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();
}
///
/// Call to update the geometry of the Graphic onto the CanvasRenderer.
///
protected override void UpdateGeometry()
{
}
///
/// This function is called when the parent property of the transform of the GameObject has changed.
///
protected override void OnTransformParentChanged()
{
}
///
/// Callback for when properties have been changed by animation.
///
protected override void OnDidApplyAnimationProperties()
{
}
//################################
// Private Members.
//################################
private static bool HasMaterialChanged(Material material, ref int current)
{
var old = current;
current = material ? material.GetInstanceID() : 0;
return current != old;
}
internal void UpdateTrailParticle()
{
// Should have a UIParticle for trail particle?
if (isActiveAndEnabled && isValid && !isTrailParticle && isSpritesModeAndHasTrail)
{
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);
else
#endif
Destroy(m_TrailParticle.gameObject);
m_TrailParticle = null;
}
}
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;
else
scale3D = transform.localScale;
m_Scale = 0;
}
}
}