Search Unity

Particle System Custom Data Glitch

Discussion in 'General Graphics' started by Malveka, Dec 15, 2020.

  1. Malveka

    Malveka

    Joined:
    Nov 6, 2009
    Posts:
    191
    In this post I discuss an issue I encountered when making use of the custom data scripting interfaces for a (Shuriken) ParticleSystem. I am still uncertain if the problem is with my code or is unexpected behavior from Unity, but I'm documenting it here in case anyone else runs into a similar problem.

    The behavior I observed was that GetCustomParticleData() would return incorrect data in the following circumstances:
    • Trails are enabled.
    • The trails option "Die With Particles" is not selected.
    • The particle system emission is such that particles are continually being born and dying.
    • SetCustomParticleData() is called after SetParticles() in Update.
    When these conditions were true, I found that GetCustomParticleData() would lose entries for particles that were still living, but retain entries for dead particles. This caused very strange behavior in my particle effects and it took me a few days to track down the source of the issue. I observed the issue in Unity 2018, 2019 and 2020.

    Here is a very simplified script that will cause the problem. This should be attached to a ParticleSystem object with settings as mentioned above.

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. public class Psys_Custom_Data_Bug : MonoBehaviour
    5. {
    6.     private ParticleSystem partSystem;
    7.     private ParticleSystem.MainModule partMain;
    8.     private ParticleSystem.Particle[] particles;
    9.     private int numParticles;
    10.     private List<Vector4> customParticleData = new List<Vector4>();
    11.     private uint nextParticleID = 1;
    12.  
    13.  
    14.     // Start is called before the first frame update
    15.     void Awake()
    16.     {
    17.         partSystem = this.GetComponent<ParticleSystem>();
    18.         partMain = partSystem.main;
    19.         particles = new ParticleSystem.Particle[partMain.maxParticles];
    20.     }
    21.  
    22.     // Update is called once per frame
    23.     void Update()
    24.     {
    25.         // Get all current particles.
    26.         numParticles = partSystem.GetParticles(particles);
    27.         print("Update: numParticles=" + numParticles);
    28.  
    29.         // Get all the custom particle data. Custom data is 1-to-1 with the particle array.
    30.         partSystem.GetCustomParticleData(customParticleData, ParticleSystemCustomData.Custom2);
    31.         print("After GetCustomParticleData.......................");
    32.         for (int i = 0; i < customParticleData.Count; i++) {
    33.             print("customParticleData[" + i + "] = " + customParticleData[i]);
    34.         }
    35.  
    36.         // Loop thru the particles and determine particles that have just been born.
    37.         for (int i = 0; i < numParticles; i++) {
    38.  
    39.             // Assign a unique ID to a particle without an ID.
    40.             // A particle without an ID signifies this particle has just been born.
    41.             if (customParticleData[i].x < 1.0f) {
    42.  
    43.                 customParticleData[i] = new Vector4(nextParticleID, 0, 0, 0);
    44.                 nextParticleID++;
    45.  
    46.             }
    47.         }
    48.  
    49.         // This is the original location of SetParticles.
    50.         // When performed here the result is INCORRECT custom data.
    51.         //partSystem.SetParticles(particles, numParticles);
    52.  
    53.         // Update particle system custom data with new particle ID info.
    54.         print("Before SetCustomParticleData.......................");
    55.         for (int i = 0; i < customParticleData.Count; i++) {
    56.             print("customParticleData[" + i + "] = " + customParticleData[i]);
    57.         }
    58.         partSystem.SetCustomParticleData(customParticleData, ParticleSystemCustomData.Custom2);
    59.  
    60.         // Placing SetParticles() here results in CORRECT custom data.
    61.         partSystem.SetParticles(particles, numParticles);
    62.  
    63.     }
    64. }
    65.  
    Consider a case when we just have two particles. If SetParticles() is called before SetCustomParticleData(), the following behavior is observed:
    1. Particle 1 is emitted.
      1. GetCustomParticleData returns data for particleID 1.
    2. Particle 2 is emitted.
      1. GetCustomParticleData returns data for particleID 1.
      2. GetCustomParticleData returns data for particleID 2.
    3. Particle 1 dies.
      1. GetCustomParticleData returns data for particleID 1 <= INCORRECT
      2. GetCustomParticleData does not return data for particleID 2. <= INCORRECT

    If SetParticles() is called after SetCustomParticleData(), the following behavior is observed:
    1. Particle 1 is emitted.
      1. GetCustomParticleData returns data for particleID 1.
    2. Particle 2 is emitted.
      1. GetCustomParticleData returns data for particleID 1.
      2. GetCustomParticleData returns data for particleID 2.
    3. Particle 1 dies.
      1. GetCustomParticleData returns data for particleID 2 <= CORRECT
    It seems the moral of the story is to make sure you call SetParticles() after SetCustomParticleData(). My code was a lot more complicated than this example, of course, and it took a long time to arrive at this conclusion. If you've encountered this same problem I hope you find this forum post before you spend days debugging it!
     
  2. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,285
    Hi, please submit a bug report for these scenarios, thanks!
     
    Malveka likes this.
  3. Malveka

    Malveka

    Joined:
    Nov 6, 2009
    Posts:
    191
    Hey Richard, thanks for looking into this. Bug report submitted, case number 1299919.
     
    richardkettlewell likes this.