white_door - particle

Making a simple 2d particle engine - Nathan Smith 

Particle engines can be very useful in adding interest to a game.

The two effects I will look at in this tutorial is rain and snow, but the particle engine should be flexible enough to add other nice effects.

First what is a particle? A particle is single small object that can be modelled to behave in a certain way, examples include a drop of rain, a snow flake, or even a falling leaf.

Start the particle structure with a x,y for its position on the screen, then add a dx,dy (delta x, delta y) these are the amount the particle moves each logic update. I have a rx,ry for the amount of randomness in the particles movement. For the rain we need to leave a trail so I like to include a tx,ty for the end point of the trail. We also need a color variable. And finally the life variable, this is a counter that counts how many frames the particle will live for.

However that is just one particle how do we store a large number of them? The best way is to use a unordered linked list, but you can also use a simple array if you know for certain that there will never be over a certain number of particle alive at any given time.

typedef struct particle
{
double x,y,      // location of the particle on the screen
dx,dy,       // speed and direction of movement
rx,ry,       // the amount of randomness in the movement
tx,ty;       // the location this particle was last at.
int color,      // the particle's color
type,         // the drawing type of the particle
life;         // This is a counter!
// When the counter hits zero we remove the particle.
struct particle *next; // a link to the next particle.
} particle;

#define RAIN_PARTICLE 0 // here are just some types for the drawing types :)
#define SNOW_PARTICLE1 1
#define SNOW_PARTICLE2 2
This structure will store all the information we need for both the rain and the snow effect. However the first step is to create a particle:
struct particle *create_particle(struct particle *parent)
{
struct particle *tmp;
 tmp = (struct particle *)malloc(sizeof(struct particle));
 tmp->next = parent;
 return tmp;
}

The basic idea is we will end up with a chain of particles something like this:

particle 1 -> particle 2 -> particle 3 -> ... -> particle n -> null

With the next pointer in the structure linking to the next particle in the chain.

The function can be used like this:
lastparticle = create_particle(lastparticle); 
And this will add a new particle on to the linked list in front of the last particle. However this function only allocates the memory for our particle. It doesn't define any information about it.

Also remember that because this function allocates memory, this memory must be freed once we are finished with it or the program may run out of memory and it might crash.

In order to have proper rain we will add a new function that will define all the information we need on a brand new particle of rain.
struct particle *create_rain_drop(struct particle *parent,double direction)
{
struct particle *tmp;
 double r;

 tmp = create_particle (parent);  // We allocate the memory for the particle here.

 // place the rain in a random place on the screen

 tmp->x = float(rand()%(SCREEN_W+40))-20;
 tmp->y = float(rand()%SCREEN_H)-60;

 // decide speed of the particle

 r = ((double)(rand()%30))/10+20; // good rain needs to be fast! ;)

 // set the speed & direction

 tmp->dx = r*sin(direction);
 tmp->dy = -r*cos(direction);

 // rx & ry should be 0 since we don't want the rain to wobble.

 tmp->rx = 0;
 tmp->ry = 0;

 // don't forget to add life to the particle.

 tmp->life = rand()%30+60;

 // I like my rain grey coloured ;)

 tmp->color = makecol(220,220,220);

 // and finally set the drawing type

 tmp->type = RAIN_PARTICLE;

  return tmp;
}

As you can see it just sets up all the of fields in a newly created structure and returns it. In fact it can be used in the same way as the create_particle function. So in many ways this can be looked at, as an extention of the create_particle function.

To create a snow particle we the same thing but with a few changes.
struct particle *create_snow_flake(struct particle *parent)
{
struct particle *tmp;
double r,direction;
 int c;

 tmp = create_particle (parent);  // once again start by creating the particle

 // place the snow in a random place on the screen

 tmp->x = float(rand()%(SCREEN_W+20))-20;
 tmp->y = float(rand()%(SCREEN_H+20))-20;

 // decide speed and direction of this particle of snow

r = float(rand()%15)/15+1; // notice the snow is much slower than the rain was
 direction=rand()%64+64;

 // set the speed & direction

 tmp->dx = r*sin(direction);
 tmp->dy = -r*cos(direction);

 // snow is very light so it tends to be blown about, so we add some randomness
// to its movement.

 tmp->rx = 1;
tmp->ry = 1;

 // don't forget to add life to the particle.

 tmp->life = rand()%30+60;

 // snow should be all white.. but I made it a random shade of grey to make it look
 // more... interesting.

 c = rand()%128+128;
 tmp->color=makecol(c,c,c);

// and finally set the drawing type
// randomly set it to either of the two flake types.

 tmp->type = rand()%2+SNOW_PARTICLE1;
return tmp;
}
We now we have the functions for creating particles... However what happens once a particle dies or when we are done with the program? Well we need to remove it from the list of particles. Here is a function to help us do it:
struct particle *clean_particles(struct particle *t,int all) 
{
struct particle *tmp;
struct particle *top;

top=t;
tmp=NULL;

 while(t) { // basically we are looping through all the particles
// in the list.
if(t->life<0||all) { // is the particle is dead or if we are deleting all particles
  if(tmp) { // then delete it!
   tmp->next=t->next;
   free(t);
   t=tmp->next;
  }
  else {
   top=t->next;
   free(t);
   t=top;
  }
 }
 else {
  tmp=t;
  t=t->next;
 }
}
return top; // return the list back, possibly empty :)
}
Now that was a complex function, but it is as important as the function to create the new particles!

The next step is to draw the particles on the screen!! ;) Now each type of particle must be drawn in a different way
void draw_particle(BITMAP *bmp, struct particle *t) 
{
 switch(t->type) {
  case RAIN_PARTICLE:  // for rain just draw a simple line
   line(bmp, (int)t->x, (int)t->y, (int)t->tx, (int)t->ty, t->color);  
   break;

  case SNOW_PARTICLE1: // just draw a single pixel for a small flake of snow
   putpixel(bmp, (int)t->x, (int)t->y, t->color); 
 break;

case SNOW_PARTICLE2:
   putpixel(bmp, (int)t->x, (int)t->y, t->color); 
 putpixel(bmp, (int)t->x-1, (int)t->y, t->color); 
 putpixel(bmp, (int)t->x+1, (int)t->y, t->color);
 putpixel(bmp, (int)t->x, (int)t->y-1, t->color);
 putpixel(bmp, (int)t->x, (int)t->y+1, t->color);
 break;
 }
}

Another way to have done the snow would be to have draw a filled circle. Which might have looked nicer, I'll leave that up to you to try. The draw_particle function only draws a single particle though, and not all of them so we will need to loop through whole list again:
void draw_particles(BITMAP *bmp, struct particle *t) 
{

while(t) { // once again we are just looping through all the particles
  draw_particle(bmp,t);
  t=t->next;
}
}

Woah that was easy eh? its fairly easy when you don't have to worry about deleting stuff ;)

However if you stopped at this point you would find your particles wouldn't move!!! they would just be frozen. :( So we need a function that will update the particles and move them around etc...
void update_particle(struct particle *t)
{
 // first store the old positon as the end of the trail

 t->tx = t->x; // first things first! store the current position of the particle
 t->ty = t->y;

 // then update the position using its speed & direction

 t->x += t->dx; 
 t->y += t->dy; 

 // now add any randomness

 if(rx>0) t->x += rand() % (t->rx*2) - t->rx; 
 if(ry>0) t->y += rand() % (t->ry*2) - t->ry;

 // subtract one from the particles life.

 t->life--;
}
Some particle engines also change the color each logic update, but I have never liked that one. Notice this function also subtracts one from the particle's life counter... in the case of rain & snow.. a particle is meant to vanish when it touchs the ground.. but we just fake it.

As with the draw_particle, we need a function to call update_particle on ALL the particles in a list.
void update_particles(struct particle *t) 
{

while(t) { // and once again we are just looping through all the particles
  update_particle(t);
  t=t->next;
}
}
As you can see from the examples the adding of new particle types is very easy, and by having one particle engine for all your different particle effects you reduce the coding effort. 

This does have one major flaw, if you are using the weather over a 2d scrollable map, when the map is scrolled the particles are not scrolled with it. This causes an ugly effect! However by making a small change to the update_particle function....
void update_particle(struct particle *t,int scroll_x,int scroll_y)
{
...
t->x += scroll_x + dx;
t->y += scroll_y + dy;
...
}
As long as the scroll amount isn't too big... it won't cause any problems.. but if the scroll_x and scroll_y suddenly jumps... it will leave gaps on the edges of the screen.

Here is an example main game loop
#include <allegro.h>
...
insert the particle stuff here or in a header or something ;)
...
void main()
{
particle *list = NULL; // make sure it starts by being NULL!!!
BITMAP *buffer;

allegro_init();
install_keyboard();
set_color_depth(16);
set_gfx_mode(GFX_AUTODETECT,640,480,0,0); // should really check the mode was set!
// but I'm lazy.

buffer = create_bitmap(640,480);

while(!key[KEY_ESC]) {
list=clean_particles(list,FALSE); // remove any old particles.
list=create_rain_drop(list);
update_particles(list); // update the particles and move them around!
clear(buffer); // clear the buffer and make it black
draw_particles(buffer,list); // draw them to the buffer
blit(buffer,screen,0,0,0,0,640,480); // draw the buffer to the screen.
}
list=clean_particles(list,TRUE); // free any other particles that are left.
}

well I hope someone can learn from this and hopefully add some interesting effects to their games.

Please email me at white_door@dread.nl if you need any help.

Here a few other nice particle effects that you might want to try

- Clouds
- Hail (small chunks of fast falling ice)
- Leaves (a leaf falling off a tree requires some animation to be drawn, but otherwise it can be easily done)

And here are some other effects that could work but would take a little more work

- Stars (stars that scroll at different speeds with you map would require small additions to the particle structure)
- Explosions/Fire (would require a blur function to mix the particles together, quite easy for 256 color modes but a true color blur takes more work)



news / info / articles / programming / contact / gallery / links RPGDX Valid CSS! Valid XHTML 1.0 Strict