How do you add a trail in particle playground?

Ive been messing around with playground and im loving it, but i cant figure out how to add a trail after the particle has come out of the emitter.

You can now since Particle Playground 3 use the Playground Trail component to draw trails after particles. This is preferred in oppose to the standard trails as they will batch dynamically and run asynchronous. There’s a preview of working with them in the [release video][1] and more info on the [Playground Trail][2] page.


Previous answer:

Some background

To make an object follow a particle is a quick task, you can either reach into the Playground Cache (the directly calculated values) or the Particle Cache (the synced live Shuriken particles). Example:

yourTransform.position = yourParticleSystem.playgroundCache.position[particleId];

However, a trail will need a bit of time slack to remain in the scene while a particle is recycled. Therefore it’s a good idea to make the trails have a separate following lifetime. You can listen for particle birth and death using Particle Playground’s Event system. That way you can easily add custom logic to a particle during its lifetime.

![Particle Playground birth and death events][3]

Listening to an event is very straightforward to setup, where you hook up a custom function to the event delegate:

using UnityEngine;
using System.Collections;
using ParticlePlayground;

public class ListenForParticleEvents : MonoBehaviour {

	public PlaygroundParticlesC particles;

	/// <summary>
	/// Add your event listener to the delegate.
	/// </summary>
	void OnEnable () {
		particles.events[0].particleEvent += ThingToHappenOnEvent;
	}

	/// <summary>
	/// Remove your event listener from the delegate.
	/// </summary>
	void OnDisable () {
		particles.events[0].particleEvent -= ThingToHappenOnEvent;
	}

	/// <summary>
	/// Your event listener. This will pass in an event particle with detailed info about the particle triggering the event.
	/// </summary>
	/// <param name="particle">Event Particle with detailed info.</param>
	void ThingToHappenOnEvent (PlaygroundEventParticle particle) {
		Debug.Log ("Particle "+particle.particleId+" has position "+particle.position);
	}
}

You can also have a look in the example scene Event Listener.

As Playground is multithreaded the broadcasted events will live on a separate thread unless anything else is specified in the Thread Aggregation. Therefore you may need to find some workarounds when working with Unity’s components (such as the Transform), usually the most efficient approach is to add wrapper classes for the components in those cases. In this case, we can add a waiting queue of followers which can live on another thread, then make any calls for Instantiation on the main-thread and transfer those followers onto the live list for translation along the particles.

Trails and followers example

![Particle Playground trails after particles][4]

This explanation wouldn’t be very clear without a script, so below you’ll find an example of how to setup your trails towards moving along with your particles. It will use a referenceObject which could be any type of object, in your case assign a GameObject with a TrailRenderer component. This object will be cloned whenever a particle birth is broadcasted which triggers the OnParticleDidBirth event listener. The follower will be destroyed when its lifetime reaches 0, setting lifetime to 0 from the beginning will make it inherit the particle system’s lifetime. Therefore when working with trails you may want to add a higher lifetime than your particle system (for instance trail components Time + particle system lifetime), otherwise trails will be destroyed before the effect has finished. Note that the script will create the necessary events for you.

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

public class PlaygroundFollow : MonoBehaviour {

	/// <summary>
	/// Reference to the particle system.
	/// </summary>
	public PlaygroundParticlesC particles;
	/// <summary>
	/// Reference to an existing GameObject. This will be cloned to be used on every particle.
	/// </summary>
	public GameObject referenceObject;
	/// <summary>
	/// The lifetime of the followers. Set 0 to follow during each particle's individual lifetime.
	/// </summary>
	public float followerLifetime = 0;

	TrailRenderer referenceTrailRenderer;
	/// <summary>
	/// If the follower has a Trail Renderer component, this sets trail time once the follower is active again.
	/// </summary>
	float trailTime = 0;
	/// <summary>
	/// The size of the cache. Set 0 to automatically set the needed amount.
	/// </summary>
	public int cacheSize = 0;
	/// <summary>
	/// The list of active followers.
	/// </summary>
	List<PlaygroundFollower> followers = new List<PlaygroundFollower>();
	/// <summary>
	/// As Playground is running in a multithreaded environment we need a queue for instantiation (which cannot be called from a different thread).
	/// </summary>
	List<PlaygroundFollower> waitingFollowers = new List<PlaygroundFollower>();
	PlaygroundFollower[] referenceObjectsCache;
	PlaygroundFollower[] queue = new PlaygroundFollower[0];
	int cacheIndex = 0;

	PlaygroundEventC birthEvent;
	PlaygroundEventC deathEvent;
	Transform followerParent;

	void Awake () {

		// Create and setup the birth event
		birthEvent = PlaygroundC.CreateEvent(particles);
		birthEvent.broadcastType = EVENTBROADCASTC.EventListeners;
		birthEvent.eventType = EVENTTYPEC.Birth;

		// Create and setup the death event
		deathEvent = PlaygroundC.CreateEvent(particles);
		deathEvent.broadcastType = EVENTBROADCASTC.EventListeners;
		deathEvent.eventType = EVENTTYPEC.Death;

		// Hook up the event listeners to the delegates
		birthEvent.particleEvent += OnParticleDidBirth;
		deathEvent.particleEvent += OnParticleDidDie;

		// Setup the followers
		followerParent = new GameObject("Followers").transform;
		followerParent.parent = transform;
		referenceTrailRenderer = referenceObject.GetComponent<TrailRenderer>();
		if (referenceTrailRenderer!=null)
			trailTime = referenceTrailRenderer.time;

		int extra = followerLifetime<=0? 
			Mathf.CeilToInt(Mathf.Abs (particles.lifetime-trailTime)+(trailTime-particles.lifetime))+2 : 
				Mathf.CeilToInt(Mathf.Abs (particles.lifetime-followerLifetime)+(followerLifetime-particles.lifetime))+2 ;
		if (particles.lifetime<=1f) extra++;
		referenceObjectsCache = new PlaygroundFollower[cacheSize>0? cacheSize : particles.particleCount+Mathf.CeilToInt(particles.particleCount*extra)];
		for (int i = 0; i<referenceObjectsCache.Length; i++) {
			GameObject clone = (GameObject)Instantiate(referenceObject);
			referenceObjectsCache *= new PlaygroundFollower(clone.transform, clone, clone.GetComponent<TrailRenderer>(), 0, 0);*

_ referenceObjectsCache*.transform.parent = followerParent;_
_ if (referenceObjectsCache.trailRenderer!=null)
referenceObjectsCache.trailRenderer.time = 0;
referenceObjectsCache.gameObject.SetActive(false);
}
}*_

* ///

*
* /// Event listener for particle birth.*
* ///
*
* /// Particle.*
* void OnParticleDidBirth (PlaygroundEventParticle particle) {*
* waitingFollowers.Add (new PlaygroundFollower(null, null, null, followerLifetime<=0? particle.totalLifetime+trailTime : followerLifetime, particle.particleId));*
* }*

* ///

*
* /// Event listener for particle death.*
* ///
*
* /// Particle.*
* void OnParticleDidDie (PlaygroundEventParticle particle) {*
* int followerId = GetFollowerWithId(particle.particleId);*
* if (followerId<0) return;*
* followers[followerId].enabled = false;*
* }*

* ///

*
* /// Gets the follower which has the passed in particle identifier.*
* ///
*
* /// The follower with particle identifier.*
* /// Particle identifier.*
* int GetFollowerWithId (int particleId) {*
* float lowestLife = 999f;*
* int returnIndex = -1;*
* for (int i = 0; i<followers.Count; i++)*
if (followers_.particleId==particleId && followers*.lifetime<lowestLife)
returnIndex = i;
return returnIndex;
}*_

* void Update () {*
* if (waitingFollowers.Count>0) {*
* queue = waitingFollowers.ToArray();*
* }*
* }*
* void LateUpdate () {*
* UpdateFollowers();*
* }*

* void UpdateFollowers () {*

* // Follow, lifetime, remove*
* for (int i = 0; i<followers.Count; i++) {*

* // Follow particle*
_ if (followers*.enabled)
followers.transform.position = particles.particleCache[followers.particleId].position;*_

* // Subtract lifetime*
_ followers*.lifetime -= Time.deltaTime;*_

* // Remove if no lifetime left*
_ if (followers*.lifetime<=0) {
RemoveFollower(i);
continue;
}
}*_

* // Add any waiting followers to the live follower list. The waiting list may change during iteration!*
* if (queue.Length>0) {*
* if (queue.Length!=waitingFollowers.Count) return;*
* int inQueueThisFrame = waitingFollowers.Count;*

* foreach (PlaygroundFollower wFollower in queue) {*
* AddFollower (wFollower, followers.Count-1);*
* }*
* if (inQueueThisFrame==waitingFollowers.Count)*
* waitingFollowers = new List();*
* else waitingFollowers.RemoveRange (0, inQueueThisFrame);*
* queue = new PlaygroundFollower[0];*
* }*
* }*

* void AddFollower (PlaygroundFollower follower, int i) {*
* if (follower==null) return;*
* followers.Add (follower.Clone());*
* followers[followers.Count-1].enabled = true;*
* followers[followers.Count-1].gameObject = referenceObjectsCache[cacheIndex].gameObject;*
* followers[followers.Count-1].gameObject.SetActive(true);*
* followers[followers.Count-1].transform = referenceObjectsCache[cacheIndex].transform;*
* followers[followers.Count-1].trailRenderer = referenceObjectsCache[cacheIndex].trailRenderer;*
* followers[followers.Count-1].particleId = follower.particleId;*
* followers[followers.Count-1].transform.position = particles.playgroundCache.position[followers[followers.Count-1].particleId];*
* if (followers[followers.Count-1].trailRenderer!=null)*
* followers[followers.Count-1].trailRenderer.time = trailTime;*
* NextCacheIndex();*
* }*

* void RemoveFollower (int i) {*
_ followers*.enabled = false;
if (followers.trailRenderer!=null)
followers.trailRenderer.time = 0;
followers.gameObject.SetActive(false);
followers.RemoveAt(i);
}*_

* void NextCacheIndex () {*
* cacheIndex = (cacheIndex+1)%referenceObjectsCache.Length;*
* }*
}

///


/// Playground follower class.
///

public class PlaygroundFollower {
* public bool enabled = true;*
* public float lifetime;*
* public Transform transform;*
* public GameObject gameObject;*
* public TrailRenderer trailRenderer;*
* public int particleId;*

* ///

*
* /// Initializes a new instance of the class.*
* ///
*
* /// Transform to reposition.*
* /// Start lifetifetime.*
* /// Particle identifier to follow.*
* public PlaygroundFollower (Transform setTransform, GameObject setGameObject, TrailRenderer setTrailRenderer, float setLifetime, int setParticleId) {*
* transform = setTransform;*
* gameObject = setGameObject;*
* trailRenderer = setTrailRenderer;*
* lifetime = setLifetime;*
* particleId = setParticleId;*
* }*

* ///

*
* /// Clones this instance.*
* ///
*
* public PlaygroundFollower Clone () {*
* return new PlaygroundFollower (transform, gameObject, trailRenderer, lifetime, particleId);*
* }*
}
For more detailed info you can refer to the [Playground script reference][5].
*[1]: Particle Playground 3 - YouTube
[2]: http://polyfied.com/products/particle-playground/playground-trails/*_
_[3]: http://www.polyfied.com/development/unity/playground/particle-birth-death-events.png*_

*[4]: http://www.polyfied.com/development/unity/playground/particle-birth-death-events.gif*_
*[5]: http://polyfied.com/products/playground-scriptreference/html/index.html*_