Shuriken sub emitter particle inherit parent particles position

Hi, was wondering if there’s a way to lock a sub emitter’s particle position to the particle that spawned it? I know about inherit velocity but that only seems to be applied once when the sub particle is spawned. For example I want to apply a noise to the parent particles position over time and have the child particle locked to the parents position over time.

TNKS!

G

Inherit velocity is mainly for inheriting the game object’s velocity. It’s a nice bonus that initial velocity also works for sub emitters, I don’t believe it always has. There are kind of three ways to do what you’re trying to do.

Duplicate particle positions with a custom script
Have a script on your particle system that copies a parent particle system’s particle positions. This is the most accurate way to do what you want, but it’s slow and bug prone.

Use a fixed random seed
What I’ve done for more complex setups is disable “Auto Random Seed” and set both particle emitters to use the same seed. Then as long as they share identical emission and velocity values elsewhere they’ll have identical motion making them appear to be linked. This is probably the easiest method.

Custom shader
Have a single shader that renders both particles (assuming the particles are billboards). You can use the custom data properties to allow you to individually scale (by modifying the UVs in the shader) each, as well as animate the color independently if needed.

I don’t know any way to tell which sub-particle is the child of which spawning particle just by looking at the API of Particle. All particles emitted from a sub emitter are treated as a single entity indistinguishably. Most likely you would duplicate the leading particle system, use a script to generate a new seed and assign it to all particle systems involved (copying the seed generated by the particle system sadly doesn’t work. I have tried it.)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class particleFollowMe : MonoBehaviour
{
    ParticleSystem m_System;
    public ParticleSystem m_SystemLeader;

    void OnEnable()
    {
        m_System = GetComponent<ParticleSystem>();
        m_System.Stop();
        m_SystemLeader.Stop();
        m_SystemLeader.randomSeed = (uint)Random.Range(-1000000, 1000000);
        m_System.randomSeed = m_SystemLeader.randomSeed;
        m_System.Play();
        m_SystemLeader.Play();
    }
}

@richardkettlewell @karl_jones
Actually I have attempted to match the sub particles to their spawning particle by color using “inherit color” when the spawning emitter is given random gradient of start color. The problem is that Particle.velocity does not calculate noise but everything else (even randomized force) for the resultant velocity/speed. On the other hand, this is what I would like to be a new mode for inherit velocity module when applying to sub emitter (“current velocity of spawning particle”) or add velocity to inheritance option in sub emitter module.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class particleSubFollowMe : MonoBehaviour
{
    ParticleSystem m_System;
    public ParticleSystem subEmitter;
    ParticleSystem.Particle[] m_Particles;
    ParticleSystem.Particle[] m_ParticlesSub;

    void OnEnable()
    {
        m_System = GetComponent<ParticleSystem>();
        m_Particles = new ParticleSystem.Particle[m_System.main.maxParticles];
        m_ParticlesSub = new ParticleSystem.Particle[subEmitter.main.maxParticles];
    }
 
    private void LateUpdate()
    {
        int numParticlesAlive = m_System.GetParticles(m_Particles);
        int numParticlesAliveSub = subEmitter.GetParticles(m_ParticlesSub);

        for (int i = 0; i < numParticlesAlive; i++)
        {
            for (int j = 0; j < numParticlesAliveSub; j++)
            {
                if (m_ParticlesSub[j].startColor.r == m_Particles[i].startColor.r && m_ParticlesSub[j].startColor.g == m_Particles[i].startColor.g && m_ParticlesSub[j].startColor.b == m_Particles[i].startColor.b && m_ParticlesSub[j].startColor.a == m_Particles[i].startColor.a)
                {
                    m_ParticlesSub[j].velocity += m_Particles[i].velocity;
                    //Debug.Log(m_Particles[i].velocity);
                }
            }
        }
        subEmitter.SetParticles(m_ParticlesSub, numParticlesAliveSub);
    }
}

Bgolus, each particle gets assigned a random seed when born. It’s used to decide how to read curves when using any of the random modes. If we added an “Inherit Random Seed” to the sub emitter module inheritance options, do you think it could help to make this easier to achieve? (To pass the seed of the parent particle to any particles it spawns)

It would remove the need to use a fixed seed, and the need to keep the emission variables identical, but it would only really be useful with a birth sub emitter that spawns a single burst particle time 0. Also everything else velocity related would still need to be identical between the two emitters.

Would this make it easier? Slightly, yes. But it’s unlikely to make it any less obscure for the average user, and insignificantly easier for the more experienced user.

Using the inherited seed for sub emitters using death, collision, or birth w/ emission rate … I’m having a hard time thinking of a use case for any of these. Especially with out a more thorough understanding of how the seed is applied internally. That significantly limits the usefulness.

Lets take the case of a birth sub emitter with an emission rate. If it’s inheriting the seed it’ll spawn with the initial values of the original particle, but have its lifetime out of sync. The noise module could presumably still be in sync (?), but any velocity affecting over lifetime modules would not. You would also need to use the inherit velocity module still to ensure the initial velocity is the same. That’s a lot of special cases the user has to know about and avoid otherwise making the inherited seed option seem “broken”.

Honestly I think the only thing that would really make this easier for the average user is extending the inherit velocity → “current” to work with sub emitters. At least birth and collision, doesn’t make much sense with death. I fully understand that’s a significantly larger task than inheriting the seed.

Inheriting the seed might help for what @ifurkend is attempting, but that’s still an edge case that’s just trying to get around the fact you can’t use the “current” option.

1 Like

Commanding the sub emitter to use the same emitter/emission seed (not particle seed) as the spawning emitter does allow both emitters to share the same noise pattern (otherwise they act apparently under 2 independent noise patterns), it slightly resembles a “follow me” behavior, or a better regulated/glorified inherit initial velocity by the sub emitter. But it still doesn’t answer the original request, which is very valid to me, to constantly update the current velocity (noise induced velocity included) of the spawning particle to its sub particles.

1 Like

GearKlik, bgolus, I have given this some more thought today and hopefully the root of the problem is that the Inherit Velocity Module only works for SubEmitters when using the “Initial” mode. Using “Current” mode does nothing in this case, because there is no code for retrieving the velocity from the parent-particle each frame, which I believe is what would most elegantly solve this problem.

To that end, I have made a prototype that fixes this situation, allowing you to use “Current” mode with SubEmitters. Hopefully it is the right approach…

3210620--245789--subemitter_inherit_current_velocity.gif

4 Likes

Yeah, this was the question I had in my head about the noise. I hadn’t played with it enough to know if it used the per-particle random seed or the emitter random seed. It makes more sense for it to use the emitter seed, so I’m glad it does. Using a fixed seed would “solve” this case too, but I agree it would be nice to be able to use a random-but-shared emitter seed with out the scripting.

This is a different problem than what @GearKlik was attempting to solve though.

Awesome! :smile:

@richardkettlewell
I know this is already a separate issue, but please take a look at this:
http://answers.unity3d.com/questions/1403524/how-does-particlevelocity-relate-to-the-particles.html?childToView=1403635#answer-1403635

It sounds like this is related to a problem I have been grappling with lately, namely that Particle.velocity does not reflect the actual velocity of the particle under all conditions. It does not, for example, appear to reflect the Velocity Over Lifetime module (details here: http://answers.unity3d.com/questions/1403524/how-does-particlevelocity-relate-to-the-particles.html). If Particle.velocity cannot be made to reflect the actual velocity, would it possible to expose the method you are mentioning here in conjunction with the sub-emitters issue? Presuming that it is the actual velocity?

Am I doing something wrong?
I need to have my sub-particles follow the parent particles as well.

1hf2t
Just for clarity I made the sub-particles into big red orbs.
Any settings that I have to check? Is it because I’m using the new radial velocity settings?

This is 2018.1.0 B12

Our prototype never shipped because it had quite a few problems. We are not working on solving it at the moment, sorry.

The effect of having particles of one particle system follow particles from a different particle system is achievable using Participle, an asset I developed. Participle is a general purpose event dispatcher for particle systems that makes many kinds of novel effects easier. Here’s a gif of an effect that I think is similar to what you’re trying to achieve:

3452823--273604--follower_1A.gif

The white particles are the main system and the red particles with trails are from the follower system. Note that there are just two particle systems here - main and follower. A similar effect could be achieved, however, with a main system and a follower prefab per main particle if that was more desirable.

Here’s the particle system setup for the orbital effect in the gif:

3452823--273605--follower_editor.jpg 3452823--273613--follower_editor_2.png

And the EH_FollowParticles code is below. I’ll add this as an example to the next update of Participle. I’ll be happy to answer any questions.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using CogentWhir.Participle;
using CogentWhir.Participle.Utilities;

namespace CogentWhir.Participle.Examples
{

    /* -----------------------------------------------------------------------------------------------------
        -- EH_FollowParticles --
   
        This script serves as an event handler for Participle particle events.

        Provides the ability for the particles of particle system A (the main particle system) to be
        followed by one or more particles emitted from particle system B (the follower particle system).
        Each time a particle in system A is born, one or more particles from system B are emitted to follow
        it.
       ------------------------------------------------------------------------------------------------------ */

    public class EH_FollowParticles : MonoBehaviour
    {
        [Tooltip("The minimum number of followers for each particle.")]
        public int minFollowers = 1;

        [Tooltip("The maximum number of followers for each particle.")]
        public int maxFollowers = 1;

        [Tooltip("The minimum follow distance.")]
        public float minDistance = 1;

        [Tooltip("The maximum follow distance.")]
        public float maxDistance = 1;

        [Tooltip("The particle system from which follow particles will be emitted.")]
        public ParticleSystem followers;

        /* ------------------------------------------------------------------------------------------------------*/

        // Data we will store on a per particle basis using the particle event dispatcher.
        // This data is stored on the followed (system A) particles.
        private class FollowedData : ParticleInfoClientData
        {
            public Vector3 velocity;
            public Vector3 position;
            public float remainingLifetime;
        }

        // Data we will store on a per particle basis using the particle event dispatcher.
        // This data is stored on the follower (system B) particles.
        private class FollowerData : ParticleInfoClientData
        {

            public uint targetParticleID;
            public float followDistance;

        }

        // Data for the pending particles queue.
        private struct PendingParticleData {

            public uint particleID;
            public float followerDistance;
        }

        // A list of particles that have been born but haven't had a follower assigned yet.
        // When a follower particle is born it will grab the particle at the head of the queue
        // and start following it.
        // We need this list to communicate data between the followed and the follower particles.
        private Queue<PendingParticleData> pendingParticlesToFollow;

        // The dispatcher for followed particles (system A).
        private ParticleEventDispatcher followedDispatcher;

        private FollowedData workFollowedData;
        private FollowerData workFollowerData;
        private ParticleSystem.Particle workParticle;
        private PendingParticleData workPendingParticleData;
        private Vector3 workPosition;

        private ParticleSystem.EmitParams emitParams = new ParticleSystem.EmitParams();

        public void Awake()
        {
            followedDispatcher = GetComponent<ParticleEventDispatcher>();
        }

        public void Start()
        {
            pendingParticlesToFollow = new Queue<PendingParticleData>();
        }

        /* ------------------------------------------------------------------------------------------------------
            -- InitFollowed --

            This method will be invoked for each particle to be followed (system A) when it is born.

            Causes the follower particles to be emitted and creates pending queue data entries so
            that each follower particle will know which particle to follow.
           ------------------------------------------------------------------------------------------------------- */
        public void InitFollowed(ParticleEventDispatcher.ParticleBirthInfo eventInfo)
        {
            workFollowedData = new FollowedData();

            workFollowedData.velocity = eventInfo.particle.totalVelocity;
            workFollowedData.position = eventInfo.particle.position;
            workFollowedData.remainingLifetime = eventInfo.particle.remainingLifetime;

            // Record followed particle data with dispatcher.
            eventInfo.dispatcher.SetClientData(eventInfo.particleID, workFollowedData);

            int numFollowers = Random.Range(minFollowers, maxFollowers + 1);

            // Generate a pending queue entry for each follower particle.
            for (int i = 0; i < numFollowers; i++) {

                // Queue up this particle so that a follower particle will be assigned.
                workPendingParticleData.particleID = eventInfo.particleID;
                workPendingParticleData.followerDistance = Random.Range(minDistance, maxDistance);
                pendingParticlesToFollow.Enqueue(workPendingParticleData);

                emitParams.position = GetFollowerPosition(workFollowedData.position, workFollowedData.velocity,
                                                          workPendingParticleData.followerDistance);
                emitParams.startLifetime = eventInfo.particle.startLifetime - 0.01f;

                // Emit the follower particles from the follower particle system.
                followers.Emit(emitParams, 1);
            }
        }

        /* ------------------------------------------------------------------------------------------------------
            -- InitFollower --

            This method will be invoked for each follower particle (system B) when it is born.

            Causes the follower particle to select a particle from system A to follow and records the
            particleID of that particle.
           ------------------------------------------------------------------------------------------------------- */
        public void InitFollower(ParticleEventDispatcher.ParticleBirthInfo eventInfo)
        {
            if (pendingParticlesToFollow.Count > 0) {

                workFollowerData = new FollowerData();
                workPendingParticleData = pendingParticlesToFollow.Dequeue();
                workFollowerData.targetParticleID = workPendingParticleData.particleID;
                workFollowerData.followDistance = workPendingParticleData.followerDistance;

                // Record follower data with dispatcher.
                eventInfo.dispatcher.SetClientData(eventInfo.particleID, workFollowerData);
            }
        }

        /* ------------------------------------------------------------------------------------------------------
            -- Follow --

            This method will be invoked for every follower particle (system B) for each frame.
           ------------------------------------------------------------------------------------------------------- */
        public void Follow(ParticleEventDispatcher.ParticleConditionalInfo eventInfo)
        {
            // Get the follower data for this particle and the particle data.
            workFollowerData = eventInfo.dispatcher.GetClientData(eventInfo.particleID) as FollowerData;
            workParticle = eventInfo.particles[eventInfo.index];

            // Get the client data of the followed particle.
            workFollowedData =
                followedDispatcher.GetClientData(workFollowerData.targetParticleID) as FollowedData;

            // Set follower particle position.
            workParticle.position = GetFollowerPosition(workFollowedData.position, workFollowedData.velocity,
                                                        workFollowerData.followDistance);

            eventInfo.particles[eventInfo.index] = workParticle;
        }


        /* ------------------------------------------------------------------------------------------------------
            -- UpdateFollowed --

            This method will be invoked for every followed particle (system A) for each frame.
           ------------------------------------------------------------------------------------------------------- */
        public void UpdateFollowed(ParticleEventDispatcher.ParticleConditionalInfo eventInfo)
        {
            // Get the followed data for this particle.
            workFollowedData =
                eventInfo.dispatcher.GetClientData(eventInfo.particleID) as FollowedData;

            // Update the client data with the current velocity and position of the particle.
            workFollowedData.velocity = eventInfo.particle.totalVelocity;
            workFollowedData.position = eventInfo.particle.position;
        }


        /* ------------------------------------------------------------------------------------------------------
            -- GetFollowerPosition --

           ------------------------------------------------------------------------------------------------------- */
        private Vector3 GetFollowerPosition(Vector3 followedPosition, Vector3 followedVelocity, float distance)
        {
            return (-Vector3.Normalize(followedVelocity) * distance + followedPosition);
        }
    }
}
3 Likes

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

https://vimeo.com/272999663

The dedicated forum thread is here .

1 Like

Soooo, looks like this issue wasn’t resolved, am I right?
Does InveritVelocity work with Current mode for SubEmitters?

Correct - no more progress was ever made on it.

Sorry to give you bad news, but best to be honest - at this point it will never happen. All new feature effort goes into the VFX Graph now. The built-in Particle System is only receiving bugfixes (and tiny improvements on a case-by-case basis).

There is some argument to be made about this being a bug rather than a feature, and I sympathise with that point of view, but the complexity/risk of trying to resolve it means it will never get addressed.

Ok, got it. Went to learn VFX Graph. =)

1 Like