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 death

Prefab-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)
PositionFollows parent objectAny world position
Use forPlayer hit flash, persistent trails, death burstCoin collect, enemy pop, explosion
CleanupStays in scene (fine for persistent)Self-destructs via Stop Action → Destroy
SetupAssign component in InspectorAssign 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 call Play() 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, check isPlaying first or use Emit instead.
  • A ParticleSystem component 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.