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.