Particle Engine
This is based on the Godot’s particle system. In godot we have following options to configure the particle sysytem
-
emitting: on -
amount: 8 -
texture (some texture value): emtpy, if it is empty then makes the particles be squares
-
Time
lifetime: 1.0s - the time in seconds that every particle will stay alive for, when lifetime ends, a new particle is created to replace the old oneone_shot: off - when enabled, a particle node will emit all of it’s particles at once and never again- preprocess: 0.0s - process the particle system for a few seconds before it is actually drawn, this allows for things like torches to be already lit or smoke to be already present once you enter a scene
speed scale: 1.0 - lowering this value will make the system slower while increasing it will make the system fasterNOTE: it also affects the number of particles that we can see, basically it increases the speed of emission of those paricles
explosiveness: 0.0 - If lifetime is 1 and thereH are 10 particles, it means a particle will be emitted every 0.1 seconds. The explosiveness parameter changes this, and forces particles to be emitted all together, this is what allows for things like explosions to be programmedNOTE: key point is the description that if the lifetime is 1.0s and the amount is 10, then a particle will be emitted every (lifetime/amount) second
randomness: 0.0NOTE: formula for randomization of any physical property is:
initial_value = param_value + param_value * randomness- use fixed seed(bool): off
- fixed FPS: 0
- fract delta: on
-
Drawing
visibility rect- this is similar to broadphase collision detection, basically at every Particle2DNode, there is a rectangle present, godot basically checks whether this rectangle is in the view(the visible screen space) then only the particles are rendered on the screen, but if the entire rectangle is out of view then the particles from that node are not renderedlocal cords: off - this controls whether the particles retain the relative position of the node that emitted them, by default this is “true” which means that if at any moment during emission, the node itself is moved, then all the particles which “have been” emitted also move relative to the position of the node, but if this is off, then only the position of the particles that will be emitted changes with the node, but the particles which are already emitted do not change their position relative to the node- draw order(select between 2 options): (Index,Lifetime), default is index
-
Particle Flags
angle: Determines the initial angle of the particle (in degrees). This parameter is mostly useful randomized.velocity:- direction: This is the base direction at which particles emit. The default is Vector3(1, 0, 0) which makes particles emit to the right. However, with the default gravity settings, particles will go straight down.
NOTE: For this property to be noticeable, you need an initial_velocity greater than 0, if we set the initial velocity to something like 40, the particles firstly go to the right then succumb to the effect of gravity
- direction: This is the base direction at which particles emit. The default is Vector3(1, 0, 0) which makes particles emit to the right. However, with the default gravity settings, particles will go straight down.
spread: This parameter is the angle in degrees which will be randomly added in either direction to the base Direction. A spread of 180 will emit in all directions (+/- 180). For spread to do anything the “Initial Velocity” parameter must be greater than 0.initial_velocity: Initial velocity is the speed at which particles will be emitted (in pixels/sec). Speed might later be modified by gravity or other accelerationsNOTE: unlike “speed_scale”, this parameter cannot modify the number of particles appearing on the screen, but it can only modify the velocity of the particle appearing on the screen, but the rate and number of emission of particles stays the same
angular_velocity: the velocity at which particles rotate around their center (in degrees/sec).damping: force of friction for the particles so that they slow down after they are emittedscale: the size at which the particles start out at, you can make it so that they slowly shrink in size as their lifetime decreases
Implementation
On the base of it, there is the ‘particle_pool’ which is of type std::vector<Particle>. This is a simple list of Particle objects which is created with a fixed size every time a ParticleEngine is initiated. One thing to keep in mind is that once it is initialized to a specific size, that size will never be increased or decreased and we will only recycle the particles in the vector, neither delete nor create new ones.
We start by creating a simple Particle struct and ParticleEngine class:
struct Particle {
Vector2 pos;
Vector2 vel;
bool alive{ false };
float lifetime;
float age{ 0.0F };
};
class ParticleEngine {
private:
std::vector<Particle> particle_pool;
float cycle_time{ 0.0F };
int next_slot{ 0 };
std::mt19937 rand_gen;
public:
// particle engine settings
bool emitting{ true };
int amount{ 10 }; // the total number of particles per cycle
float lifetime{ 1.0F }; // how long you want an individual particle to stay alie for (in seconds)
float velocity_scale{ 30.0F };
float explosiveness{ 0.0F };
// drawing functions
std::function<void(Vector2 pos, float radius, Color color)> render_circle_fcn{ nullptr };
// methods
ParticleEngine(int pool_size)
: rand_gen(std::random_device{}()), particle_pool(pool_size) {};
Particle* alloc_particle();
void reset_cycle();
void spawn_n(Vector2 spawn_pos, int count);
void emit_stream(Vector2 emit_pos, float delta_time);
void emit_burst(Vector2 emit_pos);
void update(float delta_time);
void render();
};Don’t worry about the fields and methods in either of these because we will slowly go over all of them.
Emission Cycles
If you ask any beginner(real beginner) on how to make a particle system they will probably just say to spawn a particle evey frame(or every few frames) and that way you get a stream of particles and if you want a burts of particles on any event, then just spawn (n) number of particles on that event and you have it.
This theory is absolutely correct this is the way you would go about creating a particle system and this is the way this particle system is created. The key difference is some fancy words.
How this particle system works is that we consider a spawn amount, this is the number of particles that we have to spawn per “spawning cycle”, a “spawning cycle” is just a name for a time period of length (L) in which we have to spawn the fixed number of particles (N or amount), we calculate the cycle period based on the specified lifetime field in the particle_engine.
Just think of it like this, for a steady stream of particles when the first one spawns, the job of the particle engine is to spawn amount (or N) number of particles before that particle dies, this is termed as one spawning cycle (or emission cycle, the name is arbitrary).
One thing you can conclude from all this is that for a lifetime of 1.0 second, and amount of 10, the particle engine will be spawning particles at the rate of 10 particles/sec (amount/lifetime) and a particle will be spawned every 0.1 second (lifetime/amount)
Note: the rate and timing of emission of each particle is further modified by
explosivenessvalue, which is further discussed later on
In order to keep track of how many particles have been spawned since the cycle started, we use the next_slot field, which is pretty self explanatory. Every time we spawn a particle, we increment it by one and once the cycle ends, we just set set it back to 0. The cycle_time field tracks how much time has it been since the cycle started and also goes to zero when the cycle ends.
The thing that determines whether the cycle has ended or not is the
lifetimefield, we just check whethercicle_time>=lifetimeand if it is true, then it concludes that the cycle has ended
We cannot just emit particles at random intervals during the cycle, we have to keep track of when each particle is going to emitted during the cycle, this is where spawn_at_time value comes in (this is not a class member, it is a local variable in the emitting function). The function for calculating spawn_at_time is below
now the window here refers to the spawn_window (another local variable) which is also a special thing. This basically keeps track of how much time we have left to emit all the particles. For a steady stream of particles this is equal to lifetime but, we often times do not need a steady stream and in that can case we use the explosiveness field of state_engine, and we get the following function:
NOTE:
explosivenessjust measures how much you want the particles to be emitted together, for an explosiveness of 0, the particles will follow a steady stream, between 0 and 1, some particles will emit at the same time and some later or before, for more than 1, all the particles will be emitted together in a burst
Here, the x is next_slot, and f(x) is the spawn_at_time which calculates at what time interval will a particle of that slot be spawned in. In order to make this sure we just check if for the current value of next_slot the calculated is less than the cycle_time, so basically we have some time spawn a particle, then we just spawn that particle and increment next_slot, if it is not less than or equal to cycle_time then that means that we don’t have time to spawn another particle
You can check out the particle_engine.hpp and particle_engine.cpp files for more information on how the particle_engine is actually implemented.