A basic Particles System
By , 19 Apr 2005
Introduction
Particle Systems have long ago intruded into game engines, to become one of the basic features and foundations of a realistic environment. In this article, I will introduce you to the basic idea of the particle systems, and will show you how to create basic effects such as explosions and water fountains. This article does not cover much on the graphics side, and assume that once you have the particle system itself, you're free to display it in whatever way pleases you.
The single particle
A particle system is actually just a group of particles that are grouped together and have the same general behavior. These particles can be anything, from parts of a car when it hits a wall, to drops of water when there's rain.
All particles have a couple of things in common - position, direction, color and age. Each particle keeps its location in the space, the direction where it's going, its own color, and how long it has been alive.
Before we start looking at the particle, we need a class to keep information about the location and direction. Since we're dealing with a 3D world, a simple 3D-vector should be enough. You can find a fully working vector class in the attached files. It's enough for us now to understand that a Vector is a class that encapsulates three float variables, with functions for adding, subtracting and multiplying vectors.
Now let's have a look at our basic particle:
Collapse |
using System;
using System.Drawing;
namespace Particles
{
///<SUMMARY>
/// Summary description for Particle.
///</SUMMARY>
publicclass Particle
{
publicstaticreadonlyint MAX_LIFE = 1000;
// Position of the particle
private Vector m_Position;
// Direction and speed the particle is moving
private Vector m_Velocity;
// Age of the particle
privateint m_Life;
// Color of the particle
private Color m_Color
///<SUMMARY>
/// Default constructor
///</SUMMARY>
public Particle() : this(Vector.Zero, Vector.Zero, Color.Black, 0)
{ }
///<SUMMARY>
/// Constructor
///</SUMMARY>
///<PARAMname="pos">Position
///<SEEcref="Vector"/> of newly created particle</PARAM>
///<PARAMname="vel">Velocity
///<SEEcref="Vector"/> of newly created particle</PARAM>
///<paramname="col">Color of newly created particle</param>
///<PARAMname="life">Starting age of newly created particle</PARAM>
public Particle(Vector pos, Vector vel, Color col, int life)
{
// Create particle at given position
m_Position = pos;
// Set particle's speed to given speed
m_Velocity = vel
// Set particle's color to given color
m_Color = col;
// Make sure starting age is valid
if (life < 0)
m_Life = 0;
else
m_Life = life;
}
///<SUMMARY>
/// Update position, velocity and age of particle
///</SUMMARY>
///<RETURNS>False - if particle is too old and should be killed
/// True - otherwise</RETURNS>
publicbool Update()
{
// Update particle's movement according to environment
m_Velocity = m_Velocity - Environment.getInstance().Gravity
+ Environment.getInstance().Wind;
// Update particle's position according to movement
m_Position = m_Position + m_Velocity;
// Update particle's age
m_Life++;
// If particle if too old
if (m_Life > MAX_LIFE)
// Notify caller to kill particle
returnfalse;
returntrue;
}
#region Accesors
///<SUMMARY>
/// Read Only - Position <SEEcref="Vector"/> of the particle
///</SUMMARY>
public Vector Position
{
get { return m_Position; }
}
///<SUMMARY>
/// Read Only - Velocity <SEEcref="Vector"/> of the particle
///</SUMMARY>
public Vector Velocity
{
get { return m_Velocity; }
}
///<SUMMARY>
/// Read Only - Age of the particle
///</SUMMARY>
publicint Life
{
get { return m_Life; }
}
///<summary>
/// Read Only - Color of the particle
///</summary>
public Color Color
{
get { return m_Color; }
}
#endregion Accessors
}
}
The code is pretty self-explanatory, and I believe that the only part that needs explanation is the following line:
Collapse |
// Update particle's movement according to environment
m_Velocity = m_Velocity - Environment.getInstance().Gravity
+ Environment.getInstance().Wind;
Since our Particle is just a small entity in our world, it is affected by outside forces such as gravity and wind. In the next section, we'll cover the Environment.
The Environment
Our environment includes all external forces that will affect all particles in all the different systems. Such forces include the trivial gravity and wind, but can also include forces such as temperature or any other idea you might have. Since we want only one instance for the environment, I have implemented it as a Singleton:
Collapse |
using System;
namespace Particles
{
///<SUMMARY>
/// Summary description for Enviroment.
///</SUMMARY>
publicclass Environment
{
///<SUMMARY>
/// Single instance of the Environment
///</SUMMARY>
privatestatic Environment m_Instance = new Environment();
// Default Gravity vector in our world
private Vector m_Gravity = Vector.Zero;
// Default Wind vector in our world
private Vector m_Wind = Vector.Zero;
///<SUMMARY>
/// Protected constructor
///</SUMMARY>
protected Environment()
{
}
// Public accessor function to get an instance of the Environment
publicstatic Environment getInstance()
{
return m_Instance;
}
///<SUMMARY>
/// Accessor for the Gravity Vector
///</SUMMARY>
public Vector Gravity
{
get { return m_Gravity; }
set { m_Gravity = value; }
}
///<SUMMARY>
/// Accessor for the Wind Vector
///</SUMMARY>
public Vector Wind
{
get { return m_Wind; }
set { m_Wind = value; }
}
}
}
Nothing here should make you even raise an eye-brow.
The System Abstract Class
Until now we've seen only single particles. As much fun as it might have been for you to watch a single dot move around on the screen, if you even bothered to try it, it's no real buzz. The beauty of particle systems can only be seen when we have large numbers of particles moving together. In this section, we will create the basic class for a system. This class, which is actually an abstract class, will handle the list of particles, and will require each class that inherit from it to implement a function to create new particles, and a function to update those particles. Let's have a look at the code:
Collapse |
using System;
using System.Collections;
using System.Drawing;
namespace Particles
{
///<SUMMARY>
/// Summary description for ParticlesList.
///</SUMMARY>
publicabstractclass ParticlesSystem
{
// Array to keep all the particles of the system
protected ArrayList m_Particles = new ArrayList();
// Should the particles regenerate over time
protectedbool m_Regenerate = false;
// Central position of the system
protected Vector m_Position;
// Default color of a particle
protected Color m_Color;
///<SUMMARY>
/// Generate a single particle in the system.
/// This function is used when particles
/// are first created, and when they are regenerated
///</SUMMARY>
///<RETURNS>New particle</RETURNS>
protectedabstract Particle GenerateParticle();
///<SUMMARY>
/// Update all the particles in the system
///</SUMMARY>
///<RETURNS>False - if there are no more particles in system
/// True - otherwise</RETURNS>
publicabstractbool Update();
///<SUMMARY>
/// Draw all the particles in the system
///</SUMMARY>
///<PARAMname="g">Graphics object to be painted on</PARAM>
publicvirtualvoid Draw(Graphics g)
{
Pen pen;
int intense;
Particle part;
// For each particle in the system
for (int i = 0; i < m_Particles.Count; i++)
{
// Get the current particle
part = this[i];
// Calculate particle intensity
intense = (int)((float)part.Life / PARTICLES_MAX_LIFE);
// Generate pen for the particle
pen = new Pen(Color.FromArgb(intense * m_Color.R ,
intense * m_Color.G,
intense * m_Color.B));
// Draw particle
g.DrawEllipse(pen, part.Position.X, part.Position.Y,
Math.Max(1,4 * part.Life / PARTICLES_MAX_LIFE),
Math.Max(1,4 * part.Life / PARTICLES_MAX_LIFE));
pen.Dispose();
}
}
///<SUMMARY>
/// Indexer allowing access to each particle in the system
///</SUMMARY>
public Particle this[int index]
{
get
{
return (Particle)m_Particles[index];
}
}
///<SUMMARY>
/// Accessor to the number of particles in the system
///</SUMMARY>
publicint CountParticles
{
get { return m_Particles.Count; }
}
///<SUMMARY>
/// Accessor to the maximum life of particles in the system
///</SUMMARY>
publicvirtualint PARTICLES_MAX_LIFE
{
get { return particleMaxLife; }
}