Using Particle Systems with ECS in Unity
This tutorial assumes you are already familiar with Unity itself and the ECS API!
I’ve been upgrading more systems to Unity’s ECS (Entity Component System) for my game. One of the things are these “drones” that shoot projectiles at the player. So I had to figure out how to use particle systems with entities and figured I’d explain how I did it.
Setting up
Lets start by creating a tag component. Tag components are called like that because they contain no data, they are used to “tag” entities by acting like markers. We will add this component to entities we want to emit particles from.
You can learn more about IComponentData
in the ECS docs.
using Unity.Entities;
public struct VfxEmitter : IComponentData { }
Then create a new file and set up a basic ECS system. This will be the script in charge of spawning the particles.
using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
public class VfxSystem : SystemBase
{
protected override void OnCreate()
{
base.OnCreate();
Enabled = false; // Dont run the system until we have set everything up
}
protected override void OnUpdate()
{
// Empty for now
}
}
The particles in the scene
Now we can create the actual particle effect. This is done as usual, with a built-in ParticleSystem.
For example here is what I made:
Limitations
Since we’ll need to do the emission of particles by hand there are limitations on the type of effects that can be used. Only looping effects will work. A lot of custom code is needed to recreate specific effects that are not loopable.
Effects that have multiple ParticleSystems (usually as children of the parent effect) can be used with the same limitation of being looping effects. To do it you can just loop over the children and find the ParticleSystems or using GetComponentsInChildren
. Then loop over those instead of just using the single one. (The files for Patreon supporters contain a ready script for this).
Make sure PlayOnAwake
is off when actually running the game. Otherwise the particles will start spawning immediately and not at the entities’ positions.
Now we need to get a reference to the particle emitter in the scene from the ECS system. This can be done in many different ways, for example using Find
or by instantiating it at runtime.
For the purposes of this tutorial we’ll just make a quick and dirty helper scripts that finds the system and passes it our ParticleSystem
from the game scene.
using Unity.Entities;
using UnityEngine;
public class VfxSystemHelper : MonoBehaviour
{
public ParticleSystem particles;
void Start()
{
World.DefaultGameObjectInjectionWorld.GetOrCreateSystem<VfxSystem>().Init(particles);
}
}
Then we create the function that gets called by the helper script. This function receives the reference and then enables the system to make it start running.
public class VfxSystem : SystemBase
{
UnityEngine.ParticleSystem particleSystem;
Transform particleSystemTransform;
protected override void OnCreate() // ...
public void Init(UnityEngine.ParticleSystem particleSystem)
{
this.particleSystem = particleSystem;
particleSystemTransform = particleSystem.transform;
Enabled = true; // Everything is ready, can begin running the system
}
protected override void OnUpdate()
Emitting custom particles
And now lets make it actually emit particles!
We’ll do this by using the ParticleSystem.Emit method.
For each entity that has our tag component attached, every frame, the system will move the ParticleSystem’s transform to that position and emit some particles.
Note that the entity must have a
Translation
component (from Unity.Transforms) or some other custom component from which a position can be read, in addition to theVfxEmitter
component we created at the start of this tutorial.
protected override void OnUpdate()
{
Entities.WithAll<VfxEmitter>().ForEach((in Translation translation) =>
{
particleSystemTransform.position = translation.Value;
particleSystem.Emit(1);
}).WithoutBurst().Run();
}
Frame-rate indipendence
However, since the Emit
method takes in a count of how many particles to spawn we could run into an issue: the amount of particles spawned is framerate dependent.
For example, say we are emitting one particle for every time the Update
function is executed. Then if the game is running at 30fps that will be 30 particles; but if the game runs at 60 it would spawn 60. And it’s even worse if the framerate is not constant!
We can fix this by emulating a fixed emission rate, similar to how the builtin emission would work. We’ll also read the emission over time rate directly from the effect.
First, let’s add some variables. interval
is time in seconds between each particle spawn and timer
is to keep track how much time has passed since the last spawn.
UnityEngine.ParticleSystem particleSystem;
UnityEngine.ParticleSystem.EmitParams emitParams;
float interval;
float timer;
In the Init
method we calculate the interval
value.
This could also be done every frame if you want to change the emission rate dynamically. We’ll just do it a the start and then use the cached value to save on performance.
particleSystemTransform = particleSystem.transform;
interval = 1f / particleSystem.emission.rateOverTimeMultiplier;
Enabled = true; // Everything is ready, can begin running the system
And in the Update
method; we begin by incrementing the timer with the time passed since the last frame.
var deltaTime = (float)Time.DeltaTime;
timer += deltaTime;
From that we calculate how many particles should be spawned this frame. The count will be zero until enough time has passed. Or in case of very high emission rate or very long frame times the count will be larger to account for skipped emissions.
var count = Mathf.RoundToInt(timer / interval);
If the count is more than 0 we emit that amount at every entity’s position.
if (count > 0)
{
Entities.WithAll<VfxEmitter>().ForEach((in Translation translation) =>
{
particleSystemTransform.position = translation.Value;
particleSystem.Emit(count);
}).WithoutBurst().Run();
}
And finally (still inside the if (count > 0)
) we adjust the timer based on how many particles were spawned.
timer -= count * interval;
Particles with ECS!
And this is the result in game:
Look at them go!
Doing more
You can add a field to the tag component to store things like scale or color for that entity. Then instead of just passing the count to the Emit
method you can also pass some EmitParams with your customized fields. Remember to use a shader compatible with ParticleSystems to make sure changing the color for example actually works.
Download files
The Standard package contains the script to put on the entity and the system. The Premium package, instead, also contains a test scene which includes the helper script and also a script that supports child ParticleSystems.
Preview of the example scene