The Allegro Wiki is migrating to github at https://github.com/liballeg/allegro_wiki/wiki

Allegro particles

From Allegro Wiki
Jump to: navigation, search

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.

What is a particle? A particle is an 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. This particle will follow a set of simple rules. Normally the term particle suggest something small.

How is this useful? You can then create a large number of this particles and they will all follow the simple rules to similate a large number of the objects at the same time. Your simple rain drop then becomes a full storm.

Creation

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.

<highlightSyntax language="cpp">

   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

</highlightSyntax>

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:

<highlightSyntax language="cpp">

   struct particle *create_particle(struct particle *parent)
   {
     struct particle *tmp;
     tmp = (struct particle *)malloc(sizeof(struct particle));
     tmp->next = parent;
     return tmp;
   }

</highlightSyntax>

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:

<highlightSyntax language="cpp">

   lastparticle = create_particle(lastparticle); 

</highlightSyntax>

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.

<highlightSyntax language="cpp">

   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;
   }

</highlightSyntax>

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.

<highlightSyntax language="cpp">

   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;
   }

</highlightSyntax>

Cleaning up

Once a particle dies or when we are done with the program, we need to free the memory allocated for the particles we created. Here is a function to help us do it:

<highlightSyntax language="cpp">

   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 :)
   }

</highlightSyntax>

Now that was a complex function, but it is as important as the function to create the new particles!

Drawing

The next step is to draw the particles on the screen!! ;) Now each type of particle must be drawn in a different way

<highlightSyntax language="cpp">

   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;
     }
   }

</highlightSyntax>

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:

<highlightSyntax language="cpp">

   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;
     }
   }

</highlightSyntax>

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


Updating

Finally we need to make sure each particle's movement is updated with its rules in mind.

<highlightSyntax language="cpp">

   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--;
   }

</highlightSyntax>

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.

<highlightSyntax language="cpp">

   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;
     }
   }

</highlightSyntax>

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, it can 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....

<highlightSyntax language="cpp">

   void update_particle(struct particle *t,int scroll_x,int scroll_y)
   {
   ...
     t->x += scroll_x + dx;
     t->y += scroll_y + dy;
   ...
   }

</highlightSyntax>

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.


Main loop

Here is an example main game loop

<highlightSyntax language="cpp">

   #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.
   }

</highlightSyntax>

Conclusion

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

Please email me at whitedoor@gmail.com 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)


Credits

Written by: Nathan Smith (2000)