Particles

Particles are tiny sprites that are rendered with about 50-percent alpha transparency so that they seem to glow. The key to creating a particle system—that is, an emitter or other special effect—is to start with a good source particle image. Figure 4.7 shows an enlarged view of a 16 × 16 particle sprite. Note the amount of alpha transparency in the image—only the central white portion is fully opaque, while the rest will blend with whatever background the particle is rendered over.

Figure 4.7. Source particle image.


Sprite-based particles differ significantly from shader-based particles rendered by the 3D hardware. Three-dimensional particles can emit light (emissive) or reflect light (reflective) and can be used to simulate real smoke and fog. Sprite-based particles can be used to generate smoke trails behind missiles and spaceships, among other things.

A so-called particle system is a managed list of particles that are rendered in creative ways. That list takes the form of an std::vector—something we have demonstrated before, but have not yet fully utilized in the game engine. (That is the subject of Chapter 7, “Entities.”). An std::vector will work slightly faster than an std::list when your list does not need to change very often. Our particle emitter will create particles but not remove any (until the object is destroyed, that is). An std::list would be preferred if you needed to add and remove items regularly, but it’s not quite as fast as an std::vector when it comes to sequential iteration.

To make working with particles more reasonable, we’ll code up the most obvious functionality into a class. Following is the definition for the ParticleEmitter class. This class uses an std::vector filled with Sprite objects to represent the entities in the emitter. The class is otherwise completely self contained and can handle most types of particle systems that I have seen over the years. Basically, a great particle system works in such a way that the player shouldn’t notice that it’s a particle at all. When a spaceship is cruising through space, it can emit a flame and smoke with the use of two particle emitters, for example. This code belongs in the ParticleEmitter.h file.

#include "Advanced2D.h"
#pragma once
namespace Advanced2D {
     class ParticleEmitter
     {
     private:
         typedef std::vector<Sprite*>::iterator iter;
         std::vector<Sprite*> particles;
         Texture *image;
         Vector3 position;
         double direction;
         double length;
         int max;
         int alphaMin,alphaMax;
         int minR,minG,minB,maxR,maxG,maxB;
         int spread;
         double velocity;
         double scale;
     public:
         void setPosition(double x, double y) { position.Set(x,y,0); }
         void setPosition(Vector3 vec) { position = vec; }
         Vector3 getPosition() { return position; }
         void setDirection(double angle) { direction = angle; }
         double getDirection() { return direction; }
         void setMax(int num) { max = num; }
         void setAlphaRange(int min,int max);
         void setColorRange(int r1,int g1,int b1,int r2,int g2,int b2);
         void setSpread(int value) { spread = value; }
         void setLength(double value) { length = value; }
         void setVelocity(double value) { velocity = value; }
         void setScale(double value) { scale = value; }

         ParticleEmitter();
         virtual ~ParticleEmitter();
         bool loadImage(std::string imageFile);
         void draw();
         void update();
         void add();
     }; //class
}; //namespace

Following is the implementation file for the ParticleEmitter class. I’ll explain how it works at the end of the code listing.

#include "Advanced2D.h"
namespace Advanced2D {
     ParticleEmitter::ParticleEmitter()
     {
       //initialize particles to defaults
       image = NULL;
       max = 100;
       length = 100;
       direction = 0;
       alphaMin = 254; alphaMax = 255;
       minR = 0; maxR = 255;
       minG = 0; maxG = 255;
       minB = 0; maxB = 255;
       spread = 10;
       velocity = 1.0f;
       scale = 1.0f;
     }

     bool ParticleEmitter::loadImage(std::string imageFile)
     {
         image = new Texture();
         return image->Load(imageFile);
     }


     ParticleEmitter::~ParticleEmitter()
     {
         delete image;

         //destroy particles
         for (iter i = particles.begin(); i != particles.end(); ++i)
         {
              delete *i;
         }
         particles.clear();
     }

     void ParticleEmitter::add()
     {
         static double PI_DIV_180 = 3.1415926535 / 180.0f;
         double vx,vy;

         //create a new particle
         Sprite *p = new Sprite();
         p->setImage(image);
         p->setPosition(position.getX(), position.getY());
         //add some randomness to the spread
         double variation = (rand() % spread - spread/2) / 100.0f;

         //set linear velocity
         double dir = direction - 90.0;
         vx = cos( dir * PI_DIV_180) + variation;
         vy = sin( dir * PI_DIV_180) + variation;
         p->setVelocity(vx * velocity,vy * velocity);

         //set random color based on ranges
         int r = rand()%(maxR-minR)+minR;
         int g = rand()%(maxG-minG)+minG;
         int b = rand()%(maxB-minB)+minB;
         int a = rand()%(alphaMax-alphaMin)+alphaMin;
         p->setColor(D3DCOLOR_RGBA(r,g,b,a));

         //set the scale
         p->setScale( scale );

         //add particle to the emitter
         particles.push_back(p);

     }

     void ParticleEmitter::draw()
     {
         //draw particles
         for (iter i = particles.begin(); i != particles.end(); ++i)
         {
             (*i)->draw();
         }
     }

     void ParticleEmitter::update()
     {
         static Timer timer;

         //do we need to add a new particle?
         if ((int)particles.size() < max)
         {
             //trivial but necessary slowdown
             if (timer.stopwatch(1)) add();
         }


         for (iter i = particles.begin(); i != particles.end(); ++i)
         {
              //update particle's position
              (*i)->move();

              //is particle beyond the emitter's range?
              if ( (*i)->getPosition().Distance(this->position) > length)
              {
                   //reset particle to the origin
                   (*i)->setX(position.getX());
                   (*i)->setY(position.getY());
              }
          }
      }

      void ParticleEmitter::setAlphaRange(int min,int max)
      {
          alphaMin=min;
          alphaMax=max;
      }

void ParticleEmitter::setColorRange(int r1,int g1,int b1,int r2,int g2,int b2)
      {
          minR = r1; maxR = r2;
          minG = g1; maxG = g2;
          minB = b1; maxB = b2;
      }
}

Using the ParticleEmitter class is very easy; you just have to supply the source image. That image can be any reasonably nice-looking circle on a bitmap, or perhaps a simple square image if you want to produce a blocky effect. I have created a circle on a 16 × 16 bitmap with several shades of alpha built into the image. Combined with the color and alpha effects we’ll apply when drawing the image, this will produce the particles in our emitter. However, you can produce quite different particles using a different source image—something to keep in mind!

Here is how you can create a simple emitter. This example code creates a new particle emitter using the particle16.tga image; sets it at screen location 400,300; sets the angle to 45 degrees; sets a maximum of 1,000 particles; sets an alpha range of 0 to 100 (which is faint); sets the random spread from the given angle to 30 pixels; and sets the range to 250 pixels.

ParticleEmitter *p new ParticleEmitter();
p->loadImage("particle16.tga");
p->setPosition(400,300);
p->setDirection(45);
p->setMax(1000);
p->setAlphaRange(0,100);
p->setSpread(30);
p->setLength(250);

After creating the emitter, you need to give it a chance to update its particles and draw itself. The ParticleEmitter::update() method should be called from your game_update() function, while ParticleEmitter::draw() should be called from your game_render2d() function.

Following is an example program called ParticleDemo that demonstrates just a few of the possibilities! This example creates two normal emitters, a rotation pattern, and then two emitters together, rotating in a circle, generating a smoke-like effect. Figure 4.8 shows the output with a white background. To really appreciate the alpha blending taking place here, you must see it with a dark background, as shown in Figure 4.9.

Figure 4.8. Particle demonstration with a white background.


Figure 4.9. Particle demonstration with a black background.


There’s a lot of code in the ParticleTest program, which is listed below. But most of this is setup code to configure the many particle emitters demonstrated in the program. Once the emitters are configured, the rest of the program listing is fairly short, with just calls to update and draw each emitter.

#include "..EngineAdvanced2D.h"
using namespace Advanced2D;

ParticleEmitter *pa;
ParticleEmitter *pb;
ParticleEmitter *pc;
ParticleEmitter *pd;
ParticleEmitter *pe;

bool game_preload()
{
     g_engine->setAppTitle("PARTICLE DEMO");
     g_engine->setFullscreen(false);
     g_engine->setScreenWidth(1024);
     g_engine->setScreenHeight(768);
     g_engine->setColorDepth(32);
     return 1;
}

bool game_init(HWND)
{
     g_engine->setMaximizeProcessor(true);

     pa = new ParticleEmitter();
     pa->loadImage("particle16.tga");
     pa->setPosition(100,300);
     pa->setDirection(0);
     pa->setMax(500);
     pa->setAlphaRange(100,255);
     pa->setSpread(30);
     pa->setVelocity(2.0);
     pa->setLength(250);

     pb = new ParticleEmitter();
     pb->loadImage("particle16.tga");
     pb->setPosition(300,100);
     pb->setDirection(180);
     pb->setScale(0.6);
     pb->setMax(500);
     pb->setAlphaRange(0,100);
     pb->setColorRange(200,0,0,255,10,10);
     pb->setVelocity(2.0);
     pb->setSpread(40);
     pb->setLength(200);

     pc = new ParticleEmitter();
     pc->loadImage("particle16.tga");
     pc->setPosition(250,525);
     pc->setDirection(0);
     pc->setScale(0.5);
     pc->setMax(2000);
     pc->setAlphaRange(100,150);
     pc->setColorRange(0,0,200,10,10,255);
     pc->setVelocity(0.2);
     pc->setSpread(5);
     pc->setLength(180);
     pd = new ParticleEmitter();
     pd->loadImage("particle16.tga");
     pd->setPosition(750,650);
     pd->setScale(0.75);
     pd->setMax(10);
     pd->setAlphaRange(50,100);
     pd->setColorRange(210,50,0,255,255,1);
     pd->setVelocity(2.0);
     pd->setDirection(0);
     pd->setSpread(40);
     pd->setLength(100);

     pe = new ParticleEmitter();
     pe->loadImage("particle16.tga");
     pe->setPosition(730,575);
     pe->setScale(4.0f);
     pe->setMax(1000);
     pe->setAlphaRange(1,20);
     pe->setColorRange(250,250,250,255,255,255);
     pe->setVelocity(2.0);
     pe->setDirection(0);
     pe->setSpread(80);
     pe->setLength(800);

     return true;
}

void game_update()
{
     //move particles
     pa->update();
     pb->update();

     //update the circular emitter
     float dir = pc->getDirection() + 0.2f;
     pc->setDirection(dir);
     pc->update();

     //update the rotating emitter
     static double unit = 3.1415926535 / 36000.0;
     static double angle = 0.0;
     static double radius = 150.0;
     angle += unit;
     if (angle > 360) angle = 360 - angle;
     float x = 750 + cos(angle) * radius;
     float y = 500 + sin(angle) * radius;
     pd->setPosition(x,y);
     pd->update();

     //update smoke emitter
     pe->setPosition(x,y);
     pe->update();

     //exit when escape key is pressed
     if (KEY_DOWN(VK_ESCAPE)) g_engine->Close();
}

void game_end()
{
     delete pa;
     delete pb;
     delete pc;
     delete pd;
     delete pe;
}

void game_render3d()
{
     g_engine->ClearScene(D3DCOLOR_XRGB(0,0,0));
}

void game_render2d()
{
     pa->draw();
     pb->draw();
     pc->draw();
     pd->draw();
     pe->draw();
}

There is so much more potential for particles than what you’ve seen here! In Chapter 10, “Math,” you will learn to calculate the angle between two sprites, which will make it possible to emit a particle stream from the rear direction of a spaceship or rocket and have that stream point in the right direction. You could also create a slow particle emitter to simulate smoke and have it appear as if a ship, aircraft, or other type of game object is damaged!

Now it feels as if we’ve been dealing with sprites for a long time now, so let’s take a break! The next two chapters cover device input and game audio, which will be a nice change of pace.

 

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset