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);
}

Read the prefab approach as a short hand-off:

  1. The gameplay script knows where the event happened.
  2. It passes that worldPosition to the feedback method.
  3. The feedback method instantiates a visual effect prefab at that position.
  4. The ParticleSystem’s Inspector setting handles cleanup through Stop Action Destroy.

The code does not need to remember the spawned particle object unless it wants to cancel or modify it later.

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.

Debugging particle setup

SymptomLikely causeFirst check
Effect appears as soon as Play startsPlay On Awake enabledUntick it for event-driven bursts
Effect appears at the wrong locationWrong position passed into InstantiateLog the worldPosition from the gameplay event
Scene fills with old effect objectsStop Action not set to DestroyCheck the ParticleSystem main module on the prefab
Nothing happens when code runsPrefab or ParticleSystem reference is nullCheck the Inspector slot and add a Debug.LogError guard