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.