Juz's Slipgate - Tutorials

 


Explosion Particles Tutorial


Introduction

This tutorial will show you how to produce some simple particle effects for explosions, as seen in mods such as Q3F and CQA, and in the original Quake. Most of the code you'll need to produce these effects is already in place in the Q3A source, so there isn't much work to do. This tutorial assumes you are familiar with C programming and the Q3A source. You'll need to produce a texture and shader for your particles, or sparks, or you can download my example here.

The particle explosion effect in action in my CQA mod. (Click to see a larger image)

Preparatory coding

The first thing to do is to put our shader as a member of the cgs.media structure, and to register the shader so it is precached and we can use it in the code. So, in cg_local.h at the bottom of the cgMedia_t, beneath the line sfxHandle_t wstbactvSound;, put the following code:

qhandle_t sparkShader;

To register the shader, put the following line at the bottom of the CG_RegisterGraphics function in cg_main.c:

cgs.media.sparkShader = trap_R_RegisterShader("spark");

This assumes your particle shader is called "spark", which it will be if you've used my example shader and graphic. If you've called your particle shader something else, change the argument to trap_R_RegisterShader accordingly.

The Particle Launch Function

Most of the work in producing the particles is done in CG_ExplosionParticles, a new funciton which you should add in cg_weapons.c anywhere above the existing function CG_MissileHitWall. The function is defined as follows:

void CG_ExplosionParticles( int weapon, vec3_t origin ) {

int number; // number of particles
int jump; // amount to nudge the particles trajectory vector up by
int speed; // speed of particles
int light; // amount of light for each particle
vec4_t lColor; // color of light for each particle
qhandle_t shader; // shader to use for the particles
int index;
vec3_t randVec, tempVec;

// set defaults
number = 32;
jump = 50;
speed = 300;
light = 50;
lColor[0] = 1.0f;
lColor[1] = 1.0f;
lColor[2] = 1.0f;
lColor[3] = 1.0f; // alpha

switch( weapon ) {

case WP_ROCKET_LAUNCHER:

number = 128;
jump = 70;
light = 100;
lColor[0] = 1.0f;
lColor[1] = 0.56f;
lColor[2] = 0.0f;
shader = cgs.media.sparkShader;
break;

case WP_GRENADE_LAUNCHER:

number = 64;
jump = 60;
light = 100;
lColor[0] = 1.0f;
lColor[1] = 0.56f;
lColor[2] = 0.0f;
shader = cgs.media.sparkShader;
break;

default:
return;

}

for( index = 0; index < number; index++ ) {

localEntity_t *le;
refEntity_t *re;


le = CG_AllocLocalEntity(); //allocate a local entity
re = &le->refEntity;
le->leFlags = LEF_PUFF_DONT_SCALE; //don't change the particle size
le->leType = LE_MOVE_SCALE_FADE; // particle should fade over time
le->startTime = cg.time; // set the start time of the particle to the current time
le->endTime = cg.time + 3000 + random() * 250; //set the end time
le->lifeRate = 1.0 / ( le->endTime - le->startTime );
re = &le->refEntity;
re->shaderTime = cg.time / 1000.0f;
re->reType = RT_SPRITE;
re->rotation = 0;
re->radius = 3;
re->customShader = shader;
re->shaderRGBA[0] = 0xff;
re->shaderRGBA[1] = 0xff;
re->shaderRGBA[2] = 0xff;
re->shaderRGBA[3] = 0xff;
le->light = light;
VectorCopy( lColor, le->lightColor );
le->color[3] = 1.0;
le->pos.trType = TR_GRAVITY; // moves in a gravity affected arc
le->pos.trTime = cg.time;
VectorCopy( origin, le->pos.trBase );

tempVec[0] = crandom(); //between 1 and -1
tempVec[1] = crandom();
tempVec[2] = crandom();
VectorNormalize(tempVec);
VectorScale(tempVec, speed, randVec);
randVec[2] += jump; //nudge the particles up a bit
VectorCopy( randVec, le->pos.trDelta );

}

}

This arguments to this function are the weapon number, for example WP_ROCKET_LAUNCHER and the 3 dimensional vector representing the position of the explosion. First of all a number of variables representing various characteristics of the particles are declared, for example the speed of the particles, the shader to use etc. In the following switch statement, these values are changed for specific weapons. In this example only the rocket launcher and grenade launcher have particle explosions, and the other weapons will cause the default case to be used, and the function returns.

The for loop then creates the actual particles. The variable number holds the number of particles to create, and the code is looped until the correct number of particles have been created. The code inside the loop first allocates a space on the local entity list for the particle, then sets up the values in the local entity that define how the particle will move, how long it will appear for etc.

The lines at the bottom of the loop, starting with tempVec[0] = crandom(); generate a random vector which will act as the movement vector for the particle. This vector is then scaled so the particle is moving at the correct speed, and the upwards component of the movement vector is increased by a small amount to launch the particles move vertically and give a better affect. This vector is then copied to the particle's localentity's pos.trDelta vector, the vector which represents the movent of that particle.

The final step is to call this function by putting the following line at the bottom of CG_MissileHitWall, the function responsible for creating the effects for an explosion:

CG_ExplosionParticles( weapon, origin );

You should now be able to compile your code and see the particle effect produced when a rocket or grenade explodes. I hope this tutorial hasn't been too hard to follow :) Please note that the code shown in this tutorial isn't perfect, it's just a relatively simple and effective method of producing explosion particles. There is plenty of room for improvment to create a more powerful system, but this should provide a base for you to work from. If you have any questions/comments/flaming/mad ramblings about this tutorial, please email me here.

Juz