godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
87.18k stars 19.59k forks source link

GPUParticles2D: Oneshot Sub Emitter won't emit finished() signal #93546

Open 4X3L82 opened 1 month ago

4X3L82 commented 1 month ago

Tested versions

System information

Godot v4.3.beta2 - Manjaro Linux #1 SMP PREEMPT_DYNAMIC Mon May 27 03:41:25 UTC 2024 - X11 - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 3080 Laptop GPU - AMD Ryzen 9 5900HX with Radeon Graphics (16 Threads)

Issue description

I've created a simple GPUParticles2D effect with a main+subemitter, both set to OneShot and I want to know when the whole effect is done. I want to know when it's done so I can then destroy the effect (cleaning up). This could be done by using the finished() signal of the subemitter, however, it's never emitted.

Goal: I need the finished() signal from the oneshot subemitter to know when the whole GPUParticles2D effect is done, so I can then call queue_free() to make it clean itself up.

Steps to reproduce

Minimal reproduction project (MRP)

subemitter_no_finished.zip

This MRP has a button to restart() the main emitter, and 2 print statements connected to the 2 finished() signals of the main and sub emitter. Once the button is pressed, you can see the particles doing their thing. At the end of the main emitter, two things happen: "main done!" is printed, and the subemitter kicks in. Then at the end of the subemitter, nothing happens.

I expected the finished() signal of the sub emitter to emit, which would then print "sub done!".

4X3L82 commented 2 weeks ago

Based on this comment by @patwork on a similar issue, I was able to resolve this:

I've added %sub.restart() just after the %main.restart() for the button. Now when the button is pressed, the emitter does its thing and both main & sub emitter send the finished() signal.

However, things still don't look/feel right to me. Basically, a double restart() to make a single effect work seems gratuitous.

Without the restart() on the sub-emitter, the whole particle effect (both main+sub) work fine. There's no (visual) feedback that the restart() is missing/required on the sub-emitter and not just main. Shouldn't the restart() on the main emitter trickle down and automatically fire off a restart() on its own sub-emitter(s)? Or rephrased: Is there ever a case where you want to restart() the main emitter without restarting its sub-emitter(s)?

If all the above is fully expected behavior, including restart() not trickle down to its own sub-emitter(s), then I think me (and probably others too) would benefit from clearer documentation.

patwork commented 2 weeks ago

This case is different from the one described in https://github.com/godotengine/godot/issues/93991. I don't want to speak for the authors of the GPU Particles code, but in my opinion in that (not this) case is an oversight of a possible situation where the one_shot flag is set by the script when the particle emitter has been already added to the tree and passed its initialization.

💣 warning, below is just my current understanding, not confirmed by Godot devs

This case refers to something else, namely sub-emitters. The difference is that you do not have a problem with the finished signal in the main emitter, but in the sub-emitter that is triggered by it.

I was looking at the GPU Particles code for the first time yesterday so my conclusions are not necessarily 100% correct, but as far as I can see, sub-emitters do not send signals because they are never emitted on their own. In fact, the effect from a sub-emitter is "attached" to the renderer queue of the emitter that triggers it.

If you look in the scene's live-view debugger at the behavior of the emitters in your example, you'll see that the sub-emitter never changes its "Emitting" state, even when the results of its action start to appear on the screen. Therefore, if it is never emitted, it cannot return the finished flag.

This also confirms why you only received the finished signal when you manually started the sub-emitter using restart().

These are the words from the documentation:

When you set a particle system as the sub-emitter of another, the system stops emitting, even if the Emitting property was checked. Don't worry, it didn't break. This happens to every particle system as soon as it becomes a sub-emitter. You also won't be able to re-enable the property as long as the particle system is used as a sub-emitter.

Even though the parent particle system can be selected from the list of available particle systems, a particle system which is its own sub-emitter does not work in Godot. It will simply not spawn. The same is true for any other kind of recursive or self-referential sub-emitter setup.

https://docs.godotengine.org/pl/4.x/tutorials/3d/particles/subemitters.html

https://docs.godotengine.org/pl/4.x/tutorials/3d/particles/process_material_properties.html#doc-process-material-properties-subemitter