During my sophomore year at DigiPen, I worked on a game called Project: Gemini as a graphics and gameplay programmer. While working on the project one of my jobs ended up being rendering and creating a particle system that could handle the basics of spawning particles and how particles moving on screen. Something not to robust but just enough so designers or artists could rapidly make particles.

The Particle System Component

Looking back at this component, it was shocking to see how much was actually in it and how there was so much more room to grow. The particle system component in Project: Gemini was quite robust and had many things in it. This blog here is to show a glimpse of how complex a particle system go.

A quick intro to particle systems and terminology, particle systems are generally in its simplest form small 2D/3D images rendered multiple times. Now what are some use cases: rain, fire, explosiony effects. A particle emitter a is holder for all the data a particle has such as where is it spawned from , what it looks like, and maybe how fast a particle is going.

Lets deep dive into the Project: Gemini’s Particle Emitter Component. This is just to show how much there really was in the particle emitter component. Scroll to the end of the code dump to see some implementation parts of the system.

  enum class ParticleEmitterSpawnShape
  {
    POINT,  // particles spawning from a point
    CIRCLE, // particles spawning within a circle
    RECTANGLE // particles spawning in a rectangle shape
  };

  enum class ParticleEmitterMotion
  {
    LINEAR, // how particles are moving
    CIRCULAR // this is more like going outward, I don't know why I named it circular at the time
  };

  enum class ColorEffects
  {
    NOEFFECT, // unnecessary but color doesn't change throughout  lifetime
    CHANGE //color changes form start color to end color
  };
  struct ParticleEmitterComponent
  {
    std::vector<Particle> particleList{};                  // number of particles in the list
    SpriteSheetHandle     spriteSheet          = nullptr;  // data for the animated sprites
    Animation*            currentAnimation     = nullptr;  // animation data for the particle
    String                currentAnimationName = "";       // name of animation
    Transform             transform            = {};       // position data for a particle
    MaterialHandle        material             = nullptr;  // texture for a particle
    Vec2                  direction;                       // in what direction is the particle traveling
    Vec2                  random_influence;                // random influence is a fun factor of how particles will behave
    Vec2                  emitterParticleSize;             // start size of particle
    Vec2                  emitterParticleEndSize;          // end size of particle
    Vec2                  acceleration;                    // particle acceleration
    std::uint32_t         maxParticles;                    // max number of particles total
    float                 animationScale   = 1.0f;         // how fast is the animation going
    float                 currentParticles = 0.0f;         // current number of paritcles
    float                 spawnSpeed       = 1.0f;         // how fast particles spawn upfront
    float                 lifeTime;                        // start lifeitme of each particle
    float                 maxLifeTime;                     // end lifetime of each particle
    bool                  isPlaying;                       // are the current set of particles playing
    bool                  isAnimated;                      // are these particles animated

    ParticleEmitterSpawnShape spawnShape;       // how do particles spawn
    ParticleEmitterMotion     motion;           // how do particles travel
    ColorEffects              effect;           // what do particles look like
    Color                     color = Color();  // start color of a particle
    Color                     endColor;         // end color of particle

    union
    {
      struct
      {
        float innerRadius;  // used for spawn shape, spawn particles in a cirle inner radius
        float radius;       // outer radius
      };
      struct
      {
        float width;   // used for rectangle spawning
        float height;  // height of rectangle
      };
    };
  };

Yup, that’s a lot.

Just to keep it simple (yup after all that) particles need to know where they spawn and where they go, so we will be looking at just those two things for now. If you want any information on the rest please send me an email!

Where Do Particles Come From?

I thought well particles can spawn at a point or they can spawn in some region. Well I decided to encompass this idea within a struct.

 enum class ParticleEmitterSpawnShape
  {
    POINT,  // particles spawning from a point
    CIRCLE, // particles spawning within a circle
    RECTANGLE // particles spawning in a rectangle shape
  };

Looking at this you can easily see my thought process. I will based on the SpawnShape type, decide where my particles will spawn relative to the origin of the particle emitter. Very simple and easy to understand and also very expandable. Any of my teammates could have jumped in and customized their own particle spawn shape. Here is the implementation of this idea.

/******************SPAWN_SHAPE*******************************/
switch (emitter.spawnShape)
{
  case ParticleEmitterSpawnShape::POINT:
  {
    particle.position = pos;  // position is emitters position
    break;
  }
  case ParticleEmitterSpawnShape::CIRCLE:
  {
    float degree = randomRange(0, 2.0f * (float)M_PI);
    float distance = randomRange(emitter.innerRadius, emitter.radius);  // particles within the inner and outer radius
  
    particle.position = pos; // set particle position
    particle.position.x += sinf(degree) * distance; // set x pos of particle
    particle.position.y += cosf(degree) * distance; // set  y pos of particle
    break;
  }
  case ParticleEmitterSpawnShape::RECTANGLE:
  {
    const float half_width = emitter.width * 0.5f;
    const float half_height = emitter.height * 0.5f;
  
    particle.position = pos + Vec3(randomRange(-half_width, half_width), randomRange(-half_height, half_height), 0.0f);
    break;
  }
}

Where Do Particles Go?

If you see where this is going, I thought well particles can either go in a straight motion or any motion. Who really tells a particle what to do ( oh wait, we do)? Well, understandably I decided to go make a struct for this route as well.

  enum class ParticleEmitterMotion
  {
    LINEAR, // how particles are moving
    CIRCULAR // this is more like going outward, I don't know why I named it circular at the time
  };

You see particle systems are not that bad! Although there are more complicated and probably better ways of doing this, I saw this as a viable option. Here is the implementation of how particles moved.

/******************MOTION*******************************/
switch (emitter.motion)
{
  case ParticleEmitterMotion::LINEAR:  // the direction in which the particles should go in a linear fashion
  {
    //the randomness of the speed in which the particle is going
    particle.velocity = emitter.direction * Vec2(randomRange(0.0f, 1.0f) * emitter.random_influence.x + 1.0f , randomRange(0.0f, 1.0f) * emitter.random_influence.y + 1.0f );  
    break;
  }
  
  case ParticleEmitterMotion::CIRCULAR:  // particles go out in a circular motion
  {
    const Vec2  dir = particle.position - pos;   // directions
    const float angle = std::atan2(dir.y, dir.x) ;  // calculation of the angle
  
    if (glm::length2(dir) >= PHYSICS_EPSILON)  // checing for division by 0
    {
      particle.velocity.x = emitter.direction.x * cosf(angle);
      particle.velocity.y = emitter.direction.y * sinf(angle);
    }
    else
    {
      particle.velocity.x = emitter.direction.x * cosf(randomRange(0, 2.0f * (float)M_PI));
      particle.velocity.y = emitter.direction.y * sinf(randomRange(0, 2.0f * (float)M_PI));
    }
  
    break;
  }
}

To show of some cool particle effects, here is a particle effect I made with the particle system to show off dashing to players.

Dashing Particles

Conclusion

The particle system was a key system that we had made for designers to help signify different things to the player and also for player feedback whenever they jump, dash, and destroy a drone. All of the information is conveyed via particles. The usefulness of a simple yet robust particle system can really “liven” up the game because a lot of problems we had early on in is that our game looked like a picture. “Particles everywhere!” as one of the teachers kept shouting out us when we came to them with this problem and to our surprise that is what really gave our the game the extra “umph!” that wee needed.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.