Particles are small, short lived entities which are controlled by a script. UFOAI uses particles to depict bullets, rockets, explosions, smoke, laser beams, and so on. Some effects are a combination of several particles – in fact a particle can spawn more particles.
Particle are local entities, like actors and aliens. Like other entities the particle has an origin, a think function,
The quake2 engine measures time in milliseconds. For instance, the cl.time variable is defined as “int time” in the client_state_t struct, which means that its size varies depending on the size of an int on the local system. Specifically, f the local system defines integers as 16 bits, then cl.time has a maximum value of 32767, which means it overflows after about 33 seconds. The gnu-linux-386 architecture defines 32 bit integers, which means the cl.time value is good for about 24 days. We shall assume 32 bit integers and therefore a non-overflowing cl.time value.
The UFOAI TD1 particle system measures time in seconds. Time values are stored in float type variables. Basically, the particle functions multiply the quake2 time by a scaling factor of 0.001.
Distance is measured in texels. A typical male actor is 56 texels tall and 24 texels wide. If the height of an average white man is about 180 cm then a texel is about 3.2 cm, or 5/4 inches. A UFOTD1 “cell” is 32x32x72 texels which is about 1x1x2.3 meters.
Every particle in the UFOAI game is an instantiation of a particle definition. Particle definitions are declared by scripts in the base/ufos directory. A particle defintion consists of three functions: a mandatory init function and optional think and run functions. Each function contains commands. A command may or may not have a reference (ie an argument). Here is an example of a particle definition (taken from UFOAI TD1 base/ufos/weaponsfx.ufo).
// ================== // flamethrower // ================== particle fireBall { init { image sfx/fireball blend add style facing tps 2.5 } run { push vector "80 80" mul *t add vector "10 10" pop *size } think { tfade out } }
In the init and run functions each command is written on a separate line, but that is not required. In fact, the whole script could be written without any line feeds or carriage returns. Line feeds/carriage returns are not required – but whitespace must appear around every token. Brace brackets, function names, commands and arguments are tokens. The fireBall particle only uses nine commands. In fact, there are nineteen particle commands. Commands have a data type in the same way that variables have a data typed. Commands may have a reference (ie an argument). The reference and the command must be of the same type. Here is a table of all the particle commands, the data type of the command, and a description of what the command does.
Particle Command |
Command Type |
Description |
---|---|---|
end |
no reference |
Marks the end of a function. This command is not usually seen in script code – it is automatically added to the end of the function by the parser. |
push |
untyped |
Put reference on the stack. The reference may be any type. |
pop |
untyped |
Pop value from the stack to the indicated variable. |
kpop |
untyped |
|
add |
float, pos, vector or color |
Pop value from the stack, add it to reference value, push the sum to the stack. |
sub |
float, pos, vector or color |
Pop value from the stack, subtract reference, push the result to the stack. |
mul |
float, pos, vector or color |
Pop value from the stack, multiply it by reference, push the result to the stack |
div |
float, pos, vector or color |
|
sin |
only float |
calculate sine of reference, push result to the stack |
cos |
only float |
calculate cosine of reference, push result to the stack |
tan |
only float |
calculate tangent of reference, push result to the stack |
rand |
float, pos, vector or color |
push a random value between 0 and the argument onto the stack. |
crand |
float, pos, vector or color |
push a random value between -argument and +argument onto the stack. |
v2 |
no reference |
pop the last two values from the stack, use these values to create a pos type value, and push the pos value onto the stack |
v3 |
no reference |
pop the last three values from the stack, use these values to create a vector type value, and push this vector value onto the stack |
v4 |
no reference |
pop the last four values from the stack, use these values to create a color type value, and push this color value onto the stack |
kill |
no reference |
De-activates the particle. |
spawn |
only string |
Instantiates a new particle. The argument must be a defined particle name. The spawned particle inherits the parent's s, v and a values. |
nspawn |
only string |
Instantiates one or more new particles. The argument must be a defined particle name. The number of particles to create is taken from the stack. The spawned particles inherit the parent's s, v and a values.
|
A command may include a target of the operation. This is called the “reference”. The reference may take several forms: a constant, a stack, or a variable. If the reference is a constant and the command accepts more than one type of reference, then the constant will be preceded by a string specifying the type (float, pos, vector, or color). The octalthorpe (#) reference means that the command operates on the top element on the stack. Variables are more complicated ...
UFOAI TD1 defines twenty-two variables. Each variable has a specific type. Each variable corresponds to a property of the particle (ie a value in its local entity or particle type structure). Most commands can take a variable as an argument. When used as an argument, variables are prefixed with an asterisk “*”. For instance pop *v moves a value from the stack to variable v.
Variable |
Data Type |
Property |
---|---|---|
image |
string |
What the particle looks like; an image from the base/pics directory. This is mostly used for particles which do not have models, ie impact particles. |
model |
string |
The particle's shape; a model from the base/models directory. Models are only rendered if the particle is an entity, which only happens for projectiles. |
blend |
blend |
controls how the particle's image is mixed with the background |
style |
style |
|
tfade |
fade |
“think fade” |
ffade |
fade |
“frame fade” |
size |
position |
Typically size[0] is the length of the weapon in the direction in which it is fired. Some particle represent beams from beams weapons – these beams are considered to travel infinitely fast ie they are instantaneous. For these instantaneous particles, the size is equal to the distance between the origin and the target. |
scale |
vector |
|
color |
color |
particle color |
a |
vector |
particle acceleration |
v |
vector |
particle velocity |
s |
vector |
particle location |
angles |
vector |
<Pitch, yaw, roll>. Typically this is used for projectiles; it points from the muzzle coordinate towards the impact coordinate. |
t |
float |
time that the particle has been active |
dt |
float |
time increment for rendering this particle |
life |
float |
maximum lifetime of the particle |
tps |
float |
Number of times per second to run the think function. |
lastthink |
float |
Time (in seconds) when the think function was last executed. |
frame |
integer |
|
endframe |
integer |
|
fps |
float |
Number of times to show the image (see above) each second. |
lastframe |
float |
Time (in seconds) when the image was last shown. |
You are a coder so you should already know float and integer types. A position type is a set of two positive integers; a vector type is a set of three floats; a color type is a set of four floats, each float having a value between 0 and 1 inclusive (normalized red green blue and alpha channels). Style, fade and blend have specific allowed values defined in source/game/shared.c as shown below:
Blend Values |
Style Values |
Fade Values |
---|---|---|
replace |
facing |
none |
blend |
rotated |
in |
add |
beam |
out |
filter |
line |
sin |
invfilter |
|
saw |
|
|
blend |
The assignment command is simply the name of the variable followed by the value. In the fireBall example image sfx/fireball assigns the string value “sfx/fireball” to the image variable. Note that strings to not require quotation marks. Also, remember that the data types of variable and value must match.
It is possible to specify the individual components of a vector or color. This is done by adding a suffix to the argument. The suffix consists of a period “.” followed by the index of the desired component. For instance, in the particle laserPulse definition:
push float 2.2 pop *size.2
Moves the value 2.2 to size[1].
Weapons fire projectiles: beams, bullets, rockets, etc. When a weapons fires, the LE_AddProjectile function creates an entity, sets the s variable to the location of the muzzle of the gun and spawns particles representing the projectiles. The init function runs as soon as the particle spawns. The le->ptl element points to the particle, so projectiles are both entities and particles.
Next, the LE_AddProjectile function sets the particle's angle attribute to point toward the target. As far as I can tell, this does two things: it rotates the particle image so that the particle (eg a beam) is pointed the right way, and similarly for finite speed particle models. AFAIK the angles element has nothing to do with motion because that is accomplished using le->mins and/or the v and a variables.
If the speed of a projectile is set to zero, then it is an infinite speed projectile: eg laser beam, bullet, SMG. In this case, LE_AddProjectile sets the length of the projectile (size[0]) to the distance between the muzzle and the target and sets the s variable to the midpoint between the muzzle and the target. If the weapons has impact effects, then LE_AddProjectile spawns the impact particles. Then LE_AddProjectile returns. LE_AddProjectile does not kill the particle – all infinite speed projectiles contain a kill command in the think function, so the particle will disappear when the particle entity performs its first think routine. The lifetime of such a particle can be controlled in the init function by manipulating the tps and lastthink variables.
If the speed of a projectile is not zero, then it is a finite speed projectile: a rocket, napalm (from the flamethrower), or tachyon burst. These projectiles fly from target to impact. LE_AddProjectile does the following:
The LET_Projectile function implements motion for finite speed projectiles. Remember that the velocity of a projectile is stored in the le->mins vector. Each time the local entity does a think function LET_Projectile which moves the projectile allow the mins vector towards its target. For this reason, the particle definitions for finite speed projectiles never use the v or a variables.
Impact particles are a little different from projectile particles – most importantly, impact particles are not entities. Since they are not entities, they do not receive an entity think function. Instead, once per frame the CL_Frame function calls the CL_ParticleRun function. For each active particle CL_ParticleRun:
If you want an impact particle to move then you must set the v or a variables in its particle definition.