Search Unity

[RELEASED] Participle - Particle Event Dispatcher

Discussion in 'Assets and Asset Store' started by Malveka, Feb 5, 2018.

  1. Malveka

    Malveka

    Joined:
    Nov 6, 2009
    Posts:
    191
    Version 1.2 Now Available - Changes:
    • Add option to perform particle event dispatch during either Update (default) or LateUpdate. Use of LateUpdate generally yields more accurate positioning of particles with respect to game objects in the world. The default remains Update for backwards compatibility for existing users. When creating new effects it is recommended to select LateUpdate.
    • Add new methods SetClientDataByKey() and GetClientDataByKey() for storing and retrieving client data by key. Storing client data by key allows multiple scripts to store, read and modify only the data that is of interest to that script, without the need to be aware that other scripts may also be modifying client data for the same particle.
    • Add Clean Up On Disable option. Enabling the option will cause all non-persistent event listeners (i.e. those added by using the API) to be removed when the particle system is disabled. This is useful when you need to enable and disable a particle system object during a scene. If a particle system object is always enabled or you haven't added Participle event triggers using the runtime API, you don't need to set this.
    • Add new Spirograph example that explicitly emits particles at their proper starting locations to prevent "trail spikes". An issue with using birth events and then moving particles to a desired starting position is that, if trails are enabled for the particle system, you will get a trail segment drawn from the emission position to the desired starting position. Since there is no way to enable/disable trail generation for individual particles, the only way to avoid these trails spikes is to make sure that the particles are emitted at their desired starting position.
    • Fix issue that was causing bizarre wave drawing in the Frame_SineWaves example. Needed to reorder calls to SetParticles() and SetCustomParticleData() in the particle event dispatcher. This issue has been reported to Unity and was investigated. They confirmed that it is a Unity issue, but the risks of changing ("fixing") the behavior are substantial. Fortunately, reordering the aforementioned calls seems to fix it. For more info see case 1299919 in the Unity issue tracker.
    • Fix the event info "particle" structure not being up to date when multiple events were being dispatched for the same particle in the same frame.
    • Fix issue in EH_FollowParticles to prevent null references in the Follow method. If follower particles are emitted independently of the InitFollower method (e.g. Emission module is active), there were previously null reference exceptions in the Follow method. This wasn't a problem with the example scene associated with this script, but a user encountered it when they adapted the example script for a different purpose.
    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.

    Participle is an asset that makes it easy to script creative and novel behaviors for particle effects. At its core, Participle is a flexible event dispatcher that offers convenient access to the state of individual particles within Unity’s Shuriken particle system. It makes it simple to construct effects that respond to fundamental events, such as particle birth and death, as well as more sophisticated conditions like the length of time a particle has lived or the distance it has traveled.

    The kernel of the Participle asset emerged from my work on creating real-time, generative art to be embedded in glass sculptures. When I considered the widespread use of particle effects in Unity apps of every description, however, I realized that my small seed of an idea could be grown into a general purpose asset that could benefit many. It is my hope and my goal that this proves to be the case.



    Examples of support for causing game objects to follow particles:



    Participle offers a custom inspector that makes setting up basic and conditional trigger events straightforward. It also offers a full featured programming API.

    Features:
    • Built-in event triggers - Birth, death, time lived, distance traveled, remaining lifetime, size, distance to transform, every frame.
    • Event trigger modes - OneShot, Repeat, RepeatByIncrement, RepeatByDecrement
    • Custom event triggers - User specified evaluator to determine the conditions that will cause an event to be dispatched.
    • Particle attachments - Built-in support for causing game objects to follow individual particles.
    • Client data - Store and retrieve arbitrary application data on a per particle basis.
    • Comprehensive API - Create and remove event triggers from code.
    • Complete documentation
    • Lots of examples

    Cogent Whir Web Site

    Participle Documentation

    Participle on the Asset Store
     
    Last edited: Mar 19, 2021
  2. DanielleBBI

    DanielleBBI

    Joined:
    Mar 28, 2018
    Posts:
    30
    Is it possible to add the particle ID to the death event info? I am trying to attach a prefab to the particle that will get destroyed when the particle dies. I am new to scripting so maybe there is a way to do it that I am not aware of? (I am currently keeping the id and attached prefab in a static class list)
     
  3. Malveka

    Malveka

    Joined:
    Nov 6, 2009
    Posts:
    191
    As you noted, the particleID is not currently passed with the death event info. My reasoning was that since the particle is now dead, the particleID is no longer valid and attempting to use it with Participle interfaces could have undesired consequences. I think, however, that decision may need to be reconsidered. In sounds like in your case you are using the particleID as an index or locator in your own data structure. Is that the case? If so, I can see why it would be desirable to get the particleID on the death event.

    As an alternative, have you considered using the ClientData mechanism to store your prefab with the particle event dispatcher itself? Here are the relevant interfaces:

    Code (CSharp):
    1.  
    2.         /// <summary>
    3.         /// Set the client data for a specified particle. If the specified particle does
    4.         /// not exist (e.g. is dead), no action will be taken.
    5.         /// <param name='particleID'>
    6.         /// The particle identifier. Is included in the data associated with a particle event.
    7.         /// </param>
    8.         /// <param name='clientData'>
    9.         /// The data to be associated with the specified particle.
    10.         /// The client defines a class that inherits from ParticleInfoClientData and contains
    11.         /// the client data.
    12.         /// </param>
    13.         /// </summary>
    14.         public void SetClientData(uint particleID, ParticleInfoClientData clientData) {
    15.  
    16.             if (particleInfo.ContainsKey(particleID)) {
    17.                 particleInfo[particleID].clientData = clientData;
    18.             }
    19.         }
    20.  
    21.         /// <summary>
    22.         /// Get the client data for a specified particle. If the specified particle does
    23.         /// not exist, returns null.
    24.         /// <param name='particleID'>
    25.         /// The particle identifier. It is included in the data associated with a particle event.
    26.         /// </param>
    27.         /// </summary>
    28.         /// To-Do:  Document storage mgmt. aspects of this interface, e.g. this is a ref to the
    29.         /// client data, not a copy of the data.  When particle dies, ref will become null.
    30.         public ParticleInfoClientData GetClientData(uint particleID) {
    31.  
    32.             if (particleInfo.ContainsKey(particleID)) {
    33.                 return particleInfo[particleID].clientData;
    34.             } else {
    35.                 return null;
    36.             }
    37.         }
    38.  
    39.         /// <summary>
    40.         /// Set a value for the client data to be populated for each particle on birth.
    41.         /// If this data is to apply to every particle birthed by the particle system,
    42.         /// this method must be invoked prior to any particle emissions from the system.
    43.         ///
    44.         /// Use of this method is optional. If it is not invoked, clientData for a particle
    45.         /// will be null until SetClientData is called for the particle.
    46.         /// </summary>
    47.         /// <param name='defaultClientData'>
    48.         /// A reference to client data. The reference will be copied to the information
    49.         /// maintained for each particle and will be included with the event information
    50.         /// whenever an event is dispatched.
    51.         /// </param>
    52.         public void SetDefaultClientData(ParticleInfoClientData defaultClientData) {
    53.  
    54.             this.defaultClientData = defaultClientData;
    55.         }
    56.  
    You could use SetClientData to associate your prefab with the particle when it is born. When the death event is issued, a reference to the client data for the particle is included in the event info. Likewise, a client data reference is passed along with any conditional events issued by Participle. This way you could access your prefab directly any time an event is issued for that particle.

    A typical pattern that I use is to store client data with a particle when it is born, access the client data when particle events occur and then do any needed clean-up when the particle dies.

    Let me know if you think this approach will or will not work for you, or if my explanation was not clear. I'll do what I can to help.
     
  4. DanielleBBI

    DanielleBBI

    Joined:
    Mar 28, 2018
    Posts:
    30
    Thanks for the help! I will give SetClientData a try. I am using the particleID as an index so I can keep the connection between the particle and the game object that's attached to it (I basically want to slave a particle prefab to a particle and have it follow it around.) at the moment I found a workaround and I depend on a life remains trigger to kill the slaved system just before the particle dies so I don't lose the connection. Your way seems better :D Thanks!
     
  5. Malveka

    Malveka

    Joined:
    Nov 6, 2009
    Posts:
    191
    You're very welcome! Let me know if you run into trouble with that approach. I'm going to consider passing the particleID on the death event for the next update to Participle.

    BTW, there's a separate thread on setting up a particle follower, where I gave an example of using Participle. It's a bit different from what you're trying to do, but if you haven't yet seen it, it may be of interest to you. It's here.

    James
     
  6. DanielleBBI

    DanielleBBI

    Joined:
    Mar 28, 2018
    Posts:
    30
    Is there an example in the tutorials on how to use the SetClientData? Like I said I am very new to scripting :D
     
  7. DanielleBBI

    DanielleBBI

    Joined:
    Mar 28, 2018
    Posts:
    30
    Thanks for the quick answers! I looked at the example and it's not exactly what I am trying to do. In the end, the idea is to use the slaved system as a "particle" (so it can have movement and transitions but it will move together) while the main system isn't rendered I just use its physics to move the slave particles

    Thanks again! Danielle
     
  8. DanielleBBI

    DanielleBBI

    Joined:
    Mar 28, 2018
    Posts:
    30
    Never mind... found plenty in the examples :D Thanks again!
     
  9. Malveka

    Malveka

    Joined:
    Nov 6, 2009
    Posts:
    191
    Yes, that makes sense. If you wanted, I think you could use a similar structure to the example in the other thread, except that when InitFollowed is invoked (either in response to a birth or other event for a master particle) you would instantiate your prefab then store a reference to it in the client data for that master particle. Then in response to a Frame event for the master particle (UpdateFollowed in the example), you would just need to access the prefab in the client data and update its position to that of the master particle. Something like that. I think you wouldn't need the pending queue or the Follow event handler from the other thread's example.

    One thing to watch out for, regardless of your approach, is to make sure that you account for whether the master particle system is operating in local or world space when you use its position to set that of the following particle system's transform. There are some ParticleHelper methods to assist with this. You can see an example of that in EH_SpawnParticlePrefab.cs.

    Best of luck with your project!
    James
     
  10. Malveka

    Malveka

    Joined:
    Nov 6, 2009
    Posts:
    191
    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.
    I'm especially excited about the new support for attaching game objects to particles. Here are some examples:



    These effects were all achieved through the use of simple API calls like this:

     dispatcher.AddFollowerAttachment(particleID, gameObjectFollower, followerDistance,
    followerSmoothing, alignToTravel);


    Multiple attachments are supported per particle. Game objects can be attached to follow the particle at a specified distance or by relative position to the particle. When setting up a follower, you can specify a smoothing factor for the attachment. In the video, the "wiggly bug-like" effect is achieved by attaching multiple followers to a single particle, where each follower is at an increasing distance and has a slightly larger smoothing factor.
     
    AlejMC likes this.
  11. TechnicalArtist

    TechnicalArtist

    Joined:
    Jul 9, 2012
    Posts:
    736
    Amazing tool.
    Its take particle to next level.
     
    AlejMC and Malveka like this.
  12. Malveka

    Malveka

    Joined:
    Nov 6, 2009
    Posts:
    191
    Thanks! If you are finding the asset truly useful, a review on the asset store would be much appreciated!
     
    TechnicalArtist likes this.
  13. timkff

    timkff

    Joined:
    May 1, 2018
    Posts:
    1
    Hi Malveka, first off congratulations making a really useful particle tool. We're considering using Participle but we're working a mobile game for iOS and Android that is already taxing the hardware and don't have much extra memory or computation time to spare as is. Is there anything that Participle is doing that would particularly memory or CPU/GPU intensive? We're mainly looking at Participle as a way to add callbacks or events to a particles life cycle similar to how you can add animation events in the Animation system.
     
  14. Malveka

    Malveka

    Joined:
    Nov 6, 2009
    Posts:
    191
    Thanks for your interest in Participle! Pretty much all of Participle's activity happens on the CPU, so it should not impact your GPU budget at all. On the CPU side, it is primarily sensitive to the number of particles in a given effect and also the frequency of occurrence of events to be dispatched. So if you have effects that are in the tens or hundreds of particles, my experience has been that the overhead is quite low. Once you start to get into the thousands, with frequent events (e.g. if you select to trigger every Frame, an event will fire for every particle every frame) the overhead will begin to increase.

    If you like, send me a direct message here on the forums or e-mail me at "support at-thingy cogentwhir dot com" and we'll work out a way for you to try Participle risk-free.
     
  15. melotraumatic

    melotraumatic

    Joined:
    Jul 24, 2013
    Posts:
    3
    Hello!

    I've been trying rather unsuccessfully to get individual particles to stick to a moving mesh when they collide with it. Do you think that your asset might help me achieve that?
     
    Malveka likes this.
  16. Malveka

    Malveka

    Joined:
    Nov 6, 2009
    Posts:
    191
    Participle has no explicit support for dealing with collisions. That being said, there are a couple of approaches that come to mind for how you could achieve the effect you desire:

    • With Participle - make use of the built-in Participle support for attaching objects to a particle. You could attach a game object to each particle at birth, where the game object had a collider component on it. Then you could use all the standard techniques for dealing with collisions between physics objects. This might be quite a good option as long as the number of particles is "reasonable".

    • Without Participle - make use of the ParticleSystem Triggers module and the OnParticleTrigger() method. Put a script with the OnParticleTrigger method on the game object that also contains the ParticleSystem component. Whenever a collision occurs between a particle of this particle system and a collider listed in the Triggers module, OnParticleTrigger will be called. The drawback to this approach is that all the potential colliders must be listed explicitly in the triggers module. Depending on how many moving meshes you have and when they are created etc., this might or might not be an issue. The code in OnParticleTrigger might look something like this (this is sample code that is incomplete, but I think it conveys the idea):
    Code (CSharp):
    1. public void OnParticleTrigger()
    2.         {
    3.  
    4.             // get the particles which matched the collision trigger conditions this frame
    5.             // mainSystem is the particle system of interest
    6.             int numEnter = mainSystem.GetTriggerParticles(ParticleSystemTriggerEventType.Enter,
    7.                 enterList);
    8.  
    9.             //print("OnParticleTrigger called... num Entered=" + numEnter);
    10.  
    11.  
    12.             // Iterate through the particles which entered a trigger collider.
    13.             for (int i = 0; i < numEnter; i++) {
    14.              
    15.                 workParticle = enterList[i];
    16.  
    17.                 // We only know that the particle entered some trigger.  To find out which one,
    18.                 // we do call OverlapSphere.
    19.                 Vector3 particlePos = ParticleHelper.ParticlePositionToWorldSpace(mainSystem, workParticle.position);
    20.                 Collider[] hitColliders = Physics.OverlapSphere(particlePos,
    21.                     workParticle.GetCurrentSize(mainSystem));
    22.  
    23.                 if (hitColliders.Length > 0) {
    24.                     // We're only going to use the first one.
    25.                     Collider hitCollider = hitColliders[0];
    26.  
    27.                     // Attach the particle to the collided mesh here...
    28.                    
    29.                 }
    30.  
    31.             }
    32.              
    33.  
    34.             // re-assign the modified particles back into the particle system
    35.             mainSystem.SetTriggerParticles(ParticleSystemTriggerEventType.Enter, enterList);
    36.         }
    Hope this helps!
     
    AlejMC likes this.