Unity Particle System – Control the events with C#

Created with Sketch.

Unity Particle System – Control the events with C#

Unity Particle System Events

How fancy would be to control Unity’s Particle System events? I have to admit that I always wanted to extend the functionality of Unity’s Particle System so to call my own functions, or to produce specific sounds.

This article is not about making a Particle System, you can find tons of them at Youtube. I will just present the idea of “capturing” the “onBirth” and “onDeath” events through a nice looking fireworks show, made by Unity’s Particle System.

There are many ways to make a fireworks (or any other type of particles you want), such as the one I found from the sweet Clammiee, which I like it very much, or the one from Random Art Attack (a very nice Particle System tutorial too).

However, the problem of these nice Particle System shows is that they do are not “alive”. Yes, they have nice colors they look like real fireworks or the like, but they are still missing of the “salt” to make them as realistic as they can get. They lack the launch and explosion sounds!!!

Unfortunately, although Unity ParticleSystem class can trigger events after the “birth” or the “death” of a firework particle system, it does not support sounds and you cannot call a custom event in order to play one by code.

So, I had to dig a lot inside the Unity’s classes and found how could I make an object that will communicate with the existing fireworks particle system in order to play the sounds I want when a particle “birth” or “death” happens. You may find more details about Unity’s legacy particle system in here.

The following class supports the Unity’s legacy particles system and not the new VFX Graph.

Advertisements

Having fun with Unity…

To begin with we first create a new project in Unity (let’s name it Fireworks). Unity will create an “empty” scene for us which contains only a “Directional Light” and the “Main Camera” objects. At the “Hierarchy” tab we will create an “empty” component (e.g. a 3D object without a specific shape) by right clicking in the “Hierarchy” tab area and select “Create Empty”. Name the object “Fireworks”.

At this point you may add  the “Rocket.unitypackage” found in the Fireworks.zip package (there is a link at the end of this article) or create your own fireworks show as explained in any of the above mentioned Youtube video tutorials.

Once you import (or create your own fireworks prefab) you should add it as a child of the  “Fireworks” object. To make sure that the child appears at the same position of the parent, select the child object and at the “object inspector” find the “Transform” component. At the very left-top side of the component notice a triple dots marker which will display the component’s menu. When show up, select the “Reset Position” option in order to reset only the position of the child object.

You may need to leave the rotation as is or to set the X axis to -90 in order to make it head upwards.

The entire “Fireworks” object should like the following screen shot at the Unity’s “Hierarchy” tab:

Next, we need to add an “Audio Source” component to the “Fireworks” object by clicking on the “Add Component” button and selecting the “Audio Source” component from the list that will appear below:

This component allows us to play the sounds that we will add later.

Dive into code…

The final component we need to add is a C# script which will do the “magical” combination between the graphical effects and the sound, making our fireworks look more realistic.

So, we will click again on the “Add Component” button at the object inspector for the “Fireworks” object and we will select the “New Script” option. After we give it a name (let’s call it ParticleEvents) we should click on the “Create and Add” button that will appear in order to apply our C# script to our “Fireworks” object. At the same time this script file will appear at the “Project” tab under the “Assets” folder.

We should open the ParticleEvents script by double-clicking on it in the “Project” tab so to open it with the pre-selected “External Script Editor” (menu Edit->Preferences->External Tools tab).

As expected, the “ParticleEvents” object is an inherited class of the Unity’s “MonoBehaviour” class.  MonoBehaviour class is the base class for almost every visual or not object that appears on a Unity’s scene. It contains several properties and methods which Unity engine uses during the game play. We can override some of its methods so to control the behavior of our game objects. You can learn more about MonoBehaviour class in here.

In “ParticleEvents” class we shall override the “Awake” function (it is called at the very first beginning of our project), the “Start” function (it is called right after the Awake function) and  the “LateUpdate” function which is called at every frame and after all the other “Update” functions have been completed.

To be able to communicate with our fireworks particle system we need to declare a public class (let’s name it “ParticlesInfo”) that will be our connection with Unity’s particle objects.

ParticlesInfo class should have the [Serializable] attribute in order to be viewable at the Unity’s Object Inspector.

In addition to the above mentioned classes we shall also need another one, inherited from the UnityEvent class (more info here), named ParticlesEvent and we will again apply the [Serializable] attribute so to make it viewable at the object inspector. This class needs to get as a parameter the ParticlesInfo class we declared above.

Next, we will give the code snippets of the ParticlesInfo and ParticlesEvent classes and we will talk about their properties.

ParticlesInfo & ParticlesEvent classes

[Serializable]
public class ParticlesInfo
{
                 public ParticleSystem system;
                 public AudioClip sound;
                 public bool callEventsOnce;
                 public ParticlesEvent onBirth;
                 public ParticlesEvent onDeath;
                 [HideInInspector]
                 public float[] m_times;
                 [HideInInspector]
                 public ParticleSystem.Particle[] m_particles;
                 [HideInInspector]
                 public bool birthInvoked;
                 [HideInInspector]
                 public bool deathInvoked;
}
 
[Serializable]
public class ParticlesEvent : UnityEvent<ParticlesInfo>
{
}

The first two properties (known to Unity developers) are of type ParticleSystem  and AudioClip respectively. The latter class holds the sound clip we will hear when an “onBirth” or “onDeath” event occurs.

The “callEventOnce” is a Boolean property that when TRUE will make sure that the event will not be triggered again.

The “m_times” is a float array property at which we will store the total lifetime of each particle system. In addition, the “m_particles” is an array of the ParticleSystem.Particle class type and will hold the “alive” particles that appear on screen.

The “birhInvoked” and “deathInvoked” properties will indicate when the “onBirth” or “onDeath” events have been triggered.

Each property has the [HideInInspector] special attribute, in order to hide them at the Unity’s Object Inspector.

ParticleEvents class

ParticleEvents class’ properties are accessible at the Unity’s Object Inspector when we select the corresponding game object on scene.

The top most required property is an array of the class type ParticlesInfo. The developer should initialize it by entering the number of items (Size) at the Object Inspector as shown below:

Unity will create the appropriate properties according to each property type. We can then apply the desired assets to these properties accordingly. The game object of the given example is a combination of the “Rocket” and “Burst” particle systems.

I also applied two sound clips, one for the launch and one for the explosion of the fireworks. Unity’s Asset Store contains hundreds of free sound clips you can import and use in place.

For the published “onBirth” and “onDeath” you can apply any public method that can take a ParticlesInfo parameter. This method may be defined to another public class as well.

Let the magic begins


foreach (var item in particles)
{
  if (item.system != null)
  {
                                                                                            item.m_times = new float[item.system.main.maxParticles];
                                                                                 item.m_particles = new ParticleSystem.Particle[item.system.main.maxParticles];
  }
}

To do its “magic”, the ParticleEvents class needs to hold valid particle systems from the object inspector through its property array.

On the “Awake” of the application, we loop through all the given ParticlesInfo objects so to prepare the m_times and m_particles properties:

Each item.system holds the particles system from which we can get the number of maximum particles that will be generated.

An Audio Source component is also required. This can be a published property or you may add it to a private field when the “Start” method occurs.

So to make sure that an Audio Source will be attached to our game object we need to apply the [RequireComponent(typeof(AudioSource))] attribute, right above the declaration of the “ParticleEvents” class.

At the “LateUpdate” method, we iterate all our “alive” particles and if their lifetime is greater than zero (0) we invoke the “onBirth” event.

The event re-invocation will be controlled by the “callEventOnce” property. Then a Co-Routine is scheduled to be triggered at the end of each event’s lifetime. You can learn more about Unity Co-Routines here.

This Co-Routine has the ability to yield the execution of its code until the given time expires and then it invokes the “onDeath” event by using the same “callEventOnce” logic. The entire snippet is presented below:

private void LateUpdate()
{
  foreach (var item in particles)
  {
    // Invokers need to be reset upon entering late update event
    item.birthInvoked = false;
    item.deathInvoked = false;

    if (item.system == null) continue;
   if (item.system.particleCount == 0) continue;

     // Get only the alive particles from the associated Particle System
     int aliveParticles = item.system.GetParticles(item.m_particles);
                                       
     for (int i = 0; i < aliveParticles; i++)
     {
                                                                                 if (item.m_times[i] < item.m_particles[i].remainingLifetime && item.m_particles[i].remainingLifetime > 0)
          {
            // On Birth event
            if (item.onBirth != null)
            {
               // Check if invoker was already called
               if (!item.birthInvoked || !item.callEventsOnce)
               {
                 item.birthInvoked = true;
                 item.onBirth.Invoke(item);                                                                }                                                                                        }
            // Wait till particle's death                               StartCoroutine(WaitTillEnd(item, item.m_particles[i].remainingLifetime));
                                                                                            }
          // Store each particle remaining lifetime for comparisons                                   item.m_times[i] = item.m_particles[i].remainingLifetime;
          }
        }
}

IEnumerator WaitTillEnd(ParticlesInfo item, float lifeTime)
{
  yield return new WaitForSeconds(lifeTime);
  // Death (If spawning a sub-emitter on death, this is when that is triggered)
   if (item.onDeath != null)
   {
     // Check if invoker was already called
     if (!item.deathInvoked || !item.callEventsOnce)
     {
         item.deathInvoked = true;
          item.onDeath.Invoke(item);
     }
   }
}

public void PlaySound(ParticlesInfo item)
{
  if (item.sound != null)
  {
     source.PlayOneShot(item.sound);
  }
}

I hope you enjoyed the Fireworks project as much as I did. You can download the entire project here.

About the author

Bill Rigas is a Web Developer – Designer and co-founder of EverFounders blog with many years of professional experience on making software for airline companies. Owner of Msc Information of Technology diploma from University of East London and many certifications from edx. Also a certified Unity game developer from Walker Boys studio.