A lifetime based particle attractor for Unity

I recently found myself in need to have some particles attracted gravity-like to an arbitrary and possibly moving point in space. I did some research and surprisingly found that there’s nothing ready made in Unity for that.
Digging some more, I found this: http://josephjacir.com/code/particleattractor.html which nicely does something similar to what I needed.
Anyway I needed the particles’ movement to be strictly tied to their lifetime (e.g.: they had to fade out exactly while approaching the attracting point), so I made some modifications to it.
I also added a treshold on the particles’ lifetime for the attractor to start it’s influence, so, for example, the particles can start “exploding” with the system’s parameters, and then be attracted at, say, half their lifetime.

A simple example of the lifetime based particle attractor in action.
A simple example of the lifetime based particle attractor in action.

I found the result very eye catching, so here’s the script (with some comments) for you to use freely, if you like it. Just attach this to any game object to act as attractor, and set up a reference to the affected particle system in the inspector. Here’s a link to a downloadable version, if you mind copy-pasting.

using UnityEngine;

public class ParticleAttractorBhv : MonoBehaviour
{
    // The particle system to operate on
    public ParticleSystem AffectedParticles = null;
	
    // Normalized treshold on the particle lifetime
    // 0: affect particles right after they are born
    // 1: never affect particles
    [Range(0.0f, 1.0f)]
    public float ActivationTreshold = 1.0f;

    // Transform cache
    private Transform m_rTransform = null;
    // Array to store particles info
    private ParticleSystem.Particle[] m_rParticlesArray = null;
    // Is this particle system simulating in world space?
    private bool m_bWorldPosition = false;
    // Multiplier to normalize movement cursor after treshold is crossed
    private float m_fCursorMultiplier = 1.0f;

    void Awake()
    {
        // Let's cache the transform
        m_rTransform = this.transform;
        // Setup particle system info
        Setup();
    }

    // To store how many particles are active on each frame
    private int m_iNumActiveParticles = 0;
    // The attractor target
    private Vector3 m_vParticlesTarget = Vector3.zero;
    // A cursor for the movement interpolation
    private float m_fCursor = 0.0f;
    void LateUpdate()
    {
	    // Work only if we have something to work on :)
        if(AffectedParticles != null)
        {
            // Let's fetch active particles info
            m_iNumActiveParticles = AffectedParticles.GetParticles(m_rParticlesArray);
            // The attractor's target is it's world space position
            m_vParticlesTarget = m_rTransform.position;
            // If the system is not simulating in world space, let's project the attractor's target in the system's local space
            if (!m_bWorldPosition)
                m_vParticlesTarget -= AffectedParticles.transform.position;

            // For each active particle...
            for(int iParticle = 0; iParticle < m_iNumActiveParticles; iParticle++) { // The movement cursor is the opposite of the normalized particle's lifetime m_fCursor = 1.0f - (m_rParticlesArray[iParticle].lifetime / m_rParticlesArray[iParticle].startLifetime); // Are we over the activation treshold? if (m_fCursor >= ActivationTreshold)
                {
                    // Let's project the overall cursor in the "over treshold" normalized space
                    m_fCursor -= ActivationTreshold;
                    m_fCursor *= m_fCursorMultiplier;
					
                    // Take over the particle system imposed velocity
                    m_rParticlesArray[iParticle].velocity = Vector3.zero;
                    // Interpolate the movement towards the target with a nice quadratic easing					
                    m_rParticlesArray[iParticle].position = Vector3.Lerp(m_rParticlesArray[iParticle].position, m_vParticlesTarget, m_fCursor * m_fCursor);
                }
            }

            // Let's update the active particles
            AffectedParticles.SetParticles(m_rParticlesArray, m_iNumActiveParticles);
        }
    }

    public void Setup()
    {
        // If we have a system to setup...
        if (AffectedParticles != null)
        {
            // Prepare enough space to store particles info
            m_rParticlesArray = new ParticleSystem.Particle[AffectedParticles.maxParticles];
            // Is the particle system working in world space? Let's store this info
            m_bWorldPosition = AffectedParticles.simulationSpace == ParticleSystemSimulationSpace.World;
            // This the ratio of the total lifetime cursor to the "over treshold" section
            m_fCursorMultiplier = 1.0f / (1.0f - ActivationTreshold);
        }
    }
}

4 thoughts on “A lifetime based particle attractor for Unity”

  1. Thank you! That is quite impressive visual improvement for making an effects in dynamic ways. Its almost too absurd for Unity to not have such targeting ability..

  2. You’re very welcome! I’m happy you found it useful.
    I didn’t have the time to look into the 5.5 particle system improvements in Unity, yet, maybe they added something similar.

  3. Awesome! That is exactly what I need. But for some reasons, the script does not work as shown in your video.
    When I play around with the m_fCursorMultiplier after init then it does something similar but not the result is shown in the video any idea what this could be.
    I figured out the m_fCursor often produces an infinit value.

    Any idea? (unity 2017)

  4. Hello Roman,
    Glad you like this little script. I haven’t tested it with recent Unity versions, but what do you mean by “playing around” with m_fCursorMultiplier? It’s a private variable because you’re not supposed to change it after it’s been precalculated during the Setup() phase. If you do so, most probably m_fCursor will produce incorrect or even invalid values.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.