godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.16k stars 97 forks source link

Improve behaviour of `emitting` property for `one_shot` ParticleSystems #7322

Open HolonProduction opened 1 year ago

HolonProduction commented 1 year ago

Describe the project you are working on

Godot Engine

Describe the problem or limitation you are having in your project

The behavior of the emitting property on one shot ParticleSystems is not very intuitive. Calling restart or setting emitting for the first time do the same. When turning emitting of after that the particle system will not emit anymore particles. But now when changing emitting to true again it will be reset to false without any effect. Using emitting = true again is only possible after the particle system would have finished if emitting was never set to false. In addition this reseting takes some time for updating the inspector which makes it feel very bugged. This can confuse users about the stability of particle systems in general and does not help with solving any problems.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

I propose, that setting emitting to true will always trigger restart internaly if one_shot == true. The current behavior feels buggy and serves no real purpose so it should be changed to feel more usefull and reliable. The behavior for setting emitting to false will stay the same as it works and there might be use cases for that.

As the current solution is more like undefined behavior this is not a breaking API change IMHO

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

func set_emitting(p_value):
    ...
    if one_shot and p_value:
       restart()
    ...

I can implement that. The proposal is ment to get feedback on the idea.

If this enhancement will not be used often, can it be worked around with a few lines of script?

Nope

Is there a reason why this should be core and not an add-on in the asset library?

Can only be solved in core

QbieShay commented 1 year ago

Would have to be very careful about interactions with animation player. It seems to be an on and off "regression" kind of thing, but sometimes it triggers bools every frame, sometimes only on switch. Furthermore I'm not sure restart is what we want here, because that kills all active particles, while i think that there's some cases where you may just want to restart it after a delay, but stilll letting the emitter finish to avoid things popping out of existence

I imagine the usecase is seeing your vfx in the editor without having to wait their "real" end in emitting toggle.

I suggest 2 things to this:

  1. A toggle on animation player that forces a restart of particles. It should be possible to keep this at runtime as well. Maybe a property of the track on the particle, or something like that.
  2. A rework of oneshot that makes particles just start emitting if you toggle it on again before the time it has finished. I wouldn't necessarily reset. But also I really never use oneshot particle due to the problematic interaction with animation player, so if this doesn't work im fine with reset too.
QbieShay commented 1 year ago

For me specifically I have my own implementation of oneshot that runs only in game because it's super annoying to work with when you're dealing with complex VFX scene.

cybernaut4 commented 1 year ago

2. A rework of oneshot that makes particles just start emitting if you toggle it on again before the time it has finished. I wouldn't necessarily reset. But also I really never use oneshot particle due to the problematic interaction with animation player, so if this doesn't work im fine with reset too.

I agree, though if possible it should've been done in a way that a reset isn't needed, OneShot should work to spawn particles at customized intervals defined by:

  1. the user (gamedev) on script
  2. non-interactive animationPlayer, or
  3. player input.

But currently as it is, especially with explosiveness values high, it doesn't work reliably.

The workaround I use today is to duplicate the GPUParticles3D node at runtime, make it unique then play that one. While this workaround gets the job done, it costs performance and is unsightly to think about. Furthermore, this method has very limited utility compared to the potential one GPUParticles3D node could've had:

If reliable, it could've spawn particles at intervals fully controlled by script, or when player manually toggles things, whether directly or via AnimationPlayer. GPUParticles3D today, in this case, isn't that much reliable.

Examples with one-shot + explosiveness:

Electrical, malfunctioning prop:

Using a reset to re-emit OneShot particles would vanish the sparks before they hit the floor.

Traps spitting fire-bursts:

Unfortunately I have no idea how this could be done, as I'm not one with the Engine's code (nevermind C++) but I thought bringing up front the potential this system could've had would be worth noticing.

Gatada commented 1 year ago

Is this suggesting that setting emitting = true should work like resetting? So if set before the one-shot emitter has finished (i.e. all particles are dead) all particles mid-air vanish as the emitter restarts?

If so, then I oppose this idea: If the emitter isn't done, setting emitting = true shouldn't do a reset (kill existing particles and restart), but more like a resume: leave live particles to reach the end of their lifespan while returning to "frame 0" of the emitter.

ArkyonVeil commented 1 year ago

Tried my own way to fix it by forcing a particle restart when both emission and activity are false #https://github.com/godotengine/godot/issues/83909#issuecomment-1779657950. Which while great when testing particle effects in editor, It's understandable how re triggering oneshot in rapid sequence intentionally could lead to hiccups.

What makes this difficult to work with is that one shot is a property of the Particle Node, but not the Particle Resource. As far as I know, the Particle Resource has no awareness of what one shot even is, meaning that it's... well, clear why it's problematic in fixing.

QbieShay commented 1 year ago

I've read the concerns in this proposal and i feel like right now we're stuck in this conundrum because we're trying to shoehorn something that's logically different into a property. Please bear with me, I'll explain what i mean.

Properties of an object are meant to represent the state of an object. The pen is blue. The water is cold. Functions of an object are meant to invoke functionality of an object. The pen breaks. The water shoots out of a fountain.

If we consider the situation with particle, we're in a bit of a funny situation here: if a particle system was a hose with water inside, it being emitting or not would be a state of the emitter (hose). But suddenly, if we use oneshot, emitting is now a punctual thing that we need to trigger at a specific point in time. It's a function. Gunpowder in a gun doesn't make it shoot, someone has to pull a trigger to make it explode and shoot.

So the reason why particles aren't quite working for oneshot is because we're using the wrong tool for the job. One of the reasons why we're using the wrong tool for the job is also because the animation player cannot trigger functions in editor (for good reasons, but that doesn't make it any less problematic for us)

I think here we need to take a step back and consider how we'd solve this problem in the beginning, if particles didn't exist and we were designing them from scratch, and then try and see what we can do to fit this new approach in the existing one. If we can. And if we cannot, how to extend things in a non-compat breaking way.

Gatada commented 1 year ago

I have been working on improving the documentation for the GPU particles, and the revised version adds a tip that the developer should use restart() after receiving Finished signal from the one-shot emitter to reset the emission cycle.

https://github.com/godotengine/godot/pull/83622

The way "emitting" currently works is in my opinion how is should work. When the documentation is updated, I think how the emitter works is entirely intuitive.

QbieShay commented 1 year ago

The oneshot workflow remains a pain to work with and I agree with the people in the thread here that it needs improvements.

Xinart commented 9 months ago

Hello there ! I just wanna say as well i'm having issue with that and that's so strange beceause Godot is such a smooth and fluid game Engine and when it comes to instanciates particules .. Yeargh

I'm just referecing the only way to workaround this without code i know image

Dusk-Dawn commented 6 months ago

My problem with OneShot is not even restarting it, what I would like is to play the particle again without deleting the existing particles

for example, my raycast gun has a hit effect particle. I would like to move it to the hit particle to the inpact point and play it. if I try to do it again while the previous play is not finished I have to use restart() and that will erase the current effect and look jarring. so for now I am spawning a new particle every time I need the hit effect, but that's not very performant

QbieShay commented 6 months ago

@Dusk-Dawn do you mind opening a proposal about being able to restart particles and let existing ones continue to exist?

I can tell you already that due to godot's architecture it will probably be quite complex to achieve, but I do agree it's worth thinking about solutions for this.

The way I've seen this done is to have multiple hit effects, one for one shot hit effects (which you spawn on every hit) and one for continuous hit effects of which you control emissions and position