Quick answer: Check that the Emission module is enabled, the system is not in a stopped state from a previous Stop(true) call, and Play On Awake is set correctly for your use case. For effects you recycle by disabling and re-enabling the GameObject, call ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear) before disabling, then ps.Play() after re-enabling.

You enable a GameObject with a ParticleSystem component and nothing happens — no burst, no trail, no spark. Or you call Play() directly from a script and the system stays silent. Particle system visibility bugs are notoriously hard to debug because the system can be in several different states that all look identical at a glance in the Scene view.

The Symptom

The particle system component is present and appears enabled in the Inspector. The emission module is ticked. The material is assigned. Yet when the GameObject becomes active at runtime, or when your code calls particleSystem.Play(), no particles appear. The particle system’s particle count stays at zero.

A related symptom: the system played correctly once, was disabled and re-enabled, and now it no longer plays. Or it plays in the Editor but not in a build.

What Causes This

1. Play On Awake vs. manual Play()

The Play On Awake checkbox in the main module of the ParticleSystem controls whether the system automatically starts playing when the GameObject first becomes active. It only fires on the first Awake after the GameObject is instantiated — not on subsequent SetActive(true) calls.

This means:

The correct pattern for reusable effects (pooled objects, toggled VFX) is to disable Play On Awake and drive playback entirely from script:

using UnityEngine;

public class ReusableEffect : MonoBehaviour
{
    private ParticleSystem ps;

    private void Awake()
    {
        ps = GetComponent<ParticleSystem>();

        // Disable Play On Awake so we control playback manually.
        var main = ps.main;
        main.playOnAwake = false;
    }

    private void OnEnable()
    {
        // Called every time the GameObject is enabled — correct for pooled VFX.
        ps.Play(true); // true = also play child systems
    }

    private void OnDisable()
    {
        // Clear all particles when returned to the pool.
        ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
    }
}

2. Stop(true) clears particles and blocks autoplay

ParticleSystem.Stop takes an optional withChildren bool and a ParticleSystemStopBehavior enum. There are two stop behaviors:

After either Stop variant, the system is in a stopped state. Re-enabling the GameObject does not restart the system unless Play On Awake is ticked (and even then, only on the first enable). You must call Play() explicitly. If something in your codebase calls Stop and you don’t track state carefully, effects silently fail on the next activate cycle.

using UnityEngine;

public class EffectController : MonoBehaviour
{
    [SerializeField] private ParticleSystem hitEffect;

    public void PlayHitEffect()
    {
        // If the effect is still playing from a previous hit, stop and reset it.
        if (hitEffect.isPlaying || hitEffect.isPaused)
            hitEffect.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);

        hitEffect.Play(true);
    }

    public void DebugParticleState()
    {
        Debug.Log(
            $"isPlaying={hitEffect.isPlaying} "
            + $"isPaused={hitEffect.isPaused} "
            + $"isStopped={hitEffect.isStopped} "
            + $"particleCount={hitEffect.particleCount}"
        );
    }
}

3. Emission module is disabled

A particle system can be playing (isPlaying == true) but emit zero particles if the Emission module is unchecked. This happens more often than you’d expect when copying or resetting components. Check it in the Inspector or verify in code:

private void EnsureEmissionEnabled(ParticleSystem ps)
{
    var emission = ps.emission;

    if (!emission.enabled)
    {
        Debug.LogWarning($"[{ps.name}] Emission module is disabled!");
        emission.enabled = true;
    }

    // Also verify rate is non-zero for continuous systems
    if (emission.rateOverTime.constant == 0f
        && emission.rateOverDistance.constant == 0f
        && emission.burstCount == 0)
    {
        Debug.LogWarning($"[{ps.name}] Emission rate is zero and no bursts defined.");
    }
}

4. Paused Editor state carried into Play mode

If you pause the Unity Editor while a particle system is playing and then enter Play mode (or reload the scene), the particle system’s serialized state can carry a “paused” flag into the runtime. The system reports isPaused == true and won’t play until you call Play() or unpause it. Always reset particle system state explicitly on scene load rather than relying on the serialized state.

private void Start()
{
    // Defensive reset: ensure the system starts from a clean state
    // regardless of any Editor state that may have been serialized.
    ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
    ps.Clear(true);

    if (playImmediately)
        ps.Play(true);
}

5. Looping vs. non-looping systems that have finished

A non-looping system plays once and then stops. After its Duration expires, isStopped becomes true and the system will not auto-replay. If your effect is meant to repeat (e.g., an ambient fire), enable Looping. If it is one-shot and you need to replay it on demand, call Stop then Play each time:

public void ReplayOneShot()
{
    // One-shot systems that have finished must be explicitly restarted.
    ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
    ps.Play(true);
}

6. Sub-emitters require the parent to emit first

Sub-emitters are triggered by events on parent particles — birth, death, collision, or trigger. Calling Play() on a parent system does not directly fire its sub-emitters. The sub-emitters only activate when the parent system emits a particle and that particle triggers the configured event. If the parent’s emission rate is zero or the burst count is zero, no particles are born and no sub-emitter event fires.

To debug sub-emitter issues, confirm the parent is actually emitting with ps.particleCount > 0 one frame after calling Play(), then check that the sub-emitter’s trigger type (birth, death, collision) matches a real event in the parent’s lifecycle:

private IEnumerator DebugSubEmitter(ParticleSystem parent)
{
    parent.Play(true);

    yield return new WaitForSeconds(0.1f);

    Debug.Log($"Parent particle count after 0.1s: {parent.particleCount}");

    var subEmitters = parent.subEmitters;
    Debug.Log($"Sub-emitter count: {subEmitters.subEmittersCount}");

    for (int i = 0; i < subEmitters.subEmittersCount; i++)
    {
        var info = subEmitters.GetSubEmitterSystem(i);
        Debug.Log($"Sub-emitter[{i}]: {info.name} "
            + $"type={subEmitters.GetSubEmitterType(i)}");
    }
}

Related Issues

When a particle system silently refuses to play, check emission first, then state (isStopped vs isPaused), then Play On Awake vs OnEnable — that order catches 95% of cases.