Summary
Unity’s ParticleSystem component emits particles according to settings configured in the Inspector. From code, scripts control when effects play, how many particles fire, and where. There are two main approaches: an attached ParticleSystem on a child object of the same GameObject (good for persistent or repeating effects like a trail or idle glow), and an instantiated prefab spawned at a world position (good for transient events like a coin burst or explosion that happen at a location the parent script does not occupy).
Key ideas
ParticleSystem.Play() — starts the particle system. If it was already playing, behaviour depends on the Inspector settings. Use for persistent effects (idle glow, trail) and for death/win bursts attached to the player.
ParticleSystem.Stop() — stops emitting new particles. Existing particles continue until they die.
ParticleSystem.Emit(int count) — fires exactly count particles in one call, independently of the system’s own emission rate or duration settings. The most reliable way to trigger a scripted burst of a known size.
Attached approach: Assign a ParticleSystem component (on a child object of the FeedbackManager or Player) via [SerializeField]. Call ps.Play() or ps.Emit(count) when the event fires. The effect stays with the parent object.
Prefab approach: Create a prefab containing a self-contained ParticleSystem with Stop Action → Destroy enabled. When the event fires, call Instantiate(prefab, worldPosition, Quaternion.identity). The particle object appears at the event location and destroys itself when its particles expire.
Stop Action → Destroy: An Inspector setting on the ParticleSystem component (under the main module). When the system stops, Unity automatically destroys the GameObject. Without this, instantiated particle prefabs accumulate in the scene indefinitely.
In practice
Attached ParticleSystem — scripted burst on event:
[Header("Attached Particle Systems")]
[SerializeField] private ParticleSystem hitEffect; // child object
[SerializeField] private ParticleSystem deathEffect;
private void EmitBurst(ParticleSystem ps, int count)
{
if (ps == null) return;
ps.Emit(count); // fires exactly N particles
}
// Usage
EmitBurst(hitEffect, 8); // small burst on damage
deathEffect.Play(); // full effect on deathPrefab-based particle at world position:
[Header("Spawnable Particle Prefabs")]
[SerializeField] private GameObject coinCollectFXPrefab; // has Stop Action → Destroy
[SerializeField] private GameObject enemyDefeatFXPrefab;
private void SpawnParticlePrefab(GameObject prefab, Vector3 position)
{
if (prefab == null) return;
Instantiate(prefab, position, Quaternion.identity);
// prefab destroys itself when particles expire (Stop Action → Destroy)
}
// Usage — position comes from the event, not the FeedbackManager's own transform
public void OnCoinCollected(Vector3 worldPosition)
{
SpawnParticlePrefab(coinCollectFXPrefab, worldPosition);
}
public void OnEnemyDefeated(Vector3 worldPosition)
{
SpawnParticlePrefab(enemyDefeatFXPrefab, worldPosition);
}Combining audio and particles in one feedback call:
public void OnCoinCollected(Vector3 worldPosition)
{
if (coinClip != null)
AudioSource.PlayClipAtPoint(coinClip, worldPosition); // sound at position
SpawnParticlePrefab(coinCollectFXPrefab, worldPosition); // particles at position
}Choosing attached vs prefab
| Attached (child object) | Prefab (Instantiate) | |
|---|---|---|
| Position | Follows parent object | Any world position |
| Use for | Player hit flash, persistent trails, death burst | Coin collect, enemy pop, explosion |
| Cleanup | Stays in scene (fine for persistent) | Self-destructs via Stop Action → Destroy |
| Setup | Assign component in Inspector | Assign prefab asset in Inspector |
Gotchas
- Forgetting to set Stop Action → Destroy on an instantiated particle prefab means every triggered event leaves a dead GameObject in the Hierarchy. The game will not break, but scene memory grows and the Hierarchy becomes cluttered.
Emit(count)fires particles immediately regardless of whether the system is paused or stopped. It does not callPlay()first — the system does not need to be in a playing state.ParticleSystem.Play()called on a system that is already playing will restart it (by default). If the effect should layer rather than restart, checkisPlayingfirst or useEmitinstead.- A
ParticleSystemcomponent must be on the same GameObject as the script, or on a child, to be referenced via[SerializeField]. It cannot be on an unrelated object in the scene without using an Inspector reference.
Related
- unity-audiosource — audio and particles are combined in the FeedbackManager pattern
- unity-prefabs-scripting —
Instantiatesyntax for spawning particle prefabs - unity-inspector-references — assigning ParticleSystem and prefab references
- game-feel — particles and audio are primary polish tools for game feel