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 […]
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.