Skip to content

Commit

Permalink
feat: ParticleAttractor supports multiple ParticleSystems
Browse files Browse the repository at this point in the history
  • Loading branch information
mob-sakai committed Jul 18, 2024
1 parent 060e50f commit e0a6381
Showing 1 changed file with 141 additions and 70 deletions.
211 changes: 141 additions & 70 deletions Packages/src/Runtime/UIParticleAttractor.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using System;
using System.Collections.Generic;
using Coffee.UIParticleInternal;
using UnityEngine;
using UnityEngine.Events;

namespace Coffee.UIExtensions
{
[ExecuteAlways]
public class UIParticleAttractor : MonoBehaviour
public class UIParticleAttractor : MonoBehaviour, ISerializationCallbackReceiver
{
public enum Movement
{
Expand All @@ -22,8 +23,12 @@ public enum UpdateMode
}

[SerializeField]
[HideInInspector]
private ParticleSystem m_ParticleSystem;

[SerializeField]
private List<ParticleSystem> m_ParticleSystems = new List<ParticleSystem>();

[Range(0.1f, 10f)]
[SerializeField]
private float m_DestinationRadius = 1;
Expand All @@ -45,7 +50,7 @@ public enum UpdateMode
[SerializeField]
private UnityEvent m_OnAttracted;

private UIParticle _uiParticle;
private List<UIParticle> _uiParticles = new List<UIParticle>();

public float destinationRadius
{
Expand Down Expand Up @@ -84,25 +89,46 @@ public UnityEvent onAttracted
}

/// <summary>
/// The target ParticleSystem to attract.
/// The target ParticleSystems to attract. Use <see cref="AddParticleSystem"/> and
/// <see cref="RemoveParticleSystem"/> to modify the list.
/// </summary>
#if UNITY_EDITOR
public new ParticleSystem particleSystem
#else
public ParticleSystem particleSystem
#endif
public IReadOnlyList<ParticleSystem> particleSystems => m_ParticleSystems;

public void AddParticleSystem(ParticleSystem ps)
{
get => m_ParticleSystem;
set
if (m_ParticleSystems == null)
{
m_ParticleSystem = value;
ApplyParticleSystem();
m_ParticleSystems = new List<ParticleSystem>();
}

var i = m_ParticleSystems.IndexOf(ps);
if (0 <= i) return; // Already added: skip

m_ParticleSystems.Add(ps);
_uiParticles.Clear();
}

public void RemoveParticleSystem(ParticleSystem ps)
{
if (m_ParticleSystems == null)
{
return;
}

var i = m_ParticleSystems.IndexOf(ps);
if (i < 0) return; // Not found. skip

m_ParticleSystems.RemoveAt(i);
_uiParticles.Clear();
}

private void Awake()
{
UpgradeIfNeeded();
}

private void OnEnable()
{
ApplyParticleSystem();
UIParticleUpdater.Register(this);
}

Expand All @@ -113,85 +139,96 @@ private void OnDisable()

private void OnDestroy()
{
_uiParticle = null;
m_ParticleSystem = null;
_uiParticles = null;
m_ParticleSystems = null;
}

internal void Attract()
{
if (m_ParticleSystem == null) return;
// Collect UIParticle if needed (same size as m_ParticleSystems)
CollectUIParticlesIfNeeded();

var count = m_ParticleSystem.particleCount;
if (count == 0) return;
for (var particleIndex = 0; particleIndex < this.m_ParticleSystems.Count; particleIndex++)
{
var particleSystem = m_ParticleSystems[particleIndex];

var particles = ParticleSystemExtensions.GetParticleArray(count);
m_ParticleSystem.GetParticles(particles, count);
// Skip: The ParticleSystem is not active
if (particleSystem == null || !particleSystem.gameObject.activeInHierarchy) continue;

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;
// Skip: No active particles
var count = particleSystem.particleCount;
if (count == 0) continue;

if (m_OnAttracted != null)
var particles = ParticleSystemExtensions.GetParticleArray(count);
particleSystem.GetParticles(particles, count);

var uiParticle = _uiParticles[particleIndex];
var dstPos = this.GetDestinationPosition(uiParticle, particleSystem);
for (var i = 0; i < count; i++)
{
// Attracted
var p = particles[i];
if (0f < p.remainingLifetime && Vector3.Distance(p.position, dstPos) < this.m_DestinationRadius)
{
try
{
m_OnAttracted.Invoke();
}
catch (Exception e)
p.remainingLifetime = 0f;
particles[i] = p;

if (this.m_OnAttracted != null)
{
Debug.LogException(e);
try
{
this.m_OnAttracted.Invoke();
}
catch (Exception e)
{
Debug.LogException(e);
}
}

continue;
}

continue;
}
// Calc attracting time
var delayTime = p.startLifetime * this.m_DelayRate;
var duration = p.startLifetime - delayTime;
var time = Mathf.Max(0, p.startLifetime - p.remainingLifetime - delayTime);

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

// Delay
if (time <= 0) continue;
// Attract
p.position = this.GetAttractedPosition(p.position, dstPos, duration, time);
p.velocity *= 0.5f;
particles[i] = p;
}

// Attract
p.position = GetAttractedPosition(p.position, dstPos, duration, time);
p.velocity *= 0.5f;
particles[i] = p;
particleSystem.SetParticles(particles, count);
}

m_ParticleSystem.SetParticles(particles, count);
}

private Vector3 GetDestinationPosition()
private Vector3 GetDestinationPosition(UIParticle uiParticle, ParticleSystem particleSystem)
{
var isUI = _uiParticle && _uiParticle.enabled;
var psPos = m_ParticleSystem.transform.position;
var isUI = uiParticle && uiParticle.enabled;
var psPos = particleSystem.transform.position;
var attractorPos = transform.position;
var dstPos = attractorPos;
var isLocalSpace = m_ParticleSystem.IsLocalSpace();
var isLocalSpace = particleSystem.IsLocalSpace();

if (isLocalSpace)
{
dstPos = m_ParticleSystem.transform.InverseTransformPoint(dstPos);
dstPos = particleSystem.transform.InverseTransformPoint(dstPos);
}

if (isUI)
{
var inverseScale = _uiParticle.parentScale.Inverse();
var scale3d = _uiParticle.scale3DForCalc;
var inverseScale = uiParticle.parentScale.Inverse();
var scale3d = uiParticle.scale3DForCalc;
dstPos = dstPos.GetScaled(inverseScale, scale3d.Inverse());

// Relative mode
if (_uiParticle.positionMode == UIParticle.PositionMode.Relative)
if (uiParticle.positionMode == UIParticle.PositionMode.Relative)
{
var diff = _uiParticle.transform.position - psPos;
var diff = uiParticle.transform.position - psPos;
diff.Scale(scale3d - inverseScale);
diff.Scale(scale3d.Inverse());
dstPos += diff;
Expand Down Expand Up @@ -237,25 +274,59 @@ private Vector3 GetAttractedPosition(Vector3 current, Vector3 target, float dura
return Vector3.MoveTowards(current, target, speed);
}

private void ApplyParticleSystem()
private void CollectUIParticlesIfNeeded()
{
_uiParticle = null;
if (m_ParticleSystem == null)
if (m_ParticleSystems.Count == 0 || _uiParticles.Count != 0) return;

// Expand capacity
if (_uiParticles.Capacity < m_ParticleSystems.Capacity)
{
#if UNITY_EDITOR
if (Application.isPlaying)
#endif
_uiParticles.Capacity = m_ParticleSystems.Capacity;
}

// Find UIParticle that controls the ParticleSystem
for (var i = 0; i < m_ParticleSystems.Count; i++)
{
var ps = m_ParticleSystems[i];
if (ps == null)
{
Debug.LogError("No particle system attached to particle attractor script", this);
_uiParticles.Add(null);
continue;
}

return;
var uiParticle = ps.GetComponentInParent<UIParticle>(true);
_uiParticles.Add(uiParticle.particles.Contains(ps) ? uiParticle : null);
}
}

#if UNITY_EDITOR
private void OnValidate()
{
_uiParticles.Clear();
}
#endif

void ISerializationCallbackReceiver.OnBeforeSerialize()
{
UpgradeIfNeeded();
}

_uiParticle = m_ParticleSystem.GetComponentInParent<UIParticle>(true);
if (_uiParticle && !_uiParticle.particles.Contains(m_ParticleSystem))
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
}

private void UpgradeIfNeeded()
{
// Multiple ParticleSystems support: from 'm_ParticleSystem' to 'm_ParticleSystems'
if (m_ParticleSystem != null)
{
_uiParticle = null;
if (!m_ParticleSystems.Contains(m_ParticleSystem))
{
m_ParticleSystems.Add(m_ParticleSystem);
}

m_ParticleSystem = null;
Debug.Log($"Upgraded!");
}
}
}
Expand Down

0 comments on commit e0a6381

Please sign in to comment.