refactor: fix package structure

This commit is contained in:
mob-sakai
2024-01-23 23:17:31 +09:00
parent 7c4242150b
commit cbe3105b0f
28 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
using System;
using UnityEngine;
namespace Coffee.UIExtensions
{
[Serializable]
public class AnimatableProperty : ISerializationCallbackReceiver
{
public enum ShaderPropertyType
{
Color,
Vector,
Float,
Range,
Texture
}
[SerializeField] private string m_Name = "";
[SerializeField] private ShaderPropertyType m_Type = ShaderPropertyType.Vector;
public int id { get; private set; }
public ShaderPropertyType type
{
get { return m_Type; }
}
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
id = Shader.PropertyToID(m_Name);
}
public void UpdateMaterialProperties(Material material, MaterialPropertyBlock mpb)
{
if (!material.HasProperty(id)) return;
switch (type)
{
case ShaderPropertyType.Color:
var color = mpb.GetColor(id);
if (color != default)
{
material.SetColor(id, color);
}
break;
case ShaderPropertyType.Vector:
var vector = mpb.GetVector(id);
if (vector != default)
{
material.SetVector(id, vector);
}
break;
case ShaderPropertyType.Float:
case ShaderPropertyType.Range:
var value = mpb.GetFloat(id);
if (!Mathf.Approximately(value, 0))
{
material.SetFloat(id, value);
}
break;
case ShaderPropertyType.Texture:
var tex = mpb.GetTexture(id);
if (tex != default(Texture))
{
material.SetTexture(id, tex);
}
break;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c434da72184404a0cbdf8e7a529a41bf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
{
"name": "Coffee.UIParticle",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 7793e5cd8d2e94e21b56de41db6606bb
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,72 @@
using System.Collections.Generic;
using UnityEngine;
namespace Coffee.UIParticleExtensions
{
internal class ModifiedMaterial
{
private static readonly List<MatEntry> s_Entries = new List<MatEntry>();
public static Material Add(Material baseMat, Texture texture, int id, int props)
{
MatEntry e;
for (var i = 0; i < s_Entries.Count; i++)
{
e = s_Entries[i];
if (e.baseMat != baseMat || e.texture != texture || e.id != id || e.props != props) continue;
++e.count;
return e.customMat;
}
e = new MatEntry
{
count = 1,
baseMat = baseMat,
texture = texture,
id = id,
props = props,
customMat = new Material(baseMat)
{
name = $"{baseMat.name}_{id}",
hideFlags = HideFlags.DontSave | HideFlags.NotEditable,
mainTexture = texture ? texture : null
}
};
s_Entries.Add(e);
//Debug.LogFormat(">>>> ModifiedMaterial.Add -> count = count:{0}, mat:{1}, tex:{2}, id:{3}", s_Entries.Count, baseMat, texture, id);
return e.customMat;
}
public static void Remove(Material customMat)
{
if (!customMat) return;
for (var i = 0; i < s_Entries.Count; ++i)
{
var e = s_Entries[i];
if (e.customMat != customMat) continue;
if (--e.count == 0)
{
//Debug.LogFormat(">>>> ModifiedMaterial.Remove -> count:{0}, mat:{1}, tex:{2}, id:{3}", s_Entries.Count - 1, e.customMat, e.texture, e.id);
Misc.DestroyImmediate(e.customMat);
e.customMat = null;
e.baseMat = null;
e.texture = null;
s_Entries.RemoveAt(i);
}
break;
}
}
private class MatEntry
{
public Material baseMat;
public int count;
public Material customMat;
public int id;
public int props;
public Texture texture;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b0beae5bb1cb142b9ab90dc0d371f026
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,672 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Coffee.UIParticleExtensions;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Serialization;
using UnityEngine.UI;
using Random = UnityEngine.Random;
[assembly: InternalsVisibleTo("Coffee.UIParticle.Editor")]
namespace Coffee.UIExtensions
{
/// <summary>
/// Render maskable and sortable particle effect ,without Camera, RenderTexture or Canvas.
/// </summary>
[ExecuteAlways]
[RequireComponent(typeof(RectTransform))]
[RequireComponent(typeof(CanvasRenderer))]
public class UIParticle : MaskableGraphic, ISerializationCallbackReceiver
{
public enum MeshSharing
{
None,
Auto,
Primary,
PrimarySimulator,
Replica
}
public enum PositionMode
{
Relative,
Absolute
}
public enum AutoScalingMode
{
None,
UIParticle,
Transform
}
[HideInInspector]
[SerializeField]
internal bool m_IsTrail;
[HideInInspector]
[FormerlySerializedAs("m_IgnoreParent")]
[SerializeField]
private bool m_IgnoreCanvasScaler;
[HideInInspector]
[SerializeField]
private bool m_AbsoluteMode;
[Tooltip("Particle effect scale")]
[SerializeField]
private Vector3 m_Scale3D = new Vector3(10, 10, 10);
[Tooltip("Animatable material properties.\n" +
"If you want to change the material properties of the ParticleSystem in Animation, enable it.")]
[SerializeField]
internal AnimatableProperty[] m_AnimatableProperties = new AnimatableProperty[0];
[Tooltip("Particles")]
[SerializeField]
private List<ParticleSystem> m_Particles = new List<ParticleSystem>();
[Tooltip("Mesh sharing.\n" +
"None: disable mesh sharing.\n" +
"Auto: automatically select Primary/Replica.\n" +
"Primary: provides particle simulation results to the same group.\n" +
"Primary Simulator: Primary, but do not render the particle (simulation only).\n" +
"Replica: render simulation results provided by the primary.")]
[SerializeField]
private MeshSharing m_MeshSharing = MeshSharing.None;
[Tooltip("Mesh sharing group ID.\n" +
"If non-zero is specified, particle simulation results are shared within the group.")]
[SerializeField]
private int m_GroupId;
[SerializeField]
private int m_GroupMaxId;
[Tooltip("Relative: The particles will be emitted from the scaled position of ParticleSystem.\n" +
"Absolute: The particles will be emitted from the world position of ParticleSystem.")]
[SerializeField]
private PositionMode m_PositionMode = PositionMode.Relative;
[SerializeField]
[Tooltip("Prevent the root-Canvas scale from affecting the hierarchy-scaled ParticleSystem.")]
private bool m_AutoScaling = true;
[SerializeField]
[Tooltip("Transform: Transform.lossyScale (=world scale) will be set to (1, 1, 1)." +
"UIParticle: UIParticle.scale will be adjusted.")]
private AutoScalingMode m_AutoScalingMode = AutoScalingMode.Transform;
[SerializeField]
private bool m_ResetScaleOnEnable;
private readonly List<UIParticleRenderer> _renderers = new List<UIParticleRenderer>();
private int _groupId;
private Camera _orthoCamera;
private DrivenRectTransformTracker _tracker;
/// <summary>
/// Should this graphic be considered a target for ray-casting?
/// </summary>
public override bool raycastTarget
{
get { return false; }
set { }
}
/// <summary>
/// Mesh sharing.
/// None: disable mesh sharing.
/// Auto: automatically select Primary/Replica.
/// Primary: provides particle simulation results to the same group.
/// Primary Simulator: Primary, but do not render the particle (simulation only).
/// Replica: render simulation results provided by the primary.
/// </summary>
public MeshSharing meshSharing
{
get { return m_MeshSharing; }
set { m_MeshSharing = value; }
}
/// <summary>
/// Mesh sharing group ID.
/// If non-zero is specified, particle simulation results are shared within the group.
/// </summary>
public int groupId
{
get { return _groupId; }
set
{
if (m_GroupId == value) return;
m_GroupId = value;
if (m_GroupId != m_GroupMaxId)
{
ResetGroupId();
}
}
}
public int groupMaxId
{
get { return m_GroupMaxId; }
set
{
if (m_GroupMaxId == value) return;
m_GroupMaxId = value;
ResetGroupId();
}
}
/// <summary>
/// Particle position mode.
/// Relative: The particles will be emitted from the scaled position of the ParticleSystem.
/// Absolute: The particles will be emitted from the world position of the ParticleSystem.
/// </summary>
public PositionMode positionMode
{
get { return m_PositionMode; }
set { m_PositionMode = value; }
}
/// <summary>
/// Particle position mode.
/// Relative: The particles will be emitted from the scaled position of the ParticleSystem.
/// Absolute: The particles will be emitted from the world position of the ParticleSystem.
/// </summary>
public bool absoluteMode
{
get { return m_PositionMode == PositionMode.Absolute; }
set { positionMode = value ? PositionMode.Absolute : PositionMode.Relative; }
}
/// <summary>
/// Prevents the root-Canvas scale from affecting the hierarchy-scaled ParticleSystem.
/// </summary>
[Obsolete("The autoScaling is now obsolete. Please use the autoScalingMode instead.", false)]
public bool autoScaling
{
get { return m_AutoScalingMode != AutoScalingMode.None; }
set
{
autoScalingMode = value ? AutoScalingMode.Transform : AutoScalingMode.None;
}
}
/// <summary>
/// Auto scaling mode.
/// Transform: Transform.lossyScale (=world scale) will be set to (1, 1, 1).
/// UIParticle: UIParticle.scale will be adjusted.
/// </summary>
public AutoScalingMode autoScalingMode
{
get { return m_AutoScalingMode; }
set
{
if (m_AutoScalingMode == value) return;
m_AutoScalingMode = value;
UpdateTracker();
}
}
internal bool useMeshSharing
{
get { return m_MeshSharing != MeshSharing.None; }
}
internal bool isPrimary
{
get
{
return m_MeshSharing == MeshSharing.Primary
|| m_MeshSharing == MeshSharing.PrimarySimulator;
}
}
internal bool canSimulate
{
get
{
return m_MeshSharing == MeshSharing.None
|| m_MeshSharing == MeshSharing.Auto
|| m_MeshSharing == MeshSharing.Primary
|| m_MeshSharing == MeshSharing.PrimarySimulator;
}
}
internal bool canRender
{
get
{
return m_MeshSharing == MeshSharing.None
|| m_MeshSharing == MeshSharing.Auto
|| m_MeshSharing == MeshSharing.Primary
|| m_MeshSharing == MeshSharing.Replica;
}
}
/// <summary>
/// Particle effect scale.
/// </summary>
public float scale
{
get { return m_Scale3D.x; }
set { m_Scale3D = new Vector3(value, value, value); }
}
/// <summary>
/// Particle effect scale.
/// </summary>
public Vector3 scale3D
{
get { return m_Scale3D; }
set { m_Scale3D = value; }
}
/// <summary>
/// Particle effect scale.
/// </summary>
public Vector3 scale3DForCalc
{
get { return autoScalingMode == AutoScalingMode.UIParticle ? m_Scale3D.GetScaled(canvasScale) : m_Scale3D; }
}
public List<ParticleSystem> particles
{
get { return m_Particles; }
}
/// <summary>
/// Get all base materials to render.
/// </summary>
public IEnumerable<Material> materials
{
get
{
for (var i = 0; i < _renderers.Count; i++)
{
var r = _renderers[i];
if (!r || !r.material) continue;
yield return r.material;
}
}
}
public override Material materialForRendering
{
get { return null; }
}
/// <summary>
/// Paused.
/// </summary>
public bool isPaused { get; private set; }
public Vector3 parentScale { get; private set; }
public Vector3 canvasScale { get; private set; }
protected override void OnEnable()
{
ResetGroupId();
UpdateTracker();
UIParticleUpdater.Register(this);
RegisterDirtyMaterialCallback(UpdateRendererMaterial);
if (0 < particles.Count)
{
RefreshParticles(particles);
}
else
{
RefreshParticles();
}
base.OnEnable();
// Reset scale for upgrade.
if (m_ResetScaleOnEnable)
{
m_ResetScaleOnEnable = false;
transform.localScale = Vector3.one;
}
}
/// <summary>
/// This function is called when the behaviour becomes disabled.
/// </summary>
protected override void OnDisable()
{
UpdateTracker();
UIParticleUpdater.Unregister(this);
_renderers.ForEach(r => r.Reset());
UnregisterDirtyMaterialCallback(UpdateRendererMaterial);
base.OnDisable();
}
/// <summary>
/// Callback for when properties have been changed by animation.
/// </summary>
protected override void OnDidApplyAnimationProperties()
{
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
UpdateTracker();
}
#endif
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
}
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
if (m_IgnoreCanvasScaler || m_AutoScaling)
{
m_IgnoreCanvasScaler = false;
m_AutoScaling = false;
m_AutoScalingMode = AutoScalingMode.Transform;
m_ResetScaleOnEnable = true;
#if UNITY_EDITOR
EditorApplication.delayCall += () =>
{
if (!this || !gameObject || !transform || Application.isPlaying) return;
transform.localScale = Vector3.one;
m_ResetScaleOnEnable = false;
EditorUtility.SetDirty(this);
};
#endif
}
if (m_AbsoluteMode)
{
m_AbsoluteMode = false;
m_PositionMode = PositionMode.Absolute;
}
}
public void Play()
{
particles.Exec(p => p.Simulate(0, false, true));
isPaused = false;
}
public void Pause()
{
particles.Exec(p => p.Pause());
isPaused = true;
}
public void Resume()
{
isPaused = false;
}
public void Stop()
{
particles.Exec(p => p.Stop());
isPaused = true;
}
public void StartEmission()
{
particles.Exec(p =>
{
var emission = p.emission;
emission.enabled = true;
});
}
public void StopEmission()
{
particles.Exec(p =>
{
var emission = p.emission;
emission.enabled = false;
});
}
public void Clear()
{
particles.Exec(p => p.Clear());
isPaused = true;
}
public void SetParticleSystemInstance(GameObject instance)
{
SetParticleSystemInstance(instance, true);
}
public void SetParticleSystemInstance(GameObject instance, bool destroyOldParticles)
{
if (!instance) return;
foreach (Transform child in transform)
{
var go = child.gameObject;
go.SetActive(false);
if (destroyOldParticles)
{
Misc.Destroy(go);
}
}
var tr = instance.transform;
tr.SetParent(transform, false);
tr.localPosition = Vector3.zero;
RefreshParticles(instance);
}
public void SetParticleSystemPrefab(GameObject prefab)
{
if (!prefab) return;
SetParticleSystemInstance(Instantiate(prefab.gameObject), true);
}
public void RefreshParticles()
{
RefreshParticles(gameObject);
}
private void RefreshParticles(GameObject root)
{
if (!root) return;
root.GetComponentsInChildren(true, particles);
particles.RemoveAll(x => x.GetComponentInParent<UIParticle>(true) != this);
for (var i = 0; i < particles.Count; i++)
{
var ps = particles[i];
var tsa = ps.textureSheetAnimation;
if (tsa.mode == ParticleSystemAnimationMode.Sprites && tsa.uvChannelMask == 0)
{
tsa.uvChannelMask = UVChannelFlags.UV0;
}
}
RefreshParticles(particles);
}
public void RefreshParticles(List<ParticleSystem> particles)
{
// #246: Nullptr exceptions when using nested UIParticle components in hierarchy
_renderers.Clear();
foreach (Transform child in transform)
{
var uiParticleRenderer = child.GetComponent<UIParticleRenderer>();
if (uiParticleRenderer != null)
{
_renderers.Add(uiParticleRenderer);
}
}
for (var i = 0; i < _renderers.Count; i++)
{
_renderers[i].Reset(i);
}
var j = 0;
for (var i = 0; i < particles.Count; i++)
{
var ps = particles[i];
if (!ps) continue;
GetRenderer(j++).Set(this, ps, false);
if (ps.trails.enabled)
{
GetRenderer(j++).Set(this, ps, true);
}
}
}
internal void UpdateTransformScale()
{
canvasScale = canvas.rootCanvas.transform.localScale.Inverse();
parentScale = transform.parent.lossyScale;
if (autoScalingMode != AutoScalingMode.Transform) return;
var newScale = parentScale.Inverse();
if (transform.localScale != newScale)
{
transform.localScale = newScale;
}
}
internal void UpdateRenderers()
{
if (!isActiveAndEnabled) return;
for (var i = 0; i < _renderers.Count; i++)
{
var r = _renderers[i];
if (!r)
{
RefreshParticles(particles);
break;
}
}
var bakeCamera = GetBakeCamera();
for (var i = 0; i < _renderers.Count; i++)
{
var r = _renderers[i];
if (!r) continue;
r.UpdateMesh(bakeCamera);
}
}
internal void ResetGroupId()
{
_groupId = m_GroupId == m_GroupMaxId
? m_GroupId
: Random.Range(m_GroupId, m_GroupMaxId + 1);
}
protected override void UpdateMaterial()
{
}
/// <summary>
/// Call to update the geometry of the Graphic onto the CanvasRenderer.
/// </summary>
protected override void UpdateGeometry()
{
}
private void UpdateRendererMaterial()
{
for (var i = 0; i < _renderers.Count; i++)
{
var r = _renderers[i];
if (!r) continue;
r.maskable = maskable;
r.SetMaterialDirty();
}
}
internal UIParticleRenderer GetRenderer(int index)
{
if (_renderers.Count <= index)
{
_renderers.Add(UIParticleRenderer.AddRenderer(this, index));
}
if (!_renderers[index])
{
_renderers[index] = UIParticleRenderer.AddRenderer(this, index);
}
return _renderers[index];
}
private Camera GetBakeCamera()
{
if (!canvas) return Camera.main;
// When render mode is ScreenSpaceCamera or WorldSpace, use world camera.
var root = canvas.rootCanvas;
if (root.renderMode != RenderMode.ScreenSpaceOverlay)
{
return root.worldCamera ? root.worldCamera : Camera.main;
}
// When render mode is ScreenSpaceOverlay, use ortho-camera.
if (!_orthoCamera)
{
// Find existing ortho-camera.
foreach (Transform child in transform)
{
var cam = child.GetComponent<Camera>();
if (cam && cam.name == "[generated] UIParticleOverlayCamera")
{
_orthoCamera = cam;
break;
}
}
// Create ortho-camera.
if (!_orthoCamera)
{
var go = new GameObject("[generated] UIParticleOverlayCamera") { hideFlags = HideFlags.HideAndDontSave };
go.SetActive(false);
go.transform.SetParent(transform, false);
_orthoCamera = go.AddComponent<Camera>();
_orthoCamera.enabled = false;
}
}
//
var size = ((RectTransform)root.transform).rect.size;
_orthoCamera.orthographicSize = Mathf.Max(size.x, size.y) * root.scaleFactor;
_orthoCamera.transform.SetPositionAndRotation(new Vector3(0, 0, -1000), Quaternion.identity);
_orthoCamera.orthographic = true;
_orthoCamera.farClipPlane = 2000f;
return _orthoCamera;
}
private void UpdateTracker()
{
#pragma warning disable CS0618 // Type or member is obsolete
if (!enabled || !autoScaling || autoScalingMode != AutoScalingMode.Transform)
#pragma warning restore CS0618 // Type or member is obsolete
{
_tracker.Clear();
}
else
{
_tracker.Add(this, rectTransform, DrivenTransformProperties.Scale);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 16f0b0b6d0b7542bfbd20a3e05b04ff1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,259 @@
using System;
using Coffee.UIParticleExtensions;
using UnityEngine;
using UnityEngine.Events;
namespace Coffee.UIExtensions
{
[ExecuteAlways]
public class UIParticleAttractor : MonoBehaviour
{
public enum Movement
{
Linear,
Smooth,
Sphere
}
public enum UpdateMode
{
Normal,
UnscaledTime
}
[SerializeField]
private ParticleSystem m_ParticleSystem;
[Range(0.1f, 10f)]
[SerializeField]
private float m_DestinationRadius = 1;
[Range(0f, 0.95f)]
[SerializeField]
private float m_DelayRate;
[Range(0.001f, 100f)]
[SerializeField]
private float m_MaxSpeed = 1;
[SerializeField]
private Movement m_Movement;
[SerializeField]
private UpdateMode m_UpdateMode;
[SerializeField]
private UnityEvent m_OnAttracted;
private UIParticle _uiParticle;
public float destinationRadius
{
get { return m_DestinationRadius; }
set { m_DestinationRadius = Mathf.Clamp(value, 0.1f, 10f); }
}
public float delay
{
get { return m_DelayRate; }
set { m_DelayRate = value; }
}
public float maxSpeed
{
get { return m_MaxSpeed; }
set { m_MaxSpeed = value; }
}
public Movement movement
{
get { return m_Movement; }
set { m_Movement = value; }
}
public UpdateMode updateMode
{
get { return m_UpdateMode; }
set { m_UpdateMode = value; }
}
public UnityEvent onAttracted
{
get { return m_OnAttracted; }
set { m_OnAttracted = value; }
}
#if UNITY_EDITOR
public new ParticleSystem particleSystem
#else
public ParticleSystem particleSystem
#endif
{
get { return m_ParticleSystem; }
set
{
m_ParticleSystem = value;
ApplyParticleSystem();
}
}
private void OnEnable()
{
ApplyParticleSystem();
UIParticleUpdater.Register(this);
}
private void OnDisable()
{
UIParticleUpdater.Unregister(this);
}
private void OnDestroy()
{
_uiParticle = null;
m_ParticleSystem = null;
}
internal void Attract()
{
if (m_ParticleSystem == null) return;
var count = m_ParticleSystem.particleCount;
if (count == 0) return;
var particles = ParticleSystemExtensions.GetParticleArray(count);
m_ParticleSystem.GetParticles(particles, count);
var dstPos = GetDestinationPosition();
for (var i = 0; i < count; i++)
{
// Attracted
var p = particles[i];
if (0f < p.remainingLifetime && Vector3.Distance(p.position, dstPos) < m_DestinationRadius)
{
p.remainingLifetime = 0f;
particles[i] = p;
if (m_OnAttracted != null)
{
try
{
m_OnAttracted.Invoke();
}
catch (Exception e)
{
Debug.LogException(e);
}
}
continue;
}
// Calc attracting time
var delayTime = p.startLifetime * m_DelayRate;
var duration = p.startLifetime - delayTime;
var time = Mathf.Max(0, p.startLifetime - p.remainingLifetime - delayTime);
// Delay
if (time <= 0) continue;
// Attract
p.position = GetAttractedPosition(p.position, dstPos, duration, time);
p.velocity *= 0.5f;
particles[i] = p;
}
m_ParticleSystem.SetParticles(particles, count);
}
private Vector3 GetDestinationPosition()
{
var isUI = _uiParticle && _uiParticle.enabled;
var psPos = m_ParticleSystem.transform.position;
var attractorPos = transform.position;
var dstPos = attractorPos;
var isLocalSpace = m_ParticleSystem.IsLocalSpace();
if (isLocalSpace)
{
dstPos = m_ParticleSystem.transform.InverseTransformPoint(dstPos);
}
if (isUI)
{
var inverseScale = _uiParticle.parentScale.Inverse();
var scale3d = _uiParticle.scale3DForCalc;
dstPos = dstPos.GetScaled(inverseScale, scale3d.Inverse());
// Relative mode
if (_uiParticle.positionMode == UIParticle.PositionMode.Relative)
{
var diff = _uiParticle.transform.position - psPos;
diff.Scale(scale3d - inverseScale);
diff.Scale(scale3d.Inverse());
dstPos += diff;
}
#if UNITY_EDITOR
if (!Application.isPlaying && !isLocalSpace)
{
dstPos += psPos - psPos.GetScaled(inverseScale, scale3d.Inverse());
}
#endif
}
return dstPos;
}
private Vector3 GetAttractedPosition(Vector3 current, Vector3 target, float duration, float time)
{
var speed = m_MaxSpeed;
switch (m_UpdateMode)
{
case UpdateMode.Normal:
speed *= 60 * Time.deltaTime;
break;
case UpdateMode.UnscaledTime:
speed *= 60 * Time.unscaledDeltaTime;
break;
}
switch (m_Movement)
{
case Movement.Linear:
speed /= duration;
break;
case Movement.Smooth:
target = Vector3.Lerp(current, target, time / duration);
break;
case Movement.Sphere:
target = Vector3.Slerp(current, target, time / duration);
break;
}
return Vector3.MoveTowards(current, target, speed);
}
private void ApplyParticleSystem()
{
_uiParticle = null;
if (m_ParticleSystem == null)
{
#if UNITY_EDITOR
if (Application.isPlaying)
#endif
{
Debug.LogError("No particle system attached to particle attractor script", this);
}
return;
}
_uiParticle = m_ParticleSystem.GetComponentInParent<UIParticle>(true);
if (_uiParticle && !_uiParticle.particles.Contains(m_ParticleSystem))
{
_uiParticle = null;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 00e55ae1441ff4583859c55384964d86
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,702 @@
#if UNITY_2022_3_0 || UNITY_2022_3_1 || UNITY_2022_3_2 || UNITY_2022_3_3 || UNITY_2022_3_4 || UNITY_2022_3_5 || UNITY_2022_3_6 || UNITY_2022_3_7 || UNITY_2022_3_8 || UNITY_2022_3_9 || UNITY_2022_3_10
#elif UNITY_2023_1_0 || UNITY_2023_1_1 || UNITY_2023_1_2 || UNITY_2023_1_3 || UNITY_2023_1_4 || UNITY_2023_1_5 || UNITY_2023_1_6 || UNITY_2023_1_7 || UNITY_2023_1_8 || UNITY_2023_1_9
#elif UNITY_2023_1_10 || UNITY_2023_1_11 || UNITY_2023_1_12 || UNITY_2023_1_13 || UNITY_2023_1_14 || UNITY_2023_1_15 || UNITY_2023_1_16
#elif UNITY_2022_3_OR_NEWER
#define PS_BAKE_API_V2
#endif
using System;
using System.Collections.Generic;
using Coffee.UIParticleExtensions;
using UnityEditor;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Rendering;
using UnityEngine.UI;
namespace Coffee.UIExtensions
{
[ExecuteAlways]
[RequireComponent(typeof(RectTransform))]
[RequireComponent(typeof(CanvasRenderer))]
[AddComponentMenu("")]
internal class UIParticleRenderer : MaskableGraphic
{
private static readonly List<Component> s_Components = new List<Component>();
private static readonly CombineInstance[] s_CombineInstances = { new CombineInstance() };
private static readonly List<Material> s_Materials = new List<Material>(2);
private static MaterialPropertyBlock s_Mpb;
private static readonly List<UIParticleRenderer> s_Renderers = new List<UIParticleRenderer>();
private static readonly List<Color32> s_Colors = new List<Color32>();
private static readonly Vector3[] s_Corners = new Vector3[4];
private Material _currentMaterialForRendering;
private bool _delay;
private int _index;
private bool _isTrail;
private Bounds _lastBounds;
private Material _modifiedMaterial;
private UIParticle _parent;
private ParticleSystem _particleSystem;
private Vector3 _prevPsPos;
private Vector3 _prevScale;
private Vector2Int _prevScreenSize;
private float _prevCanvasScale;
private bool _prewarm;
private ParticleSystemRenderer _renderer;
public override Texture mainTexture
{
get { return _isTrail ? null : _particleSystem.GetTextureForSprite(); }
}
public override bool raycastTarget
{
get { return false; }
}
private Rect rootCanvasRect
{
get
{
s_Corners[0] = transform.TransformPoint(_lastBounds.min.x, _lastBounds.min.y, 0);
s_Corners[1] = transform.TransformPoint(_lastBounds.min.x, _lastBounds.max.y, 0);
s_Corners[2] = transform.TransformPoint(_lastBounds.max.x, _lastBounds.max.y, 0);
s_Corners[3] = transform.TransformPoint(_lastBounds.max.x, _lastBounds.min.y, 0);
if (canvas)
{
var worldToLocalMatrix = canvas.rootCanvas.transform.worldToLocalMatrix;
for (var i = 0; i < 4; ++i)
{
s_Corners[i] = worldToLocalMatrix.MultiplyPoint(s_Corners[i]);
}
}
var corner1 = (Vector2)s_Corners[0];
var corner2 = (Vector2)s_Corners[0];
for (var i = 1; i < 4; ++i)
{
if (s_Corners[i].x < corner1.x)
{
corner1.x = s_Corners[i].x;
}
else if (s_Corners[i].x > corner2.x)
{
corner2.x = s_Corners[i].x;
}
if (s_Corners[i].y < corner1.y)
{
corner1.y = s_Corners[i].y;
}
else if (s_Corners[i].y > corner2.y)
{
corner2.y = s_Corners[i].y;
}
}
return new Rect(corner1, corner2 - corner1);
}
}
public void Reset(int index = -1)
{
if (_renderer)
{
_renderer.enabled = true;
}
_parent = null;
_particleSystem = null;
_renderer = null;
if (0 <= index)
{
_index = index;
}
//_emitter = null;
if (this && isActiveAndEnabled)
{
material = null;
workerMesh.Clear();
canvasRenderer.SetMesh(workerMesh);
_lastBounds = new Bounds();
enabled = false;
}
else
{
ModifiedMaterial.Remove(_modifiedMaterial);
_modifiedMaterial = null;
_currentMaterialForRendering = null;
}
}
protected override void OnEnable()
{
base.OnEnable();
if (!s_CombineInstances[0].mesh)
{
s_CombineInstances[0].mesh = new Mesh
{
name = "[UIParticleRenderer] Combine Instance Mesh",
hideFlags = HideFlags.HideAndDontSave
};
}
_currentMaterialForRendering = null;
}
protected override void OnDisable()
{
base.OnDisable();
ModifiedMaterial.Remove(_modifiedMaterial);
_modifiedMaterial = null;
_currentMaterialForRendering = null;
}
public static UIParticleRenderer AddRenderer(UIParticle parent, int index)
{
// Create renderer object.
var go = new GameObject("[generated] UIParticleRenderer", typeof(UIParticleRenderer))
{
hideFlags = HideFlags.HideAndDontSave,
layer = parent.gameObject.layer
};
// Set parent.
var transform = go.transform;
transform.SetParent(parent.transform, false);
transform.localPosition = Vector3.zero;
transform.localRotation = Quaternion.identity;
transform.localScale = Vector3.one;
// Add renderer component.
var renderer = go.GetComponent<UIParticleRenderer>();
renderer._parent = parent;
renderer._index = index;
return renderer;
}
/// <summary>
/// Perform material modification in this function.
/// </summary>
public override Material GetModifiedMaterial(Material baseMaterial)
{
_currentMaterialForRendering = null;
if (!IsActive() || !_parent)
{
ModifiedMaterial.Remove(_modifiedMaterial);
_modifiedMaterial = null;
return baseMaterial;
}
var modifiedMaterial = base.GetModifiedMaterial(baseMaterial);
//
var texture = mainTexture;
if (texture == null && _parent.m_AnimatableProperties.Length == 0)
{
ModifiedMaterial.Remove(_modifiedMaterial);
_modifiedMaterial = null;
return modifiedMaterial;
}
//
var id = _parent.m_AnimatableProperties.Length == 0 ? 0 : GetInstanceID();
#if UNITY_EDITOR
var props = EditorJsonUtility.ToJson(modifiedMaterial).GetHashCode();
#else
var props = 0;
#endif
modifiedMaterial = ModifiedMaterial.Add(modifiedMaterial, texture, id, props);
ModifiedMaterial.Remove(_modifiedMaterial);
_modifiedMaterial = modifiedMaterial;
return modifiedMaterial;
}
public void Set(UIParticle parent, ParticleSystem ps, bool isTrail)
{
_parent = parent;
maskable = parent.maskable;
gameObject.layer = parent.gameObject.layer;
_particleSystem = ps;
_prewarm = _particleSystem.main.prewarm;
#if UNITY_EDITOR
if (Application.isPlaying)
#endif
{
if (_particleSystem.isPlaying || _prewarm)
{
_particleSystem.Clear();
_particleSystem.Pause();
}
}
_renderer = ps.GetComponent<ParticleSystemRenderer>();
_renderer.enabled = false;
//_emitter = emitter;
_isTrail = isTrail;
_renderer.GetSharedMaterials(s_Materials);
material = s_Materials[isTrail ? 1 : 0];
s_Materials.Clear();
// Support sprite.
var tsa = ps.textureSheetAnimation;
if (tsa.mode == ParticleSystemAnimationMode.Sprites && tsa.uvChannelMask == 0)
{
tsa.uvChannelMask = UVChannelFlags.UV0;
}
_prevScale = GetWorldScale();
_prevPsPos = _particleSystem.transform.position;
_prevScreenSize = new Vector2Int(Screen.width, Screen.height);
_prevCanvasScale = canvas ? canvas.scaleFactor : 1f;
_delay = true;
canvasRenderer.SetTexture(null);
enabled = true;
}
public void UpdateMesh(Camera bakeCamera)
{
// No particle to render: Clear mesh.
if (
!isActiveAndEnabled || !_particleSystem || !_parent
|| !canvasRenderer || !canvas || !bakeCamera
|| _parent.meshSharing == UIParticle.MeshSharing.Replica
|| !transform.lossyScale.GetScaled(_parent.scale3DForCalc).IsVisible() // Scale is not visible.
|| (!_particleSystem.IsAlive() && !_particleSystem.isPlaying) // No particle.
|| (_isTrail && !_particleSystem.trails.enabled) // Trail, but it is not enabled.
#if UNITY_2018_3_OR_NEWER
|| canvasRenderer.GetInheritedAlpha() <
0.01f // #102: Do not bake particle system to mesh when the alpha is zero.
#endif
)
{
Profiler.BeginSample("[UIParticleRenderer] Clear Mesh");
workerMesh.Clear();
canvasRenderer.SetMesh(workerMesh);
_lastBounds = new Bounds();
Profiler.EndSample();
return;
}
var main = _particleSystem.main;
var scale = GetWorldScale();
var psPos = _particleSystem.transform.position;
// Simulate particles.
Profiler.BeginSample("[UIParticle] Bake Mesh > Simulate Particles");
if (!_isTrail && _parent.canSimulate)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
SimulateForEditor(psPos - _prevPsPos, scale);
}
else
#endif
{
ResolveResolutionChange(psPos, scale);
Simulate(scale, _parent.isPaused || _delay);
if (_delay && !_parent.isPaused)
{
Simulate(scale, _parent.isPaused);
}
// When the ParticleSystem simulation is complete, stop it.
if (!main.loop
&& main.duration <= _particleSystem.time
&& (_particleSystem.IsAlive() || _particleSystem.particleCount == 0)
)
{
_particleSystem.Stop(false);
}
}
_prevScale = scale;
_prevPsPos = psPos;
_delay = false;
}
Profiler.EndSample();
// Bake mesh.
Profiler.BeginSample("[UIParticleRenderer] Bake Mesh");
if (_isTrail && _parent.canSimulate && 0 < s_CombineInstances[0].mesh.vertexCount)
{
#if PS_BAKE_API_V2
_renderer.BakeTrailsMesh(s_CombineInstances[0].mesh, bakeCamera, ParticleSystemBakeMeshOptions.BakeRotationAndScale);
#else
_renderer.BakeTrailsMesh(s_CombineInstances[0].mesh, bakeCamera, true);
#endif
}
else if (_renderer.CanBakeMesh())
{
#if PS_BAKE_API_V2
_renderer.BakeMesh(s_CombineInstances[0].mesh, bakeCamera, ParticleSystemBakeMeshOptions.BakeRotationAndScale);
#else
_renderer.BakeMesh(s_CombineInstances[0].mesh, bakeCamera, true);
#endif
}
else
{
s_CombineInstances[0].mesh.Clear(false);
}
// Too many vertices to render.
if (65535 <= s_CombineInstances[0].mesh.vertexCount)
{
s_CombineInstances[0].mesh.Clear(false);
Debug.LogErrorFormat(this,
"Too many vertices to render. index={0}, isTrail={1}, vertexCount={2}(>=65535)",
_index,
_isTrail,
s_CombineInstances[0].mesh.vertexCount
);
s_CombineInstances[0].mesh.Clear(false);
}
Profiler.EndSample();
// Combine mesh to transform. ([ParticleSystem local ->] world -> renderer local)
Profiler.BeginSample("[UIParticleRenderer] Combine Mesh");
if (_parent.canSimulate)
{
if (_parent.positionMode == UIParticle.PositionMode.Absolute)
{
s_CombineInstances[0].transform =
canvasRenderer.transform.worldToLocalMatrix
* GetWorldMatrix(psPos, scale);
}
else
{
var diff = _particleSystem.transform.position - _parent.transform.position;
s_CombineInstances[0].transform =
canvasRenderer.transform.worldToLocalMatrix
* Matrix4x4.Translate(diff.GetScaled(scale - Vector3.one))
* GetWorldMatrix(psPos, scale);
}
workerMesh.CombineMeshes(s_CombineInstances, true, true);
workerMesh.RecalculateBounds();
var bounds = workerMesh.bounds;
var center = bounds.center;
center.z = 0;
bounds.center = center;
var extents = bounds.extents;
extents.z = 0;
bounds.extents = extents;
workerMesh.bounds = bounds;
_lastBounds = bounds;
// Convert linear color to gamma color.
if (QualitySettings.activeColorSpace == ColorSpace.Linear)
{
Profiler.BeginSample("[UIParticleRenderer] Convert Linear to Gamma");
workerMesh.GetColors(s_Colors);
var count_c = s_Colors.Count;
for (var i = 0; i < count_c; i++)
{
var c = s_Colors[i];
c.r = c.r.LinearToGamma();
c.g = c.g.LinearToGamma();
c.b = c.b.LinearToGamma();
s_Colors[i] = c;
}
workerMesh.SetColors(s_Colors);
Profiler.EndSample();
}
GetComponents(typeof(IMeshModifier), s_Components);
for (var i = 0; i < s_Components.Count; i++)
{
#pragma warning disable CS0618 // Type or member is obsolete
((IMeshModifier)s_Components[i]).ModifyMesh(workerMesh);
#pragma warning restore CS0618 // Type or member is obsolete
}
s_Components.Clear();
}
Profiler.EndSample();
// Get grouped renderers.
s_Renderers.Clear();
if (_parent.useMeshSharing)
{
UIParticleUpdater.GetGroupedRenderers(_parent.groupId, _index, s_Renderers);
}
// Set mesh to the CanvasRenderer.
Profiler.BeginSample("[UIParticleRenderer] Set Mesh");
for (var i = 0; i < s_Renderers.Count; i++)
{
if (s_Renderers[i] == this) continue;
s_Renderers[i].canvasRenderer.SetMesh(workerMesh);
s_Renderers[i]._lastBounds = _lastBounds;
}
if (!_parent.canRender)
{
workerMesh.Clear();
}
canvasRenderer.SetMesh(workerMesh);
Profiler.EndSample();
// Update animatable material properties.
Profiler.BeginSample("[UIParticleRenderer] Update Animatable Material Properties");
#if UNITY_EDITOR
if (_modifiedMaterial != material)
{
_renderer.GetSharedMaterials(s_Materials);
material = s_Materials[_isTrail ? 1 : 0];
s_Materials.Clear();
SetMaterialDirty();
}
#endif
UpdateMaterialProperties();
if (_parent.useMeshSharing)
{
if (!_currentMaterialForRendering)
{
_currentMaterialForRendering = materialForRendering;
}
for (var i = 0; i < s_Renderers.Count; i++)
{
if (s_Renderers[i] == this) continue;
s_Renderers[i].canvasRenderer.materialCount = 1;
s_Renderers[i].canvasRenderer.SetMaterial(_currentMaterialForRendering, 0);
}
}
Profiler.EndSample();
s_Renderers.Clear();
}
/// <summary>
/// Call to update the geometry of the Graphic onto the CanvasRenderer.
/// </summary>
protected override void UpdateGeometry()
{
}
public override void Cull(Rect clipRect, bool validRect)
{
var cull = _lastBounds.extents == Vector3.zero
|| !validRect
|| !clipRect.Overlaps(rootCanvasRect, true);
if (canvasRenderer.cull == cull) return;
canvasRenderer.cull = cull;
UISystemProfilerApi.AddMarker("MaskableGraphic.cullingChanged", this);
onCullStateChanged.Invoke(cull);
OnCullingChanged();
}
private Vector3 GetWorldScale()
{
Profiler.BeginSample("[UIParticleRenderer] GetWorldScale");
var scale = _parent.scale3DForCalc.GetScaled(_parent.parentScale);
Profiler.EndSample();
return scale;
}
private Matrix4x4 GetWorldMatrix(Vector3 psPos, Vector3 scale)
{
var space = _particleSystem.GetActualSimulationSpace();
if (_isTrail && _particleSystem.trails.worldSpace)
{
space = ParticleSystemSimulationSpace.World;
}
#if UNITY_EDITOR
if (!Application.isPlaying)
{
switch (space)
{
case ParticleSystemSimulationSpace.World:
return Matrix4x4.Translate(psPos)
* Matrix4x4.Scale(scale)
* Matrix4x4.Translate(-psPos);
}
}
#endif
switch (space)
{
case ParticleSystemSimulationSpace.Local:
return Matrix4x4.Translate(psPos)
* Matrix4x4.Scale(scale);
case ParticleSystemSimulationSpace.World:
return Matrix4x4.Scale(scale);
case ParticleSystemSimulationSpace.Custom:
return Matrix4x4.Translate(_particleSystem.main.customSimulationSpace.position.GetScaled(scale))
//* Matrix4x4.Translate(wpos)
* Matrix4x4.Scale(scale)
//* Matrix4x4.Translate(-wpos)
;
default:
throw new NotSupportedException();
}
}
/// <summary>
/// For world simulation, interpolate particle positions when the screen size is changed.
/// </summary>
/// <param name="psPos"></param>
/// <param name="scale"></param>
private void ResolveResolutionChange(Vector3 psPos, Vector3 scale)
{
var screenSize = new Vector2Int(Screen.width, Screen.height);
var isWorldSpace = _particleSystem.IsWorldSpace();
var canvasScale = _parent.canvas ? _parent.canvas.scaleFactor : 1f;
var resolutionChanged = _prevScreenSize != screenSize || _prevCanvasScale != canvasScale;
if (resolutionChanged && isWorldSpace)
{
// Update particle array size and get particles.
var size = _particleSystem.particleCount;
var particles = ParticleSystemExtensions.GetParticleArray(size);
_particleSystem.GetParticles(particles, size);
// Resolusion resolver:
// (psPos / scale) / (prevPsPos / prevScale) -> psPos * scale.inv * prevPsPos.inv * prevScale
var modifier = psPos.GetScaled(
scale.Inverse(),
_prevPsPos.Inverse(),
_prevScale);
for (var i = 0; i < size; i++)
{
var particle = particles[i];
particle.position = particle.position.GetScaled(modifier);
particles[i] = particle;
}
_particleSystem.SetParticles(particles, size);
// Delay: Do not progress in the frame where the resolution has been changed.
_delay = true;
_prevScale = scale;
_prevPsPos = psPos;
}
_prevCanvasScale = canvas ? canvas.scaleFactor : 1f;
_prevScreenSize = screenSize;
}
private void Simulate(Vector3 scale, bool paused)
{
var main = _particleSystem.main;
var deltaTime = paused
? 0
: main.useUnscaledTime
? Time.unscaledDeltaTime
: Time.deltaTime;
// Prewarm:
if (0 < deltaTime && _prewarm)
{
deltaTime += main.duration;
_prewarm = false;
}
// get world position.
var isLocalSpace = _particleSystem.IsLocalSpace();
var psTransform = _particleSystem.transform;
var originWorldPosition = psTransform.position;
var originWorldRotation = psTransform.rotation;
var emission = _particleSystem.emission;
var rateOverDistance = emission.enabled
&& 0 < emission.rateOverDistance.constant
&& 0 < emission.rateOverDistanceMultiplier;
if (rateOverDistance && !paused)
{
// (For rate-over-distance emission,) Move to previous scaled position, simulate (delta = 0).
var prevScaledPos = isLocalSpace
? _prevPsPos
: _prevPsPos.GetScaled(_prevScale.Inverse());
psTransform.SetPositionAndRotation(prevScaledPos, originWorldRotation);
_particleSystem.Simulate(0, false, false, false);
}
// Move to scaled position, simulate, revert to origin position.
var scaledPos = isLocalSpace
? originWorldPosition
: originWorldPosition.GetScaled(scale.Inverse());
psTransform.SetPositionAndRotation(scaledPos, originWorldRotation);
_particleSystem.Simulate(deltaTime, false, false, false);
psTransform.SetPositionAndRotation(originWorldPosition, originWorldRotation);
}
#if UNITY_EDITOR
private void SimulateForEditor(Vector3 diffPos, Vector3 scale)
{
// Extra world simulation.
var isWorldSpace = _particleSystem.IsWorldSpace();
if (isWorldSpace && 0 < Vector3.SqrMagnitude(diffPos))
{
Profiler.BeginSample("[UIParticle] Bake Mesh > Extra world simulation");
diffPos.x *= 1f - 1f / Mathf.Max(0.001f, scale.x);
diffPos.y *= 1f - 1f / Mathf.Max(0.001f, scale.y);
diffPos.z *= 1f - 1f / Mathf.Max(0.001f, scale.z);
var size = _particleSystem.particleCount;
var particles = ParticleSystemExtensions.GetParticleArray(size);
_particleSystem.GetParticles(particles, size);
for (var i = 0; i < size; i++)
{
var p = particles[i];
p.position += diffPos;
particles[i] = p;
}
_particleSystem.SetParticles(particles, size);
Profiler.EndSample();
}
}
#endif
private void UpdateMaterialProperties()
{
if (_parent.m_AnimatableProperties.Length == 0) return;
if (s_Mpb == null)
{
s_Mpb = new MaterialPropertyBlock();
}
_renderer.GetPropertyBlock(s_Mpb);
if (s_Mpb.isEmpty) return;
// #41: Copy the value from MaterialPropertyBlock to CanvasRenderer
if (!_modifiedMaterial) return;
for (var i = 0; i < _parent.m_AnimatableProperties.Length; i++)
{
var ap = _parent.m_AnimatableProperties[i];
ap.UpdateMaterialProperties(_modifiedMaterial, s_Mpb);
}
s_Mpb.Clear();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0e66d2d1ba43c4cc4bc3e754e403297b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,125 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Coffee.UIExtensions
{
internal static class UIParticleUpdater
{
private static readonly List<UIParticle> s_ActiveParticles = new List<UIParticle>();
private static readonly List<UIParticleAttractor> s_ActiveAttractors = new List<UIParticleAttractor>();
private static readonly HashSet<int> s_UpdatedGroupIds = new HashSet<int>();
private static int s_FrameCount;
public static int uiParticleCount
{
get { return s_ActiveParticles.Count; }
}
public static void Register(UIParticle particle)
{
if (!particle) return;
s_ActiveParticles.Add(particle);
}
public static void Unregister(UIParticle particle)
{
if (!particle) return;
s_ActiveParticles.Remove(particle);
}
public static void Register(UIParticleAttractor attractor)
{
if (!attractor) return;
s_ActiveAttractors.Add(attractor);
}
public static void Unregister(UIParticleAttractor attractor)
{
if (!attractor) return;
s_ActiveAttractors.Remove(attractor);
}
#if UNITY_EDITOR
[InitializeOnLoadMethod]
#endif
[RuntimeInitializeOnLoadMethod]
private static void InitializeOnLoad()
{
Canvas.willRenderCanvases -= Refresh;
Canvas.willRenderCanvases += Refresh;
}
private static void Refresh()
{
// Do not allow it to be called in the same frame.
if (s_FrameCount == Time.frameCount) return;
s_FrameCount = Time.frameCount;
// Simulate -> Primary
for (var i = 0; i < s_ActiveParticles.Count; i++)
{
var uip = s_ActiveParticles[i];
if (!uip || !uip.canvas || !uip.isPrimary || s_UpdatedGroupIds.Contains(uip.groupId)) continue;
s_UpdatedGroupIds.Add(uip.groupId);
uip.UpdateTransformScale();
uip.UpdateRenderers();
}
// Simulate -> Others
for (var i = 0; i < s_ActiveParticles.Count; i++)
{
var uip = s_ActiveParticles[i];
if (!uip || !uip.canvas) continue;
uip.UpdateTransformScale();
if (!uip.useMeshSharing)
{
uip.UpdateRenderers();
}
else if (!s_UpdatedGroupIds.Contains(uip.groupId))
{
s_UpdatedGroupIds.Add(uip.groupId);
uip.UpdateRenderers();
}
}
s_UpdatedGroupIds.Clear();
// Attract
for (var i = 0; i < s_ActiveAttractors.Count; i++)
{
s_ActiveAttractors[i].Attract();
}
}
public static void GetGroupedRenderers(int groupId, int index, List<UIParticleRenderer> results)
{
results.Clear();
for (var i = 0; i < s_ActiveParticles.Count; i++)
{
var uip = s_ActiveParticles[i];
if (uip.useMeshSharing && uip.groupId == groupId)
{
results.Add(uip.GetRenderer(index));
}
}
}
internal static UIParticle GetPrimary(int groupId)
{
UIParticle primary = null;
for (var i = 0; i < s_ActiveParticles.Count; i++)
{
var uip = s_ActiveParticles[i];
if (!uip.useMeshSharing || uip.groupId != groupId) continue;
if (uip.isPrimary) return uip;
if (!primary && uip.canSimulate) primary = uip;
}
return primary;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a0e708dc2e3034ba9a5c51db4252c7e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: -100
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,285 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Coffee.UIParticleExtensions
{
public static class Color32Extensions
{
private static byte[] s_LinearToGammaLut;
public static byte LinearToGamma(this byte self)
{
if (s_LinearToGammaLut == null)
{
s_LinearToGammaLut = new byte[256];
for (var i = 0; i < 256; i++)
{
s_LinearToGammaLut[i] = (byte)(Mathf.LinearToGammaSpace(i / 255f) * 255f);
}
}
return s_LinearToGammaLut[self];
}
}
public static class Vector3Extensions
{
public static Vector3 Inverse(this Vector3 self)
{
self.x = Mathf.Approximately(self.x, 0) ? 1 : 1 / self.x;
self.y = Mathf.Approximately(self.y, 0) ? 1 : 1 / self.y;
self.z = Mathf.Approximately(self.z, 0) ? 1 : 1 / self.z;
return self;
}
public static Vector3 GetScaled(this Vector3 self, Vector3 other1)
{
self.Scale(other1);
return self;
}
public static Vector3 GetScaled(this Vector3 self, Vector3 other1, Vector3 other2)
{
self.Scale(other1);
self.Scale(other2);
return self;
}
public static Vector3 GetScaled(this Vector3 self, Vector3 other1, Vector3 other2, Vector3 other3)
{
self.Scale(other1);
self.Scale(other2);
self.Scale(other3);
return self;
}
public static bool IsVisible(this Vector3 self)
{
return 0 < Mathf.Abs(self.x * self.y * self.z);
}
}
internal static class SpriteExtensions
{
#if UNITY_EDITOR
private static readonly Type s_SpriteEditorExtensionType =
Type.GetType("UnityEditor.Experimental.U2D.SpriteEditorExtension, UnityEditor")
?? Type.GetType("UnityEditor.U2D.SpriteEditorExtension, UnityEditor");
private static readonly MethodInfo s_GetActiveAtlasTextureMethodInfo = s_SpriteEditorExtensionType
.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 = s_GetActiveAtlasTextureMethodInfo.Invoke(null, new object[] { self }) as Texture2D;
return ret
? ret
: self.texture;
}
#else
internal static Texture2D GetActualTexture(this Sprite self)
{
return self ? self.texture : null;
}
#endif
}
public static class ParticleSystemExtensions
{
private static ParticleSystem.Particle[] s_TmpParticles = new ParticleSystem.Particle[2048];
public static ParticleSystem.Particle[] GetParticleArray(int size)
{
if (s_TmpParticles.Length < size)
{
while (s_TmpParticles.Length < size)
{
size = Mathf.NextPowerOfTwo(size);
}
s_TmpParticles = new ParticleSystem.Particle[size];
}
return s_TmpParticles;
}
public static bool CanBakeMesh(this ParticleSystemRenderer self)
{
// #69: Editor crashes when mesh is set to null when `ParticleSystem.RenderMode = Mesh`
if (self.renderMode == ParticleSystemRenderMode.Mesh && self.mesh == null) return false;
// #61: When `ParticleSystem.RenderMode = None`, an error occurs
if (self.renderMode == ParticleSystemRenderMode.None) return false;
return true;
}
public static ParticleSystemSimulationSpace GetActualSimulationSpace(this ParticleSystem self)
{
var main = self.main;
var space = main.simulationSpace;
if (space == ParticleSystemSimulationSpace.Custom && !main.customSimulationSpace)
{
space = ParticleSystemSimulationSpace.Local;
}
return space;
}
public static bool IsLocalSpace(this ParticleSystem self)
{
return GetActualSimulationSpace(self) == ParticleSystemSimulationSpace.Local;
}
public static bool IsWorldSpace(this ParticleSystem self)
{
return GetActualSimulationSpace(self) == ParticleSystemSimulationSpace.World;
}
public static void SortForRendering(this List<ParticleSystem> self, Transform transform, bool sortByMaterial)
{
self.Sort((a, b) =>
{
var aRenderer = a.GetComponent<ParticleSystemRenderer>();
var bRenderer = b.GetComponent<ParticleSystemRenderer>();
// Render queue: ascending
var aMat = aRenderer.sharedMaterial ? aRenderer.sharedMaterial : aRenderer.trailMaterial;
var bMat = bRenderer.sharedMaterial ? bRenderer.sharedMaterial : bRenderer.trailMaterial;
if (!aMat && !bMat) return 0;
if (!aMat) return -1;
if (!bMat) return 1;
if (sortByMaterial)
{
return aMat.GetInstanceID() - bMat.GetInstanceID();
}
if (aMat.renderQueue != bMat.renderQueue)
{
return aMat.renderQueue - bMat.renderQueue;
}
// Sorting layer: ascending
if (aRenderer.sortingLayerID != bRenderer.sortingLayerID)
{
return SortingLayer.GetLayerValueFromID(aRenderer.sortingLayerID) -
SortingLayer.GetLayerValueFromID(bRenderer.sortingLayerID);
}
// Sorting order: ascending
if (aRenderer.sortingOrder != bRenderer.sortingOrder)
{
return aRenderer.sortingOrder - bRenderer.sortingOrder;
}
// Z position & sortingFudge: descending
var aTransform = a.transform;
var bTransform = b.transform;
var aPos = transform.InverseTransformPoint(aTransform.position).z + aRenderer.sortingFudge;
var bPos = transform.InverseTransformPoint(bTransform.position).z + bRenderer.sortingFudge;
if (!Mathf.Approximately(aPos, bPos))
{
return (int)Mathf.Sign(bPos - aPos);
}
return (int)Mathf.Sign(GetIndex(self, a) - GetIndex(self, b));
});
}
private static int GetIndex(IList<ParticleSystem> list, Object ps)
{
for (var i = 0; i < list.Count; i++)
{
if (list[i].GetInstanceID() == ps.GetInstanceID())
{
return i;
}
}
return 0;
}
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);
}
}
internal static class Misc
{
public static void Destroy(Object obj)
{
if (!obj) return;
#if UNITY_EDITOR
if (!Application.isPlaying)
{
Object.DestroyImmediate(obj);
}
else
#endif
{
Object.Destroy(obj);
}
}
public static void DestroyImmediate(Object obj)
{
if (!obj) return;
#if UNITY_EDITOR
if (Application.isEditor)
{
Object.DestroyImmediate(obj);
}
else
#endif
{
Object.Destroy(obj);
}
}
#if !UNITY_2021_2_OR_NEWER || UNITY_2020_3_45 || UNITY_2020_3_46 || UNITY_2020_3_47 || UNITY_2020_3_48
public static T GetComponentInParent<T>(this Component self, bool includeInactive) where T : Component
{
if (!self) return null;
if (!includeInactive) return self.GetComponentInParent<T>();
var current = self.transform;
while (current)
{
var component = current.GetComponent<T>();
if (component) return component;
current = current.parent;
}
return null;
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d188d31b140094ebc84a9caafbc7ac71
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: