2020-08-28 13:38:13 +08:00
using System.Collections.Generic ;
2020-09-02 13:15:30 +08:00
using System.Linq ;
2020-08-28 13:38:13 +08:00
using System.Runtime.CompilerServices ;
2020-09-28 20:35:40 +08:00
using Coffee.UIParticleExtensions ;
2018-06-22 18:48:14 +08:00
using UnityEngine ;
2020-08-20 03:42:16 +08:00
using UnityEngine.Rendering ;
2020-08-28 13:38:13 +08:00
using UnityEngine.Serialization ;
2018-06-22 18:48:14 +08:00
using UnityEngine.UI ;
2020-08-28 13:38:13 +08:00
[assembly: InternalsVisibleTo("Coffee.UIParticle.Editor")]
2018-06-22 18:48:14 +08:00
namespace Coffee.UIExtensions
{
2020-02-12 20:38:06 +08:00
/// <summary>
/// Render maskable and sortable particle effect ,without Camera, RenderTexture or Canvas.
/// </summary>
[ExecuteInEditMode]
2020-08-28 13:38:13 +08:00
[RequireComponent(typeof(RectTransform))]
2020-08-11 23:09:55 +08:00
[RequireComponent(typeof(CanvasRenderer))]
2020-08-29 11:10:31 +08:00
public class UIParticle : MaskableGraphic
#if UNITY_EDITOR
, ISerializationCallbackReceiver
# endif
2020-02-12 20:38:06 +08:00
{
2020-09-01 12:50:54 +08:00
[HideInInspector] [ SerializeField ] internal bool m_IsTrail = false ;
2020-02-12 20:38:06 +08:00
2020-08-28 13:38:13 +08:00
[Tooltip("Ignore canvas scaler")] [ SerializeField ] [ FormerlySerializedAs ( "m_IgnoreParent" ) ]
bool m_IgnoreCanvasScaler = true ;
2020-08-12 22:19:08 +08:00
2020-08-20 03:42:16 +08:00
[Tooltip("Particle effect scale")] [ SerializeField ]
2020-08-28 13:38:13 +08:00
float m_Scale = 100 ;
2020-02-12 20:38:06 +08:00
2020-10-04 22:26:53 +08:00
[Tooltip("Particle effect scale")] [ SerializeField ]
private Vector3 m_Scale3D ;
2020-08-20 03:42:16 +08:00
[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 ] ;
2020-02-12 20:38:06 +08:00
2020-08-28 13:38:13 +08:00
[Tooltip("Particles")] [ SerializeField ]
private List < ParticleSystem > m_Particles = new List < ParticleSystem > ( ) ;
2020-02-12 20:38:06 +08:00
2020-09-01 12:50:54 +08:00
private bool _shouldBeRemoved ;
2020-08-20 03:42:16 +08:00
private DrivenRectTransformTracker _tracker ;
private Mesh _bakedMesh ;
2020-08-28 13:38:13 +08:00
private readonly List < Material > _modifiedMaterials = new List < Material > ( ) ;
2020-08-29 02:55:46 +08:00
private readonly List < Material > _maskMaterials = new List < Material > ( ) ;
2020-09-02 01:39:05 +08:00
private long _activeMeshIndices ;
2020-08-28 13:38:13 +08:00
private Vector3 _cachedPosition ;
private static readonly List < Material > s_TempMaterials = new List < Material > ( 2 ) ;
2020-08-29 02:55:46 +08:00
private static MaterialPropertyBlock s_Mpb ;
2020-08-28 13:38:13 +08:00
2020-08-20 03:42:16 +08:00
/// <summary>
/// Should this graphic be considered a target for raycasting?
/// </summary>
public override bool raycastTarget
2020-02-12 20:38:06 +08:00
{
2020-08-20 03:42:16 +08:00
get { return false ; }
2020-08-28 13:38:13 +08:00
set { }
2020-08-20 03:42:16 +08:00
}
2020-02-12 20:38:06 +08:00
2020-08-20 03:42:16 +08:00
public bool ignoreCanvasScaler
{
get { return m_IgnoreCanvasScaler ; }
2020-09-01 12:50:54 +08:00
set
{
// if (m_IgnoreCanvasScaler == value) return;
m_IgnoreCanvasScaler = value ;
_tracker . Clear ( ) ;
if ( isActiveAndEnabled & & m_IgnoreCanvasScaler )
_tracker . Add ( this , rectTransform , DrivenTransformProperties . Scale ) ;
}
2020-02-12 20:38:06 +08:00
}
/// <summary>
/// Particle effect scale.
/// </summary>
2020-08-12 22:19:08 +08:00
public float scale
{
2020-10-04 22:26:53 +08:00
get { return m_Scale3D . x ; }
set
{
m_Scale = Mathf . Max ( 0.001f , value ) ;
m_Scale3D = new Vector3 ( m_Scale , m_Scale , m_Scale ) ;
}
}
/// <summary>
/// Particle effect scale.
/// </summary>
public Vector3 scale3D
{
get { return m_Scale3D ; }
set
{
if ( m_Scale3D = = value ) return ;
m_Scale3D . x = Mathf . Max ( 0.001f , value . x ) ;
m_Scale3D . y = Mathf . Max ( 0.001f , value . y ) ;
m_Scale3D . z = Mathf . Max ( 0.001f , value . z ) ;
}
2020-08-12 22:19:08 +08:00
}
2020-02-12 20:38:06 +08:00
2020-08-28 13:38:13 +08:00
internal Mesh bakedMesh
2020-02-12 20:38:06 +08:00
{
2020-08-28 13:38:13 +08:00
get { return _bakedMesh ; }
2020-02-12 20:38:06 +08:00
}
2020-08-28 13:38:13 +08:00
public List < ParticleSystem > particles
2020-02-12 20:38:06 +08:00
{
2020-08-28 13:38:13 +08:00
get { return m_Particles ; }
2020-02-12 20:38:06 +08:00
}
2020-08-28 13:38:13 +08:00
public IEnumerable < Material > materials
2020-08-12 22:19:08 +08:00
{
2020-08-28 13:38:13 +08:00
get { return _modifiedMaterials ; }
2020-08-12 22:19:08 +08:00
}
2020-02-12 20:38:06 +08:00
2020-09-02 01:39:05 +08:00
internal long activeMeshIndices
2020-08-12 22:19:08 +08:00
{
2020-08-28 13:38:13 +08:00
get { return _activeMeshIndices ; }
set
{
if ( _activeMeshIndices = = value ) return ;
_activeMeshIndices = value ;
UpdateMaterial ( ) ;
}
2020-08-12 22:19:08 +08:00
}
2020-02-12 20:38:06 +08:00
2020-08-28 13:38:13 +08:00
internal Vector3 cachedPosition
2020-02-12 20:38:06 +08:00
{
2020-08-28 13:38:13 +08:00
get { return _cachedPosition ; }
set { _cachedPosition = value ; }
2020-08-20 03:42:16 +08:00
}
2020-02-12 20:38:06 +08:00
2020-08-28 13:38:13 +08:00
public void Play ( )
2020-08-20 03:42:16 +08:00
{
2020-08-28 13:38:13 +08:00
particles . Exec ( p = > p . Play ( ) ) ;
2020-02-12 20:38:06 +08:00
}
2020-08-28 13:38:13 +08:00
public void Pause ( )
2020-08-20 03:42:16 +08:00
{
2020-08-28 13:38:13 +08:00
particles . Exec ( p = > p . Pause ( ) ) ;
2020-08-20 03:42:16 +08:00
}
2020-08-28 13:38:13 +08:00
public void Stop ( )
2020-08-20 03:42:16 +08:00
{
2020-08-28 13:38:13 +08:00
particles . Exec ( p = > p . Stop ( ) ) ;
2020-08-20 03:42:16 +08:00
}
2020-09-02 13:15:30 +08:00
public void SetParticleSystemInstance ( GameObject instance )
{
SetParticleSystemInstance ( instance , true ) ;
}
2020-09-03 00:02:59 +08:00
public void SetParticleSystemInstance ( GameObject instance , bool destroyOldParticles )
2020-09-02 13:15:30 +08:00
{
if ( ! instance ) return ;
foreach ( Transform child in transform )
{
var go = child . gameObject ;
go . SetActive ( false ) ;
2020-09-03 00:02:59 +08:00
if ( ! destroyOldParticles ) continue ;
2020-09-02 13:15:30 +08:00
#if UNITY_EDITOR
if ( ! Application . isPlaying )
DestroyImmediate ( go ) ;
else
# endif
Destroy ( go ) ;
}
var tr = instance . transform ;
tr . SetParent ( transform , false ) ;
tr . localPosition = Vector3 . zero ;
2020-09-03 00:02:59 +08:00
RefreshParticles ( instance ) ;
2020-09-02 13:15:30 +08:00
}
public void SetParticleSystemPrefab ( GameObject prefab )
{
if ( ! prefab ) return ;
SetParticleSystemInstance ( Instantiate ( prefab . gameObject ) , true ) ;
}
2020-08-28 13:38:13 +08:00
public void RefreshParticles ( )
2020-08-20 03:42:16 +08:00
{
2020-09-03 00:02:59 +08:00
RefreshParticles ( gameObject ) ;
}
public void RefreshParticles ( GameObject root )
{
if ( ! root ) return ;
root . GetComponentsInChildren ( particles ) ;
2020-08-28 13:38:13 +08:00
2020-08-28 15:50:13 +08:00
foreach ( var ps in particles )
{
var tsa = ps . textureSheetAnimation ;
if ( tsa . mode = = ParticleSystemAnimationMode . Sprites & & tsa . uvChannelMask = = ( UVChannelFlags ) 0 )
tsa . uvChannelMask = UVChannelFlags . UV0 ;
}
2020-08-28 13:38:13 +08:00
particles . Exec ( p = > p . GetComponent < ParticleSystemRenderer > ( ) . enabled = ! enabled ) ;
particles . SortForRendering ( transform ) ;
SetMaterialDirty ( ) ;
2020-08-20 03:42:16 +08:00
}
protected override void UpdateMaterial ( )
2020-02-12 20:38:06 +08:00
{
2020-08-29 02:55:46 +08:00
// Clear mask materials.
for ( var i = 0 ; i < _maskMaterials . Count ; i + + )
{
StencilMaterial . Remove ( _maskMaterials [ i ] ) ;
_maskMaterials [ i ] = null ;
}
_maskMaterials . Clear ( ) ;
2020-08-28 13:38:13 +08:00
// Clear modified materials.
for ( var i = 0 ; i < _modifiedMaterials . Count ; i + + )
2020-08-20 03:42:16 +08:00
{
2020-08-28 13:38:13 +08:00
DestroyImmediate ( _modifiedMaterials [ i ] ) ;
_modifiedMaterials [ i ] = null ;
2020-08-20 03:42:16 +08:00
}
2020-08-28 13:38:13 +08:00
_modifiedMaterials . Clear ( ) ;
// Recalculate stencil value.
if ( m_ShouldRecalculateStencil )
2020-08-20 03:42:16 +08:00
{
2020-08-28 13:38:13 +08:00
var rootCanvas = MaskUtilities . FindRootSortOverrideCanvas ( transform ) ;
m_StencilValue = maskable ? MaskUtilities . GetStencilDepth ( transform , rootCanvas ) : 0 ;
m_ShouldRecalculateStencil = false ;
2020-08-20 03:42:16 +08:00
}
2020-08-28 13:38:13 +08:00
// No mesh to render.
if ( activeMeshIndices = = 0 | | ! isActiveAndEnabled | | particles . Count = = 0 )
2020-02-12 20:38:06 +08:00
{
2020-08-28 13:38:13 +08:00
_activeMeshIndices = 0 ;
canvasRenderer . Clear ( ) ;
return ;
2020-02-12 20:38:06 +08:00
}
2020-08-12 22:19:08 +08:00
2020-08-28 13:38:13 +08:00
/ /
var materialCount = Mathf . Max ( 8 , activeMeshIndices . BitCount ( ) ) ;
canvasRenderer . materialCount = materialCount ;
var j = 0 ;
for ( var i = 0 ; i < particles . Count ; i + + )
2020-02-12 20:38:06 +08:00
{
2020-08-28 13:38:13 +08:00
if ( materialCount < = j ) break ;
var ps = particles [ i ] ;
if ( ! ps ) continue ;
2020-08-20 03:42:16 +08:00
2020-08-28 13:38:13 +08:00
var r = ps . GetComponent < ParticleSystemRenderer > ( ) ;
r . GetSharedMaterials ( s_TempMaterials ) ;
// Main
2020-09-02 01:39:05 +08:00
var bit = ( long ) 1 < < ( i * 2 ) ;
2020-08-28 13:38:13 +08:00
if ( 0 < ( activeMeshIndices & bit ) & & 0 < s_TempMaterials . Count )
2020-08-20 03:42:16 +08:00
{
2020-08-28 13:38:13 +08:00
var mat = GetModifiedMaterial ( s_TempMaterials [ 0 ] , ps . GetTextureForSprite ( ) ) ;
2020-08-29 02:55:46 +08:00
canvasRenderer . SetMaterial ( mat , j ) ;
UpdateMaterialProperties ( r , j ) ;
j + + ;
2020-08-20 03:42:16 +08:00
}
2020-08-28 13:38:13 +08:00
// Trails
if ( materialCount < = j ) break ;
bit < < = 1 ;
if ( 0 < ( activeMeshIndices & bit ) & & 1 < s_TempMaterials . Count )
2020-08-20 03:42:16 +08:00
{
2020-08-28 13:38:13 +08:00
var mat = GetModifiedMaterial ( s_TempMaterials [ 1 ] , null ) ;
canvasRenderer . SetMaterial ( mat , j + + ) ;
2020-08-20 03:42:16 +08:00
}
2020-02-12 20:38:06 +08:00
}
2020-08-28 13:38:13 +08:00
}
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 ) ;
2020-08-29 02:55:46 +08:00
_maskMaterials . Add ( baseMaterial ) ;
2020-08-28 13:38:13 +08:00
}
2020-08-12 22:19:08 +08:00
2020-08-28 13:38:13 +08:00
if ( texture = = null & & m_AnimatableProperties . Length = = 0 ) return baseMaterial ;
2020-08-20 03:42:16 +08:00
2020-08-28 13:38:13 +08:00
baseMaterial = new Material ( baseMaterial ) ;
_modifiedMaterials . Add ( baseMaterial ) ;
if ( texture )
baseMaterial . mainTexture = texture ;
2020-08-20 03:42:16 +08:00
2020-08-28 13:38:13 +08:00
return baseMaterial ;
2020-08-20 03:42:16 +08:00
}
2020-09-14 17:29:16 +08:00
internal void UpdateMaterialProperties ( )
{
if ( m_AnimatableProperties . Length = = 0 ) 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 = ( long ) 1 < < ( i * 2 ) ;
if ( 0 < ( activeMeshIndices & bit ) & & 0 < s_TempMaterials . Count )
{
UpdateMaterialProperties ( r , j ) ;
j + + ;
}
}
}
2020-08-29 02:55:46 +08:00
internal void UpdateMaterialProperties ( Renderer r , int index )
{
if ( m_AnimatableProperties . Length = = 0 | | canvasRenderer . materialCount < = index ) return ;
r . GetPropertyBlock ( s_Mpb ? ? ( s_Mpb = new MaterialPropertyBlock ( ) ) ) ;
if ( s_Mpb . isEmpty ) return ;
// #41: Copy the value from MaterialPropertyBlock to CanvasRenderer
var mat = canvasRenderer . GetMaterial ( index ) ;
if ( ! mat ) return ;
foreach ( var ap in m_AnimatableProperties )
{
ap . UpdateMaterialProperties ( mat , s_Mpb ) ;
}
s_Mpb . Clear ( ) ;
}
2020-08-20 03:42:16 +08:00
/// <summary>
/// This function is called when the object becomes enabled and active.
/// </summary>
protected override void OnEnable ( )
{
2020-08-28 13:38:13 +08:00
_cachedPosition = transform . localPosition ;
_activeMeshIndices = 0 ;
2020-08-20 03:42:16 +08:00
2020-08-28 13:38:13 +08:00
UIParticleUpdater . Register ( this ) ;
particles . Exec ( p = > p . GetComponent < ParticleSystemRenderer > ( ) . enabled = false ) ;
2020-09-01 12:50:54 +08:00
if ( isActiveAndEnabled & & m_IgnoreCanvasScaler )
{
_tracker . Add ( this , rectTransform , DrivenTransformProperties . Scale ) ;
}
2020-02-12 20:38:06 +08:00
// Create objects.
2020-09-02 01:39:05 +08:00
_bakedMesh = MeshPool . Rent ( ) ;
2020-02-12 20:38:06 +08:00
base . OnEnable ( ) ;
2020-09-01 12:50:54 +08:00
InitializeIfNeeded ( ) ;
2020-02-12 20:38:06 +08:00
}
/// <summary>
/// This function is called when the behaviour becomes disabled.
/// </summary>
protected override void OnDisable ( )
{
2020-08-28 13:38:13 +08:00
UIParticleUpdater . Unregister ( this ) ;
2020-09-01 12:50:54 +08:00
if ( ! _shouldBeRemoved )
particles . Exec ( p = > p . GetComponent < ParticleSystemRenderer > ( ) . enabled = true ) ;
2020-08-20 03:42:16 +08:00
_tracker . Clear ( ) ;
2020-08-12 22:19:08 +08:00
2020-08-20 03:42:16 +08:00
// Destroy object.
2020-09-02 01:39:05 +08:00
MeshPool . Return ( _bakedMesh ) ;
2020-08-20 03:42:16 +08:00
_bakedMesh = null ;
2020-02-12 20:38:06 +08:00
2020-08-20 03:42:16 +08:00
base . OnDisable ( ) ;
2020-02-12 20:38:06 +08:00
}
2019-02-26 10:26:56 +08:00
2020-02-12 20:38:06 +08:00
/// <summary>
/// Call to update the geometry of the Graphic onto the CanvasRenderer.
/// </summary>
protected override void UpdateGeometry ( )
{
}
/// <summary>
/// Callback for when properties have been changed by animation.
/// </summary>
protected override void OnDidApplyAnimationProperties ( )
{
}
2018-11-28 13:19:33 +08:00
2020-08-28 13:38:13 +08:00
private void InitializeIfNeeded ( )
2020-02-12 20:38:06 +08:00
{
2020-09-01 12:50:54 +08:00
if ( enabled & & m_IsTrail )
2020-02-12 20:38:06 +08:00
{
2020-09-01 12:50:54 +08:00
UnityEngine . Debug . LogWarningFormat ( this , "[UIParticle] The UIParticle component should be removed: {0}\nReason: UIParticle for trails is no longer needed." , name ) ;
gameObject . hideFlags = HideFlags . None ;
_shouldBeRemoved = true ;
enabled = false ;
2020-08-28 13:38:13 +08:00
return ;
2020-02-12 20:38:06 +08:00
}
2020-09-01 12:50:54 +08:00
else if ( enabled & & transform . parent & & transform . parent . GetComponentInParent < UIParticle > ( ) )
2020-08-28 15:50:13 +08:00
{
2020-09-01 12:50:54 +08:00
UnityEngine . Debug . LogWarningFormat ( this , "[UIParticle] The UIParticle component should be removed: {0}\nReason: The parent UIParticle exists." , name ) ;
gameObject . hideFlags = HideFlags . None ;
_shouldBeRemoved = true ;
enabled = false ;
2020-08-28 15:50:13 +08:00
return ;
}
2020-09-02 13:15:30 +08:00
if ( ! this | | particles . Any ( x = > x ) ) return ;
2020-09-01 12:50:54 +08:00
2020-08-28 13:38:13 +08:00
// refresh.
#if UNITY_EDITOR
if ( ! Application . isPlaying )
2020-08-28 15:50:13 +08:00
UnityEditor . EditorApplication . delayCall + = ( ) = >
{
if ( this ) RefreshParticles ( ) ;
} ;
2020-08-20 03:42:16 +08:00
else
2020-08-28 13:38:13 +08:00
# endif
RefreshParticles ( ) ;
2020-08-12 22:19:08 +08:00
}
2020-08-28 15:50:13 +08:00
#if UNITY_EDITOR
2020-10-04 22:26:53 +08:00
protected override void OnValidate ( )
{
SetLayoutDirty ( ) ;
SetVerticesDirty ( ) ;
m_ShouldRecalculateStencil = true ;
RecalculateClipping ( ) ;
}
2020-08-28 15:50:13 +08:00
void ISerializationCallbackReceiver . OnBeforeSerialize ( )
{
2020-09-27 14:58:09 +08:00
if ( Application . isPlaying ) return ;
2020-08-28 15:50:13 +08:00
InitializeIfNeeded ( ) ;
}
void ISerializationCallbackReceiver . OnAfterDeserialize ( )
{
2020-10-04 22:26:53 +08:00
if ( m_Scale3D = = Vector3 . zero )
{
scale = m_Scale ;
}
2020-08-28 15:50:13 +08:00
UnityEditor . EditorApplication . delayCall + = ( ) = >
{
2020-09-27 14:58:09 +08:00
if ( Application . isPlaying | | ! this ) return ;
InitializeIfNeeded ( ) ;
2020-08-28 15:50:13 +08:00
} ;
}
# endif
2020-02-12 20:38:06 +08:00
}
2020-04-30 11:28:48 +08:00
}