JavaScript EditorFree JavaScript Editor     Ajax Editor 



Main Page
  Previous Section Next Section

Particle Systems

This is the hot topic. Everyone is always saying, "So, does it have particle systems?" Well, particle systems can be very complex or very simple. Basically, particle systems are physics models that model small particles. They are great for explosions, vapor trails, and general light shows in your game. You have already learned a lot about physics modeling and I'm sure can create your own particle system. However, just to get you started, I'm going to show you how to create a very quick and simple system based on pixel-sized particles.

Let's say that we want to use particles for explosions, and maybe vapor trails. Since a particle system is nothing more than n particles, let's just focus on the model of a single particle.

What Every Particle Needs

If you wanted, you could model collision response, momentum transfer, and all that stuff, but most particle systems have extremely simple models. The following are the general features of a garden variety particle:

  • Position

  • Velocity

  • Color/animation

  • Life span

  • Gravity

  • Wind force

When you start a particle, you will want to give it a position, initial velocity, color, and a life span at the very least. Also, the particle might be a glowing cinder, so there might be color animation involved. Additionally, you may want to have some global forces that act on all particles, like gravity and wind. You may want to have functions that create collections of particles with the desired initial conditions that you're looking for, like explosions or vapor trails. And of course, you may want to give particles the ability to bounce off objects with some physical realism. However, most of the time particles just tunnel through everything and no one cares!

Designing a Particle Engine

To design a particle system you need three separate elements:

  • The particle data structure.

  • The particle engine that processes each particle.

  • Functions to generate particular particle initial conditions.

Let's begin with the data structure. I'll assume an 8-bit display since, in animation, it's easier to work with bytes than RGB colors. Also, color effects are easier to implement in 8-bit color. Converting the particle engine to 16-bit isn't bad, but lots of the effects will be lost, so I have decided to keep it 8-bit for illustrative purposes. Anyway, here's a first attempt at a single particle:

// a single particle
typedef struct PARTICLE_TYP
        {
        int state;           // state of the particle
        int type;            // type of particle effect
        float x,y;           // world position of particle
        float xv,yv;         // velocity of particle
        int curr_color;      // the current rendering color of particle
        int start_color;     // the start color or range effect
        int end_color;       // the ending color of range effect
        int counter;         // general state transition timer
        int max_count;       // max value for counter

        }  PARTICLE, *PARTICLE_PTR;

Let's add in some globals to handle external effects such as gravity in the Y direction and wind force in the X direction.

float particle_wind = 0;   // assume it operates in the X direction
float particle_gravity = 0; // assume it operates in the Y direction

Let's define some useful constants that we might need to accomplish some of the effects:

// defines for particle system
#define PARTICLE_STATE_DEAD               0
#define PARTICLE_STATE_ALIVE              1
// types of particles
#define PARTICLE_TYPE_FLICKER             0
#define PARTICLE_TYPE_FADE                1

// color of particle
#define PARTICLE_COLOR_RED                0
#define PARTICLE_COLOR_GREEN              1
#define PARTICLE_COLOR_BLUE               2
#define PARTICLE_COLOR_WHITE              3

#define MAX_PARTICLES                     128

// color ranges (based on my palette)
#define COLOR_RED_START                   32
#define COLOR_RED_END                     47

#define COLOR_GREEN_START                 96
#define COLOR_GREEN_END                   111

#define COLOR_BLUE_START                  144
#define COLOR_BLUE_END                    159

#define COLOR_WHITE_START                 16
#define COLOR_WHITE_END                   31

Hopefully, you can see my thinking here. I want to have particles that are either red, green, blue, or white, so I took a palette and figured out the color indices for the ranges. If you wanted to use 16-bit color then you would have to manually interpolate RGB from the starting value to some ending value—I'll keep it simple. Also, you see that I'm counting on making two types of particles: fading and flickering. The fading particles will just fade away, but the flickering ones will flicker away, like sparks.

Finally, I'm happy with our little particles, so let's create storage for them:

PARTICLE particles[MAX_PARTICLES]; // the particles for the particle engine

So let's start writing the functions to process each particle.

The Particle Engine Software

We need functions to initialize all the particles, start a particle, process all the particles, and then clean up all the particles when we're done. Let's start with the initialization functions:

void Init_Reset_Particles(void)
{
// this function serves as both an init and reset for the particles

// loop thru and reset all the particles to dead
for (int index=0; index<MAX_PARTICLES; index++)
    {
    particles[index].state = PARTICLE_STATE_DEAD;
    particles[index].type  = PARTICLE_TYPE_FADE;
    particles[index].x     = 0;
    particles[index].y     = 0;
    particles[index].xv    = 0;
    particles[index].yv    = 0;
    particles[index].start_color = 0;
    particles[index].end_color   = 0;
    particles[index].curr_color  = 0;
    particles[index].counter     = 0;
    particles[index].max_count   = 0;
    }  // end if

}  // end Init_Reset_Particles

Init_Reset_Particles() just makes all particles zeros and gets them ready for use. If you wanted to do anything special, this would be the place to do it. The next function we need is something to start a particle with a given set of initial conditions. We will worry how to arrive at the initial conditions in a moment, but for now I want to hunt for an available particle, and if found, start it up with the sent data. Here's the function to do that:

void Start_Particle(int type, int color, int count,
                   float x, float y, float xv, float yv)
{
// this function starts a single particle

int pindex = -1; // index of particle

// first find open particle
for (int index=0; index < MAX_PARTICLES; index++)
    if (particles[index].state == PARTICLE_STATE_DEAD)
       {
       // set index
       pindex = index;
       break;
       }  // end if

// did we find one
if (pindex==-1)
   return;

// set general state info
particles[pindex].state = PARTICLE_STATE_ALIVE;
particles[pindex].type  = type;
particles[pindex].x     = x;
particles[pindex].y     = y;
particles[pindex].xv    = xv;
particles[pindex].yv    = yv;
particles[pindex].counter     = 0;
particles[pindex].max_count   = count;

// set color ranges, always the same
   switch(color)
         {
         case PARTICLE_COLOR_RED:
              {
              particles[pindex].start_color = COLOR_RED_START;
              particles[pindex].end_color   = COLOR_RED_END;
              }  break;

         case PARTICLE_COLOR_GREEN:
              {
              particles[pindex].start_color = COLOR_GREEN_START;
              particles[pindex].end_color   = COLOR_GREEN_END;
              }  break;

         case PARTICLE_COLOR_BLUE:
              {
              particles[pindex].start_color = COLOR_BLUE_START;
              particles[pindex].end_color   = COLOR_BLUE_END;
              }  break;

         case PARTICLE_COLOR_WHITE:
              {
              particles[pindex].start_color = COLOR_WHITE_START;
              particles[pindex].end_color   = COLOR_WHITE_END;
              }  break;

         break;

         }  // end switch

// what type of particle is being requested
if (type == PARTICLE_TYPE_FLICKER)
   {
    // set current color
    particles[index].curr_color
    = RAND_RANGE(particles[index].start_color,
                 particles[index].end_color);

   }  // end if
else
   {
   // particle is fade type
   // set current color
   particles[index].curr_color  = particles[index].start_color;
   }  // end if

}  // end Start_Particle

NOTE

There is no error detection or even a success/failure sent back. The point is that I don't care; if we can't create one teenie-weenie particle, I think I'll live. However, you might want to add more robust error handling.


To start a particle at (10,20) with an initial velocity of (0, -5) (straight up), a life span of 90 frames, colored a fading green, this is what you would do:

Start_Particle(PARTICLE_TYPE_FADE,   // type
               PARTICLE_COLOR_GREEN, // color
               90,                   // count, lifespan
               10,20,                // initial position
               0,-5);                // initial velocity

Of course, the particle system has both gravity and wind that are always acting, so you can set them anytime you want and they will globally affect all particles already online as well as new ones. Thus if you want no wind force but a little gravity, you would do this:

particle_gravity = 0.1; // positive is downward
particle_wind    = 0.0; // could be +/-

Now we have to decide how to move and process the particle. Do we want to wrap them around the screen? Or, when they hit the edges, should we kill them? This depends on the type of game; 2D, 3D, scrolling, and so on. For now let's keep it simple and agree that when a particle goes off a screen edge it's terminated. In addition, the movement function should update the color animation, test if the life counter is expired, and kill particles that are off the screen. Here's the movement function that takes into consideration all that, along with the gravity and wind forces:

void Process_Particles(void)
{
// this function moves and animates all particles

for (int index=0; index<MAX_PARTICLES; index++)
    {
    // test if this particle is alive
    if (particles[index].state == PARTICLE_STATE_ALIVE)
       {
       // translate particle
       particles[index].x+=particles[index].xv;
       particles[index].y+=particles[index].yv;

       // update velocity based on gravity and wind
       particles[index].xv+=particle_wind;
       particles[index].yv+=particle_gravity;

       // now based on type of particle perform proper animation
       if (particles[index].type==PARTICLE_TYPE_FLICKER)
          {
          // simply choose a color in the color range and
          // assign it to the current color
          particles[index].curr_color =
            RAND_RANGE(particles[index].start_color,
                       particles[index].end_color);
          // now update counter
          if (++particles[index].counter >= particles[index].max_count)
             {
             // kill the particle
             particles[index].state = PARTICLE_STATE_DEAD;

             }  // end if

          }  // end if
      else
          {
          // must be a fade, be careful!
          // test if it's time to update color
          if (++particles[index].counter >= particles[index].max_count)
             {
              // reset counter
              particles[index].counter = 0;

             // update color
             if (++particles[index].curr_color >
                              particles[index].end_color)
                {
                // transition is complete, terminate particle
                particles[index].state = PARTICLE_STATE_DEAD;

                }  // end if

             }  // end if

          }  // end else

       // test if the particle is off the screen?
       if (particles[index].x > screen_width ||
           particles[index].x < 0 ||
           particles[index].y > screen_height ||
           particles[index].y < 0)
          {
          // kill it!
          particles[index].state = PARTICLE_STATE_DEAD;
          }  // end if

       }  // end if

    }  // end for index

}  // end Process_Particles

The function is self-explanatory—I hope. It translates the particle, applies the external forces, updates the counters and color, tests whether the particle has moved offscreen, and that's it. Next we need to draw the particles. This can be accomplished in a number of ways, but I'm assuming simple pixels and a back buffered display, so here's a function to do that:

void Draw_Particles(void)
{
// this function draws all the particles

// lock back surface
DDraw_Lock_Back_Surface();

for (int index=0; index<MAX_PARTICLES; index++)
    {
    // test if particle is alive
    if (particles[index].state==PARTICLE_STATE_ALIVE)
       {
       // render the particle, perform world to screen transform
       int x = particles[index].x;
       int y = particles[index].y;

       // test for clip
       if (x >= screen_width || x < 0 || y >= screen_height || y < 0)
          continue;

       // draw the pixel
       Draw_Pixel(x,y,particles[index].curr_color,
                  back_buffer, back_lpitch);

      }  // end if

    }  // end for index

// unlock the secondary surface
DDraw_Unlock_Back_Surface();

}  // end Draw_Particles

Getting exited, huh? Want to try it out, don't you? Well, we're almost done. Now we need some functions to create particle effects like explosions and vapor trails.

Generating the Initial Conditions

Here's the fun part. You can go wild with your imagination. Let's start off with a vapor trail algorithm. Basically, a vapor trail is nothing more than particles that are emitted from a source positioned at (emit_x, emit_y) with slightly different life spans and starting positions. Here's a possible algorithm:

// emit a particle every with a change of 1 in 10
if ((rand()%10) == 1)
{
Start_Particle(PARTICLE_TYPE_FADE,   // type
               PARTICLE_COLOR_GREEN, // color
               RAND_RANGE(90,150),   // count, lifespan
               emit_x+RAND_RANGE(-4,4),  // initial x
               emit_y+RAND_RANGE(-4,4),  // initial y
               RAND_RANGE(-2,2),   // initial x velocity
               RAND_RANGE(-2,2));  // initial y velocity

}  // end if

As the emitter moves, so does the emitter source (emit_x, emit_y) and therefore a vapor trail is left. If you want to get really real and give the vapor particles an even more realistic physics model you should take into consideration that the emitter could be in motion and thus any particle emitted would have final velocity = emitted velocity + emitter velocity. You would need to know the velocity of the emitter source, (call it (emit_xv, emit_yv)) and simply add it to the final particle velocity like this:

// emit a particle every with a change of 1 in 10
if ((rand()%10) == 1)
{
Start_Particle(PARTICLE_TYPE_FADE,   // type
               PARTICLE_COLOR_GREEN, // color
               RAND_RANGE(90,150),   // count, lifespan
               emit_x+RAND_RANGE(-4,4),  // initial x
               emit_y+RAND_RANGE(-4,4),  // initial y
               emit_xv+RAND_RANGE(-2,2),   // initial x velocity
               emit_yv+RAND_RANGE(-2,2));  // initial y velocity

}  // end if

For something a little more exciting, let's model an explosion. An explosion looks something like Figure 13.43. Particles are emitted in a spherical shape in all directions.

Figure 13.43. The particles of an explosion.

graphics/13fig43.gif

That's easy enough to model. All we need do is start up a random number of particles from a common point with random velocities that are equally distributed in a circular radius. Then if gravity is on, the particle will fall toward Earth and either go off the screen or die out due to its individual life span. Here's the code to create a particle explosion:

void Start_Particle_Explosion(int type, int color, int count,
                              int x, int y, int xv, int yv,
                              int num_particles)
{
// this function starts a particle explosion
// at the given position and velocity
// note the use of look up tables for sin,cos

while(--num_particles >=0)
    {
    // compute random trajectory angle
    int ang = rand()%360;

    // compute random trajectory velocity
    float vel = 2+rand()%4;

    Start_Particle(type,color,count,
                   x+RAND_RANGE(-4,4),y+RAND_RANGE(-4,4),
                   xv+cos_look[ang]*vel, yv+sin_look[ang]*vel);

    }  // end while

}  // end Start_Particle_Explosion

Start_Particle_Explosion() takes the type of particle you want (PARTICLE_TYPE_ FADE, PARTICLE_TYPE_FLICKER), the color of the particles, the desired number of particles, along with the position and velocity of the source. The function then generates all the desired particles.

To create other special effects, just write a function. For example, one of the coolest effects that I like in movies is the ring-shaped shock wave when a spaceship is blown away. Creating this is simple. All you need to do is modify the explosion function to start all the particles out with exactly the same velocity, but at different angles. Here's that code:

void Start_Particle_Ring(int type, int color, int count,
                              int x, int y, int xv, int yv,
                              int num_particles)
{
// this function starts a particle explosion at the
// given position and velocity
// note the use of look up tables for sin,cos

// compute random velocity on outside of loop
float vel = 2+rand()%4;
while(--num_particles >=0)
    {
    // compute random trajectory angle
    int ang = rand()%360;

    //start the particle
    Start_Particle(type,color,count,
                   x,y,
                   xv+cos_look[ang]*vel,
                   yv+sin_look[ang]*vel);

    }  // end while

}  // end Start_Particle_Ring

Putting the Particle System Together

You now have everything you need to put together some cool particle effects. Just make a call in the initialize phase of your game to Init_Reset_Particles(), then in the main loop make a call to Process_Particles(). Each cycle and the engine will do the rest. Of course, you have to call one of the generator functions to create some particles! Lastly, if you want to improve the system you might want to add better memory management so you can have infinite particles, and you might want to add particle-to-particle collision detection and particle-to-environment collision detection—that would be really cool.

As a demo of using the particle system, take a look at DEMO13_10.CPP|EXE (16-bit version not available) on the CD. It is a fireworks display based on the tank projectile demo. Basically, the tank from the previous demo fires projectiles now. Also, note in the demo I jacked the number of particles up to 256.

      Previous Section Next Section
    



    JavaScript EditorAjax Editor     JavaScript Editor