simpler question than usual from me, 'cause I can't figure out what search terms to use or how this would work on my own.

I have a particle emitter, and I want the amount to go down over time to signify that the slowdown state the character is in is about to end. right now I'm doing that by saying, "if timer_amount is between 2 and 3 seconds, change amount to 6" , "if timer amount is between 1 and 2 seconds change amount to 4"

problem is, that looks really choppy. it doesn't gradually decrease. it causes a reset. is there a way I can make the amount of particles emitted go lower without causing it to reset like that?

  • xyz replied to this.

    is that the only way? because that would mean having to re-code a lot of stuff the automatic emitter is doing for me right now.

    • xyz replied to this.

      samuraidan Automatic emitter just puts out particles at some rate. With emit_particle() call you replace that automatic triggering with a manual trigger. All other particle settings will be retained. You can even use automatic emission up to a point you need to slow down, then disable it and continue with manual.

        xyz if that's all it does, why does it take five arguments?

        edit: on top of that I keep looking over the docs you linked me and I see color, scale, velocity, position, and rotation, but not amount. how do I change the amount??

          so if I'm understaning you right, then this:

          if $slowdown.time_left < 3 and $slowdown.time_left > 2:
          	$web_particles.amount = 8
          elif $slowdown.time_left < 2 and $slowdown.time_left > 1:
          	$web_particles.amount = 4
          else:
          	$web_particles.amount = 2

          should become this?:

          if $slowdown.time_left < 3 and $slowdown.time_left > 2:
          	for i in 8:
          		$web_particles.emit_particle(self.transform,Vector2(0,0),0,0,0)
          elif $slowdown.time_left < 2 and $slowdown.time_left > 1:
          	for i in 4:
          		$web_particles.emit_particle(self.transform,Vector2(0,0),0,0,0)
          elif $slowdown.time_left < 1 and $slowdown.time_left > 0:
          	for i in 2:
          		$web_particles.emit_particle(self.transform,Vector2(0,0),0,0,0)
          else:
          	$web_particles.emitting = false

          well, that almost worked. except now it's acting like "explosive" is ticked and firing them all at once.

          edit: I've just realized the problem is that it's in _process so the for loops are firing all at once- but that makes me wonder how I'm supposed to manually replicate the semi-random looking "one after the other" timing the auto emitter has.

          • xyz replied to this.

            samuraidan No, it becomes this:

            extends GPUParticles2D
            
            var custom_pps: = 2.0;
            
            var emit_time = 0.0
            func _process(delta):
            	emit_time += delta
            	while emit_time > 1.0 / custom_pps:
            		emit_particle(Transform2D.IDENTITY, Vector2.ZERO, Color.BLACK, Color.BLACK, 0)
            		emit_time -= 1.0 / custom_pps

            Animate the custom_pps property and you're done.

            I've been trying to understand this answer for three days now, and I'm still not getting it really.

            everything up to emit_tile += delta I get, but what is custom_pps (pixels per second?), why is it delta time divided by custom_pps, why is transform2D.identity in there (is it just a placeholder for an as yet unspecified object?) and why is the final line also divided by custom_pps? I think you're subtracting 1.0 from it to cause the gradual decrease I want but I'm a little fuzzy on that too.

            ..and for that matter why is there no simpler way than all this just to make it slow down over time? why can't I just lower the amount value like what makes sense in my brain? if I want to make the thing smaller over time I just decrease the scale value. if I want to make it less visible over time I just decrease the alpha. but if I want a particle system to emit less I have to override the emitter and animate a custom value? why?! (I don't expect an answer to this one but it'd be nice to get one anyway)

            edit: I did a lot more snooping about this issue and found a five year old bug report about this exact issue stating it might be fixed in 4.0. I'm not sure how to feel about that

            double edit: after snooping even more, I've discovered that the actual simplest answer is to wait for 4.2 to release because it's adding the exact feature I just complained about not having.

            • xyz replied to this.

              samuraidan You can't specify emission rate directly. The rate is implicitly defined via amount and lifetime; rate = amount/lifetime. So increasing the lifetime will slow down the apparent rate without resetting the simulation. Don't know why they decided to do it that way. Probably because having control over total amount of particles is desirable for easier performance control and scaling with large particle systems.

              But as you can see from my example, it's relatively simple to extend a particle system emission to be fully controlled via pps.

              About the code.

              As an input parameter we use particles-per-second rate (pps). Other name for rate is frequency . Frequency is how often some event happens during a time span of one second. So if frequency = 5, the event will happen 5 times in a second.

              If we know frequency, we can easily calculate how long it will be between two consecutive events. That time is called the period. It is reciprocal to frequency:

              period = 1 / frequency

              If, for example, a quacking duck quacks 4 times in a second, we can say that the quacking frequency is 4. The time between two consecutive quacks is; 1 / frequency = 1 / 4 = 0.25. We can say that the quacking period is 0.25.

              Now to the actual code.

              We give the frequency as an input via the custom_pps property. In order to know how often we need to emit a particle, we have to calculate the period. As we've seen above the period is 1.0 / custom_pps.

              In _process(), we accumulate time in emit_time property by adding passed delta time each frame. Once the emit_time gets larger than the period, it's time to emit a particle and reset the emit_time counter, so it can count again for the next particle.

              To illustrate this with a more beginner friendly example:

              var frequency: = 2.0;
              
              var emit_time = 0.0
              func _process(delta):
              	period = 1.0 / frequency
              	emit_time += delta
              	if period > emit_time:
              		emit_particle(Transform2D.IDENTITY, Vector2.ZERO, Color.BLACK, Color.BLACK, 0)
              		emit_time = 0

              This will work ok for low frequencies. However it will lose precision at high frequencies. The emit_time can shoot over period time and this leftover, however small, may affect the precision of time keeping at high frequencies. To account for that, we keep track of that leftover time by subtracting the period from emit_time, instead of resetting to 0:

              var frequency: = 2.0;
              
              var emit_time = 0.0
              func _process(delta):
              	period = 1.0 / frequency
              	emit_time += delta
              	if period > emit_time:
              		emit_particle(Transform2D.IDENTITY, Vector2.ZERO, Color.BLACK, Color.BLACK, 0)
              		emit_time -= period

              However, there's a still sneaky bug lurking in the code. It becomes apparent when the frequency is so high that the emit_time is more than 2 times longer than the period. This means we need to emit multiple particles. To handle such cases we emit in a loop, subtracting the period from emit_time until it becomes less than the period. Now we emitted all particles that were due inside our emit_time.

              var frequency: = 2.0;
              
              var emit_time = 0.0
              func _process(delta):
              	period = 1.0 / frequency
              	emit_time += delta
              	while emit_time > period:
              		emit_particle(Transform2D.IDENTITY, Vector2.ZERO, Color.BLACK, Color.BLACK, 0)
              		emit_time -= period

              As for arguments to emit_particle(), they are irrelevant. I used "placeholders" that are least expensive and stylistically suggest default values. Vector2.ZERO is a built in constant that doesn't have any additional performance or memory footprint when used as an argument, in contrast to Vector2(0, 0) which will needlessly create a new vector object every time it's called.

              This is primarily a matter of style but it may contribute to performance when a large number of particles come into play.