Quick answer: Call restart() to reset the simulation, then set emitting = true. Just toggling emitting doesn’t replay a one-shot system.

An explosion VFX uses GPUParticles3D with one_shot enabled. First explosion plays; the second time the node is reused, nothing emits. The system latched after its one shot.

Use restart()

func play_explosion():
    $Particles.restart()
    $Particles.emitting = true

restart() resets internal time and the “done” flag. Without it, a finished one_shot system ignores emitting=true.

One-Shot Semantics

one_shot = true: emit a burst, then stop. The system remembers it’s done. restart() is the only way to replay.

one_shot = false: continuous; emitting toggles work directly.

Pooling Pattern

func get_pooled_particle() -> GPUParticles3D:
    var p = pool.pop_back()
    p.global_position = spawn_pos
    p.restart()
    p.emitting = true
    return p

For pooled VFX, always restart on reuse. Position first, then restart.

Auto-Free After Done

$Particles.finished.connect(func(): return_to_pool($Particles))

The finished signal fires when a one_shot completes — clean hook for pooling.

Verifying

Trigger explosion repeatedly. Each plays fully. Pooled particles replay correctly on reuse.

“One-shot latches. restart() unlatches. Always restart before replaying.”

For high-frequency effects, pre-warm a pool of 16–32 particle nodes at level load — restart() is cheap, instantiation is not.