Previous: 1.c Essentials of vector algebra
With these notions fresh in our mind, we now go back to effects programming. As we saw earlier, the effect and ent_create functions work roughly the same way.: we create the object somewhere, and assign it a function which will determine its behavior. However, there is one big difference in how these functions are written: the wait() instruction is forbidden in particle programming, which brings as immediate consequence that the while(1) instruction is banned as well.
You might therefore wonder: "How are we supposed to initialize the variables and flags of particles if we cannot separate the function in one part which is performed only once and initializes the particle, and another part which is executed at each frame using the while(1) instruction?".
The answer is shown below where I wrote equivalent lines of code both for an action and a particle function. First for the entity's action:
action a_entity { my.red = 255; my.green = 255; my.blue = 255; my.scale_x = 1.5; my.scale_y = 1.5; ... while(1) { my.x += time*speed.x; my.y += time*speed.y; my.z += time*speed.z; ... wait(1); } } (1.10)
This is traditional programming. The instructions before the while(1) loop will be performed just one time during the first frame of existence of the entity, setting the initial state of the entity (its color, scale, etc.). The instructions in the while(1) loop will be done over and over until the entity disappears but, thanks to wait(1), only once per frame. Note also the equation of motion for vector my.x which moves the entity across the level with vector velocity speed.
Now for the particle function:
bmap image = <image.pcx>; function a_particle() { if (my.lifespan==80) { my.red = 255; my.green = 255; my.blue = 255; my.size = 100; my.bmap = image ... } my.vel_x = speed.x; my.vel_y = speed.y; my.vel_z = speed.z; ... } (1.11)
Instructions here too are separated between a group which is only performed once, right after the particle is created, and the other which is repeated over and over once per frame until the particle is eliminated. For this, we use a trick and take advantage of the variable lifespan which is set to 80 ticks by default at particle creation: all instructions in the if (my.lifespan==80) {...} condition are performed only once, ideal for initiation of the particle's characteristics. Why is that? Because at the second frame of the particle, lifespan will be equal to 80 - time, and therefore the condition my.lifespan==80 will be false.
Note that, unlike for ent_create(), the image representing the particle in the level is defined in the function itself and not in the effect() statement. Also, one chooses directly the size in quants of the particles, not their scale.
All the instructions which follow the if (my.lifespan==80) {...} instruction will be done at each frame, exactly like in the while(1) loop above. This nice little trick allows us to change dynamically the aspect and behavior of the particles with time. The following lessons are all about finding how to do so in order to get specific effects.
You may ask yourself "Why is there no time factors in the definition of the speed in a_particle but there are in a_entity?".
The answer is simply that there is no need for it. Indeed, in a_entity, we have time factors in the equation for my.x to compute the position of the entity from its velocity (see the Tuesday lesson of the C-script tutorial and equation (2.6) below). However, the effect statement possesses system-defined variables vel_x, vel_y and vel_z. With these, it will compute automatically the position of the particle at the next frame, adding all by itself the necessary time factors in background calculations. We therefore do not have to do it ourselves.
"So are we done now and ready to do some serious particle effects?"
Well, actually no. After finishing this document, I was explained by Conitec that programming particles using an if (my.lifespan==80) {...} statement to separate the instructions which initialize the particle from those dictating its subsequent dynamics, is wasteful of CPU resources. Indeed, this if statement will be evaluated at each frame, for every particle. When you have 10 000 particles and the game is running at 80 fps, that's indeed a LOT of if's to evaluate every second (800 000 to be exact)! I was told that one could almost double particle speed by using 'starter' functions instead. I tried it with all the examples in this tutorial and indeed found a gain in speed.
Here is how it's done, using again the example above.
bmap image = <image.pcx>; function starter_particle() { my.vel_x = speed.x; my.vel_y = speed.y; my.vel_z = speed.z; ... } function a_particle() { my.red = 255; my.green = 255; my.blue = 255; my.size = 100; my.bmap = image ... my.function = starter_particle; } (1.11bis)
We first create a 'starter' function starter_particle which contains all the instructions that we want the particle to perform after its initialization. We then construct the particle function a_particle as follows. First, we put the code to initialize the particle, straight up, without any if (my.lifespan==80) {...}. We then add a line of code to set the particle-specific variable function (about which the manual could be a little more explicit, I think :-)) to the starter function starter_particle defined above.
I tried it: it works. :-) Apparently, what seems to happen is that 3DGS understands that it must carry out the instructions before the my.function = ... only once, and perform only what is in the starter function at each subsequent frame. So we do not need to waste CPU time at each frame to remind it using if statements. If you do not have any instructions to write in the starter function, i.e. the particles only need to be initialized and then left alone, then simply put my.function = null (otherwise 3DGS would re-initialize the particle at each frame).
So, to my knowledge, this is the absolute-best-most-efficient way to program particles. However, as this way of particle programming was still news to me a couple of days ago, the reader will notice that all the examples below and the text which accompanies it, use the if (my.lifespan==80) {...} trick. This is no problem though since it does not modify or invalidate any of the equations presented here, which are the real core of this tutorial. The examples, as presently coded, are just running a little slower than necessary. It only takes a few minutes to write the lines of code which upgrade them to starter-function programming. I'll leave that as an exercise to the reader. ;-)
That's it for the preliminaries. Now, let's do some particle effect programming...