Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Access to the particle system lifecycle events

Discussion in 'Scripting' started by Eugenio, May 28, 2015.

  1. Eugenio

    Eugenio

    Joined:
    Mar 21, 2013
    Posts:
    197
    Hello forum.
    My question is kind of tricky and I really don't know if there is a way to do this.
    I have some fireworks particle effect.
    The particle will trigger a trail and in the sub emitters, in the death event is gonna trigger the explosion.
    Nice and clean :)
    Now... I would like to trigger sfx for this particles.
    Is there any way for me to detect when the particle will call the birth sub emitter event and also its death one? If this is possible I can trigger the trail sfx when the particle will come to life and the explosion sfx when it's dying.

    Cheers :)
     
  2. Eugenio

    Eugenio

    Joined:
    Mar 21, 2013
    Posts:
    197
    So... this is my own solution. If you have a better idea, please, share with me :)
    Thanks.

    Code (CSharp):
    1.     public ParticleSystem m_system;
    2.  
    3.     private ParticleSystem.Particle[] m_particles;
    4.  
    5.     void LateUpdate()
    6.     {
    7.         if(m_particles == null || m_particles.Length < m_system.maxParticles)
    8.         {
    9.             m_particles = new ParticleSystem.Particle[m_system.maxParticles];
    10.         }
    11.  
    12.         m_system.GetParticles(m_particles);
    13.  
    14.         // process only the particles that are alive.
    15.         foreach(ParticleSystem.Particle particle in m_particles)
    16.         {
    17.             if(particle.startLifetime - particle.lifetime < 0.01f)
    18.             {
    19.                 // the single particle has been spawned not a long ago.
    20.              
    21.                 // TODO - do what you need.
    22.              
    23.                 StartCoroutine(ParticleLifeEnding(particle.lifetime - 0.01f));
    24.             }
    25.         }
    26.     }
    27.  
    28.     private IEnumerator ParticleLifeEnding(float lifetime)
    29.     {
    30.         yield return new WaitForSeconds(lifetime);
    31.      
    32.         // TODO - do what you need.
    33.     }
    So, basically frame by frame take all the alive particles and check them one by one. If one has spawned not long ago it was born. Then just "wait" for its lifetime so it will be possible to know when it's about to die.
    For me is working.... but I really would like to be able to receive events from the particle system itself.
     
    Dante_CyberdeckGames likes this.
  3. Dante_CyberdeckGames

    Dante_CyberdeckGames

    Joined:
    Mar 14, 2015
    Posts:
    4
    I was also very surprised that these events are not exposed, makes the sub-emitter feature almost worthless without being able to attach sound and other events to birth and death.

    Here is a script I wrote derived from your example. I changed the condition for the triggers so there is no longer any room for error depending on the particle system settings. I used this to attach a boom sound to the explosion of the standard assets fireworks, which spawns the explosion effects in a subemitter on particle death.

    Also I decided to use an old style for loop because the ParticleSystem.Particle is a struct. Structs are copied in memory as values whenever passed around, unlike class objects. Using index accessors to the ParticleSystem.Particle array prevents unneeded copies.

    Code (CSharp):
    1.  
    2.     class FX_Explosion : MonoBehaviour
    3.     {
    4.         ParticleSystem m_particleSystem;
    5.         ParticleSystem.Particle[] m_particles;
    6.         float[] m_times;
    7.  
    8.         void Awake()
    9.         {
    10.             m_particleSystem = GetComponent<ParticleSystem>();
    11.             m_times = new float[m_particleSystem.maxParticles];
    12.             m_particles = new ParticleSystem.Particle[m_particleSystem.maxParticles];
    13.         }
    14.  
    15.         void LateUpdate()
    16.         {
    17.             m_particleSystem.GetParticles(m_particles);
    18.             for (int i = 0; i < m_particles.Length; ++i)
    19.             {
    20.                 if (m_times[i] < m_particles[i].lifetime && m_particles[i].lifetime > 0)
    21.                 {
    22.                     // Birth
    23.                     StartCoroutine(Play(m_particles[i].lifetime));
    24.                 }
    25.  
    26.                 m_times[i] = m_particles[i].lifetime;
    27.  
    28.             }
    29.         }
    30.  
    31.         IEnumerator Play(float lifetime)
    32.         {
    33.             yield return new WaitForSeconds(lifetime);
    34.             // Death (If spawning a sub-emitter on death, this is when that is triggered)
    35.         }
    36.     }
     
    Last edited: Sep 15, 2015
  4. MatiasPi

    MatiasPi

    Joined:
    Jul 13, 2015
    Posts:
    6
    @Dante_CyberdeckGames

    I've tried your solution, but it does skip particles. Some particles do not get processed. I wrote another solution that works all the time, as far as I've tested. It stores the lifetime of the youngest particle of the previous frame. If the lifetime of the youngest particle in the current frame is lower than that, then it means that a new particle has been born. Below is an example that changes the color of every particle at the time they are born.

    It would be REALLY nice for Unity to add Particle System Events we can listen to, like "OnBirth(Particle particle)", for example. In the meantime, we'll have to do things like these:


    Code (CSharp):
    1.  
    2.  
    3. ParticleSystem partSys;
    4. ParticleSystem.Particle[] particles;
    5. float lowestLifetime = 99f;
    6.  
    7. void Start () {
    8.   partSys = GetComponent<ParticleSystem>();
    9.   particles = new ParticleSystem.Particle[partSys.main.maxParticles];
    10.   }
    11.  
    12.  
    13.  
    14. void LateUpdate () {
    15.  
    16.   if (partSys.particleCount == 0)
    17.   return;
    18.  
    19.  
    20.   var numParticlesAlive = partSys.GetParticles(particles);
    21.  
    22.   float youngestParticleLifetime;
    23.   var part = GetYoungestParticle(numParticlesAlive, particles, out youngestParticleLifetime);
    24.   if (lowestLifetime > youngestParticleLifetime) {
    25.   particles[part].startColor = new Color(1.0f, (Mathf.Sin(Time.time * 2) + 1) / 2 * 0.8f, 0.392f + (Mathf.Cos(Time.time * 2) + 1) / 2 * 0.5f);
    26.   partSys.SetParticles(particles, numParticlesAlive);
    27.   }
    28.  
    29.   lowestLifetime = youngestParticleLifetime;
    30.   }
    31.  
    32.   int GetYoungestParticle(int numPartAlive, ParticleSystem.Particle[] particles, out float lifetime) {
    33.   int youngest = 0;
    34.  
    35.   // Change only the particles that are alive
    36.   for (int i = 0; i < numPartAlive; i++) {
    37.  
    38.   if(i == 0) {
    39.   youngest = 0;
    40.   continue;
    41.   }
    42.  
    43.   if (particles[i].remainingLifetime > particles[youngest].remainingLifetime)
    44.   youngest = i;
    45.   }
    46.  
    47.   lifetime = particles[youngest].startLifetime - particles[youngest].remainingLifetime;
    48.  
    49.   return youngest;
    50.  
    51.   }
    EDIT: I have posted this idea on feedback.unity3d.com. Please, vote it if you think this would be a good addition: https://feedback.unity3d.com/suggestions/access-to-the-particle-system-lifecycle-events
     
    Last edited: Dec 11, 2016
  5. bellicapax

    bellicapax

    Joined:
    Oct 5, 2016
    Posts:
    14
    @MatiasPi I took your solution and improved on it a bit. I gave the particles born and die events that other scripts can listen to. I also made sure that particles with different starting lifetimes would get triggered appropriately and an event would be raised for every particle born if multiple were born simultaneously. Thanks for the starting point.

    Code (CSharp):
    1. using UnityEngine;
    2. using Particle = UnityEngine.ParticleSystem.Particle;
    3. using System.Collections.Generic;
    4.  
    5. public class ParticleLifetimeEvents : MonoBehaviour
    6. {
    7.     [SerializeField] private ParticleSystem _particleSystem;
    8.     private Particle[] _particles;
    9.     private float _shortestTimeAlive = float.MaxValue;
    10.     private List<float> _aliveParticlesRemainingTime = new List<float>();
    11.  
    12.     private event System.Action<Particle> _particleWasBorn;
    13.     public event System.Action<Particle> ParticleWasBorn
    14.     {
    15.         add { _particleWasBorn += value; }
    16.         remove { _particleWasBorn -= value; }
    17.     }
    18.     private void RaiseParticleWasBorn(Particle particle) { if (_particleWasBorn != null) { _particleWasBorn(particle); } }
    19.  
    20.     private event System.Action _particleDied;
    21.     public event System.Action ParticleDied
    22.     {
    23.         add { _particleDied += value; }
    24.         remove { _particleDied -= value; }
    25.     }
    26.     private void RaiseParticleDied() { if (_particleDied != null) { _particleDied(); } }
    27.  
    28.     private void Awake()
    29.     {
    30.         _particles = new Particle[_particleSystem.main.maxParticles];
    31.     }
    32.  
    33.     private void LateUpdate()
    34.     {
    35.         TryBroadcastParticleDeath();
    36.  
    37.         if (_particleSystem.particleCount == 0)
    38.             return;
    39.  
    40.         var numParticlesAlive = _particleSystem.GetParticles(_particles);
    41.  
    42.         float youngestParticleTimeAlive = float.MaxValue;
    43.         var youngestParticles = GetYoungestParticles(numParticlesAlive, _particles, ref youngestParticleTimeAlive);
    44.         if (_shortestTimeAlive > youngestParticleTimeAlive)
    45.         {
    46.             for (int i = 0; i < youngestParticles.Length; i++)
    47.             {
    48.                 RaiseParticleWasBorn(youngestParticles[i]);
    49.                 _aliveParticlesRemainingTime.Add(youngestParticles[i].remainingLifetime);
    50.             }
    51.         }
    52.         _shortestTimeAlive = youngestParticleTimeAlive;
    53.     }
    54.  
    55.     private void TryBroadcastParticleDeath()
    56.     {
    57.         for (int i = _aliveParticlesRemainingTime.Count - 1; i > -1; i--)
    58.         {
    59.             _aliveParticlesRemainingTime[i] -= Time.deltaTime;
    60.             if (_aliveParticlesRemainingTime[i] <= 0)
    61.             {
    62.                 _aliveParticlesRemainingTime.RemoveAt(i);
    63.                 RaiseParticleDied();
    64.             }
    65.         }
    66.     }
    67.  
    68.     private Particle[] GetYoungestParticles(int numPartAlive, Particle[] particles, ref float youngestParticleTimeAlive)
    69.     {
    70.         var youngestParticles = new List<Particle>();
    71.         for (int i = 0; i < numPartAlive; i++)
    72.         {
    73.             var timeAlive = particles[i].startLifetime - particles[i].remainingLifetime;
    74.             if (timeAlive < youngestParticleTimeAlive)
    75.             {
    76.                 youngestParticleTimeAlive = timeAlive;
    77.             }
    78.         }
    79.         for (int i = 0; i < numPartAlive; i++)
    80.         {
    81.             var timeAlive = particles[i].startLifetime - particles[i].remainingLifetime;
    82.             if (timeAlive == youngestParticleTimeAlive)
    83.             {
    84.                 youngestParticles.Add(particles[i]);
    85.             }
    86.         }
    87.         return youngestParticles.ToArray();
    88.     }
    89.  
    90.     private void Reset()
    91.     {
    92.         _particleSystem = GetComponent<ParticleSystem>();
    93.     }
    94. }
    95.  
     
    fkuilman and MatiasPi like this.
  6. svadilfari

    svadilfari

    Joined:
    Jul 8, 2017
    Posts:
    1
  7. Malveka

    Malveka

    Joined:
    Nov 6, 2009
    Posts:
    191
    I've recently created an asset that addresses this very need! It's called Participle and it generates events for particle systems, including birth and death, as well conditional events like time lived, distance traveled, distance to transform, etc. It includes a custom inspector that makes it straightforward to select the events to be generated and wire them up to your event handler code. It also allows you to associate any arbitrary data with a particle and retrieve the data at will.

    To learn more, check it out on the Asset Store - Participle or my website Cogent Whir.
     
    MatiasPi likes this.
  8. lanboost

    lanboost

    Joined:
    Feb 6, 2018
    Posts:
    1
    I know this is an old thread, but as the answers doesn't work for example for collision deaths etc...
    And because this is the only thing google showed on the topic

    I made a very UNEFFICIENT working script (for the small test that I have done) and for those who don't want to spend the mega bucks for the above asset store script the idea is as following.

    We can track particle's by a unique id if we use the customparticle data, if no data exist for that particle assign a uid otherwise update that it is still alive.

    The below code generates born events and the subscript can find out the death event on its own.

    Thanks for the code of the above users as I used it as a template.

    Sample code: (Use at own risk)
    Code (CSharp):
    1. using UnityEngine;
    2. using Particle = UnityEngine.ParticleSystem.Particle;
    3. using System.Collections.Generic;
    4.  
    5. public class ParticleWrapper
    6. {
    7.     public float key;
    8.     public bool dead = false;
    9.     public ParticleLifetimeEvents owner;
    10.     int index = 0;
    11.     public Particle GetParticle()
    12.     {
    13.         return owner._particles[index];
    14.     }
    15.  
    16.     public void setIndex(int index)
    17.     {
    18.         this.index = index;
    19.     }
    20. }
    21.  
    22. public class ParticleLifetimeEvents : MonoBehaviour
    23. {
    24.     [SerializeField] private ParticleSystem _particleSystem;
    25.  
    26.     private List<Vector4> customData = new List<Vector4>();
    27.  
    28.  
    29.  
    30.  
    31.     public Particle[] _particles;
    32.     private float _shortestTimeAlive = float.MaxValue;
    33.     private Dictionary<float, ParticleWrapper> wrappers = new Dictionary<float, ParticleWrapper>();
    34.  
    35.     private event System.Action<ParticleWrapper> _particleWasBorn;
    36.     public event System.Action<ParticleWrapper> ParticleWasBorn
    37.     {
    38.         add { _particleWasBorn += value; }
    39.         remove { _particleWasBorn -= value; }
    40.     }
    41.     private void RaiseParticleWasBorn(ParticleWrapper particle) { if (_particleWasBorn != null) { _particleWasBorn(particle); } }
    42.  
    43.     private event System.Action _particleUpdate;
    44.     public event System.Action ParticleUpdate
    45.     {
    46.         add { _particleUpdate += value; }
    47.         remove { _particleUpdate -= value; }
    48.     }
    49.     private void RaiseParticleUpdate() { if (_particleUpdate != null) { _particleUpdate(); } }
    50.  
    51.  
    52.     private void Awake()
    53.     {
    54.         _particles = new Particle[_particleSystem.main.maxParticles];
    55.     }
    56.  
    57.     public float getFreeKey()
    58.     {
    59.         for (float f = 1; f < 1000; f += 1f)
    60.         {
    61.             if(!wrappers.ContainsKey(f))
    62.             {
    63.                 return f;
    64.             }
    65.         }
    66.         return -1;
    67.     }
    68.  
    69.     private void LateUpdate()
    70.     {
    71.         if (_particleSystem.particleCount == 0)
    72.             return;
    73.  
    74.         foreach(var w in wrappers.Values)
    75.         {
    76.             w.dead = true;
    77.         }
    78.  
    79.         _particleSystem.GetCustomParticleData(customData, ParticleSystemCustomData.Custom1);
    80.  
    81.         _particleSystem.GetParticles(_particles);
    82.         for (int i = 0; i < _particleSystem.particleCount; i++)
    83.         {
    84.             if (customData[i].x == 0f)
    85.             {
    86.                
    87.                 customData[i] = new Vector4(getFreeKey(), 0, 0, 0);
    88.                 ParticleWrapper w = new ParticleWrapper();
    89.                 w.owner = this;
    90.                 w.key = customData[i].x;
    91.                 w.setIndex(i);
    92.                 wrappers.Add(w.key, w);
    93.                 //Debug.Log("Spawned: " + w.key);
    94.                 RaiseParticleWasBorn(w);
    95.             }
    96.             else
    97.             {
    98.                 //Debug.Log("Not Dead: " + customData[i].x);
    99.                 wrappers[customData[i].x].dead = false;
    100.                 wrappers[customData[i].x].setIndex(i);
    101.             }
    102.         }
    103.  
    104.         RaiseParticleUpdate();
    105.         //remove old
    106.         List<float> keys = new List<float>(wrappers.Keys);
    107.         foreach (var k in keys)
    108.         {
    109.             if(wrappers[k].dead)
    110.             {
    111.                 //Debug.Log("Dead: " + k);
    112.                 wrappers.Remove(k);
    113.             }
    114.         }
    115.         _particleSystem.SetCustomParticleData(customData, ParticleSystemCustomData.Custom1);
    116.     }
    117.    
    118.  
    119.     private void Reset()
    120.     {
    121.         _particleSystem = GetComponent<ParticleSystem>();
    122.     }
    123. }
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class PrefabEmitter : MonoBehaviour {
    6.     public GameObject prefab;
    7.     // Use this for initialization
    8.  
    9.     Dictionary<ParticleWrapper, GameObject> prefabs = new Dictionary<ParticleWrapper, GameObject>();
    10.  
    11.     public ParticleLifetimeEvents events;
    12.     void Start () {
    13.         events.ParticleWasBorn += delegate (ParticleWrapper p) {
    14.            
    15.             GameObject obj = Instantiate(prefab);
    16.             obj.transform.position = p.GetParticle().position;
    17.             prefabs.Add(p, obj);
    18.             Debug.Log("Was spawn "+p);
    19.            
    20.         };
    21.         events.ParticleUpdate += delegate ()
    22.         {
    23.             List<ParticleWrapper> keys = new List<ParticleWrapper>(prefabs.Keys);
    24.             foreach(var k in keys)
    25.             {
    26.                 if(k.dead)
    27.                 {
    28.                     Destroy(prefabs[k]);
    29.                     prefabs.Remove(k);
    30.                     Debug.Log("Was dead " + k);
    31.                 }
    32.                 else
    33.                 {
    34.                     prefabs[k].transform.position = k.GetParticle().position;
    35.                 }
    36.             }
    37.         };
    38.     }
    39.    
    40.    
    41.     // Update is called once per frame
    42.     void Update () {
    43.        
    44.     }
    45. }
    46.  
     
    naoki8140 likes this.
  9. Malveka

    Malveka

    Joined:
    Nov 6, 2009
    Posts:
    191
    My asset Participle has just been updated to version 1.1. It includes some new features that may be of interest to followers of this thread:

    Version 1.1 Now Available - Changes:
    • Add particleID to the data passed on a particle death event - DeathEventInfo.
    • Add support for attaching game objects to particles via script.
    • Add support for triggering events by particle Speed.
    • New examples for particle attachments/followers and Speed triggers
    The dedicated forum thread is here.
     
    AlejMC likes this.
  10. Aaron-Meyers

    Aaron-Meyers

    Joined:
    Dec 8, 2009
    Posts:
    305
    thanks @bellicapax for the script. I went ahead and modified it to use UnityEvents because I like to be able to expose them through the inspector. Posting my modified version here for anyone who finds this thread in the future!


    Code (CSharp):
    1. using UnityEngine;
    2. using Particle = UnityEngine.ParticleSystem.Particle;
    3. using System.Collections.Generic;
    4. using UnityEngine.Events;
    5. using System;
    6.  
    7. public class ParticleLifetimeEvents : MonoBehaviour
    8. {
    9.     [Serializable] public class ParticleLifetimeEvent : UnityEvent<Particle> { }
    10.     [SerializeField] private ParticleSystem _particleSystem;
    11.     private Particle[] _particles;
    12.     private float _shortestTimeAlive = float.MaxValue;
    13.     private List<float> _aliveParticlesRemainingTime = new List<float>();
    14.     [SerializeField] ParticleLifetimeEvent _particleWasBorn = new ParticleLifetimeEvent();
    15.     [SerializeField] UnityEvent _particleDied = new UnityEvent();
    16.     public ParticleLifetimeEvent particleWasBorn { get { return _particleWasBorn; } }
    17.     public UnityEvent particleDied { get { return _particleDied; } }
    18.     private void Awake()
    19.     {
    20.         _particles = new Particle[_particleSystem.main.maxParticles];
    21.     }
    22.     private void LateUpdate()
    23.     {
    24.         TryBroadcastParticleDeath();
    25.         if (_particleSystem.particleCount == 0)
    26.             return;
    27.         var numParticlesAlive = _particleSystem.GetParticles(_particles);
    28.         float youngestParticleTimeAlive = float.MaxValue;
    29.         var youngestParticles = GetYoungestParticles(numParticlesAlive, _particles, ref youngestParticleTimeAlive);
    30.         if (_shortestTimeAlive > youngestParticleTimeAlive)
    31.         {
    32.             for (int i = 0; i < youngestParticles.Length; i++)
    33.             {
    34.                 _particleWasBorn.Invoke( youngestParticles[i] );
    35.                 _aliveParticlesRemainingTime.Add(youngestParticles[i].remainingLifetime);
    36.             }
    37.         }
    38.         _shortestTimeAlive = youngestParticleTimeAlive;
    39.     }
    40.     private void TryBroadcastParticleDeath()
    41.     {
    42.         for (int i = _aliveParticlesRemainingTime.Count - 1; i > -1; i--)
    43.         {
    44.             _aliveParticlesRemainingTime[i] -= Time.deltaTime;
    45.             if (_aliveParticlesRemainingTime[i] <= 0)
    46.             {
    47.                 _aliveParticlesRemainingTime.RemoveAt(i);
    48.                 _particleDied.Invoke();
    49.             }
    50.         }
    51.     }
    52.     private Particle[] GetYoungestParticles(int numPartAlive, Particle[] particles, ref float youngestParticleTimeAlive)
    53.     {
    54.         var youngestParticles = new List<Particle>();
    55.         for (int i = 0; i < numPartAlive; i++)
    56.         {
    57.             var timeAlive = particles[i].startLifetime - particles[i].remainingLifetime;
    58.             if (timeAlive < youngestParticleTimeAlive)
    59.             {
    60.                 youngestParticleTimeAlive = timeAlive;
    61.             }
    62.         }
    63.         for (int i = 0; i < numPartAlive; i++)
    64.         {
    65.             var timeAlive = particles[i].startLifetime - particles[i].remainingLifetime;
    66.             if (timeAlive == youngestParticleTimeAlive)
    67.             {
    68.                 youngestParticles.Add(particles[i]);
    69.             }
    70.         }
    71.         return youngestParticles.ToArray();
    72.     }
    73.     private void Reset()
    74.     {
    75.         _particleSystem = GetComponent<ParticleSystem>();
    76.     }
    77. }
    78.  
     
    GazaG, AlejMC and tannic like this.
  11. tannic

    tannic

    Joined:
    Nov 6, 2016
    Posts:
    5
    Hi Aaron,
    I am trying your script. I have attached it to my "fireworks" particle system.
    How am i able to catch the events, so I can play a sound when a particle is born and died ?
    I am new to the Events, but it looks like the right way to go here.
    Also: the lists ParticleWasBorn and ParticleDied are empty in the inspector.
    Please give me a hint or two. thanks.
     
    AlejMC likes this.
  12. AlejMC

    AlejMC

    Joined:
    Oct 15, 2013
    Posts:
    149
    Woah, getting your asset. Looks quite complete and not to mention, up to date.
    I understand that the VFX Graph is all the new rage but for 99% of the practical cases out there Shuriken+events is really way enough.
    And actually, performance-wise, I think Shuriken is not badly placed... it forwards a lot of the calls and logic to C++ side, that's why working with the particle modules is quirky in C# land, you get a struct like "main module" but it behaves like a reference, etc
    Giving it a test ride.
     
    Malveka likes this.
  13. Malveka

    Malveka

    Joined:
    Nov 6, 2009
    Posts:
    191
    Thanks, Alejandro! I hope it works out for you. If you have any questions, don't hesitate to get in touch. There is a thread dedicated to Participle here.

    As you say, VFX Graph is a great addition to Unity, but there are still many cases where Shuriken is a better fit for the task at hand. I use them both.
     
    AlejMC likes this.
  14. Chikanut

    Chikanut

    Joined:
    May 21, 2014
    Posts:
    1
    Hi, here is my solution to this problem, unlike the answers above it works with particles when they die on collision-trigger events. I'm using custom data of the particle system, to set a unique ID for each particle and compare it with the old IDs list. I just had the problem with the solution above that when a particle was triggering with an object, the action of particle death hadn't been executed. I know that we could listen to particle trigger-collision events, but again, my method works for any particle kill event ;) I hope this will help someone!

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using System.Linq;
    3. using UnityEngine;
    4. using UnityEngine.Events;
    5.  
    6. [RequireComponent(typeof(ParticleSystem))]
    7. public class ParticleSystemActions : MonoBehaviour
    8. {
    9.     [Header("Events")]
    10.     [SerializeField] UnityEvent _particleWasBorn = new UnityEvent();
    11.     [SerializeField] UnityEvent _particleDead = new UnityEvent();
    12.  
    13.     private ParticleSystem _particleSystem;
    14.  
    15.     private int _uniqueID;
    16.     private List<float> _currentParticlesIds = new List<float>();
    17.  
    18.     private void Awake()
    19.     {
    20.         _particleSystem = GetComponent<ParticleSystem>();
    21.  
    22.         if (_particleSystem == null)
    23.         {
    24.             Debug.LogError("Missing particle system!", this);
    25.         }
    26.     }
    27.  
    28.     private void LateUpdate()
    29.     {
    30.         if (_particleSystem == null)
    31.         {
    32.             return;
    33.         }
    34.      
    35.         UpdateLifeEvents();
    36.     }
    37.  
    38.     void UpdateLifeEvents()
    39.     {
    40.         var customData = new List<Vector4>();
    41.         _particleSystem.GetCustomParticleData(customData, ParticleSystemCustomData.Custom1);
    42.  
    43.         for (var i = 0; i < customData.Count; i++)
    44.         {
    45.             if (customData[i].x != 0.0f)
    46.             {
    47.                 continue;
    48.             }
    49.          
    50.             customData[i] = new Vector4(++_uniqueID, 0, 0, 0);
    51.          
    52.             _particleWasBorn?.Invoke();
    53.             _currentParticlesIds.Add(customData[i].x);
    54.  
    55.             if (_uniqueID > _particleSystem.main.maxParticles)
    56.                 _uniqueID = 0;
    57.         }
    58.  
    59.         var ids = customData.Select(d => d.x).ToList();
    60.         var difference = _currentParticlesIds.Except(ids).Count();
    61.  
    62.         for (int i = 0; i < difference; i++)
    63.         {
    64.             _particleDead?.Invoke();
    65.         }
    66.  
    67.         _currentParticlesIds = ids;
    68.         _particleSystem.SetCustomParticleData(customData, ParticleSystemCustomData.Custom1);
    69.     }
    70. }
    71.  
     
    Duckocide and Elmstrom like this.
  15. CodeWithKyrian

    CodeWithKyrian

    Joined:
    Aug 17, 2017
    Posts:
    9
    @Chikanut, thanks for this. Your implementation of this using Custom Data was the most accurate for me. No Particles were missed. However, I'm not really comfortable with creating a new customData List every frame. That doesn't look good for me. So I did a slight modification to account for that
    Code (CSharp):
    1.     using System.Collections.Generic;
    2.     using System.Linq;
    3.     using UnityEngine;
    4.     using UnityEngine.Events;
    5.    
    6.     [RequireComponent(typeof(ParticleSystem))]
    7.     public class ParticleSystemActions : MonoBehaviour
    8.     {
    9.         [Header("Events")]
    10.         [SerializeField] UnityEvent _particleWasBorn = new UnityEvent();
    11.         [SerializeField] UnityEvent _particleDead = new UnityEvent();
    12.    
    13.         private ParticleSystem _particleSystem;
    14.    
    15.         private int _uniqueID;
    16.         private List<float> _currentParticlesIds = new List<float>();
    17.         private List<Vector4> _customData = new List<Vector4>();
    18.    
    19.         private void Awake()
    20.         {
    21.             _particleSystem = GetComponent<ParticleSystem>();
    22.    
    23.             if (_particleSystem == null)
    24.             {
    25.                 Debug.LogError("Missing particle system!", this);
    26.             }
    27.         }
    28.    
    29.         private void LateUpdate()
    30.         {
    31.             if (_particleSystem == null)
    32.             {
    33.                 return;
    34.             }
    35.        
    36.             UpdateLifeEvents();
    37.         }
    38.    
    39.         void UpdateLifeEvents()
    40.         {
    41.             _particleSystem.GetCustomParticleData(customData, ParticleSystemCustomData.Custom1);
    42.    
    43.             for (var i = 0; i < _particleSystem.particleCount; i++)
    44.             {
    45.                 if (customData[i].x != 0.0f)
    46.                 {
    47.                     continue;
    48.                 }
    49.            
    50.                 customData[i] = new Vector4(++_uniqueID, 0, 0, 0);
    51.            
    52.                 _particleWasBorn?.Invoke();
    53.                 _currentParticlesIds.Add(customData[i].x);
    54.    
    55.                 if (_uniqueID > _particleSystem.main.maxParticles)
    56.                     _uniqueID = 0;
    57.             }
    58.    
    59.             var ids = customData.Select(d => d.x).ToList();
    60.             var difference = _currentParticlesIds.Except(ids).Count();
    61.    
    62.             for (int i = 0; i < difference; i++)
    63.             {
    64.                 _particleDead?.Invoke();
    65.             }
    66.    
    67.             _currentParticlesIds = ids;
    68.             _particleSystem.SetCustomParticleData(customData, ParticleSystemCustomData.Custom1);
    69.         }
    70.     }
    71.    
    72.  
     
    kyle_lam and Duckocide like this.
  16. Voxel-Busters

    Voxel-Busters

    Joined:
    Feb 25, 2015
    Posts:
    1,952
    Here is a quick script to trigger start and stop events of particle system.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Events;
    3.  
    4. namespace BubbleBuster
    5. {
    6.     internal enum ParticleSystemState
    7.     {
    8.         Idle,
    9.         Playing,
    10.         Stopped
    11.     }
    12.  
    13.     [RequireComponent(typeof(ParticleSystem))]
    14.     public class ParticleSystemLifeCycleBroadcaster : MonoBehaviour
    15.     {
    16.  
    17.         [SerializeField]
    18.         private UnityEvent m_started;
    19.  
    20.         [SerializeField]
    21.         private UnityEvent m_stopped;
    22.  
    23.         [SerializeField]
    24.         private bool m_autoDisable = true;
    25.  
    26.         private ParticleSystem m_particleSystem;
    27.  
    28.         private ParticleSystemState m_state = ParticleSystemState.Idle;
    29.  
    30.         private void Awake()
    31.         {
    32.             m_particleSystem = GetComponent<ParticleSystem>();  
    33.         }
    34.  
    35.         private void LateUpdate()
    36.         {
    37.             if (m_state == ParticleSystemState.Idle && m_particleSystem.particleCount == 0)
    38.                 return;
    39.  
    40.             if(m_state == ParticleSystemState.Idle  && m_particleSystem.IsAlive(false))
    41.             {
    42.                 m_state = ParticleSystemState.Playing;
    43.                 m_started?.Invoke();
    44.             }
    45.             else if(m_state == ParticleSystemState.Playing && !m_particleSystem.IsAlive(true))
    46.             {
    47.                 m_state = ParticleSystemState.Stopped;
    48.                 m_stopped?.Invoke();
    49.  
    50.                 if (m_autoDisable)
    51.                     enabled = false;
    52.             }
    53.         }
    54.     }
    55. }
     
    MrPapayaMan and hamza_unity995 like this.