Click here to Skip to main content
15,868,016 members
Articles / Desktop Programming / Windows Forms
Article

A basic Particles System

Rate me:
Please Sign up or sign in to vote.
4.91/5 (89 votes)
19 Apr 2005CPOL5 min read 331.7K   4.4K   142   69
Introduction to the basic idea of the particle systems, and how to create basic effects such as explosions and water fountains.

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:

C#
using System;
using System.Drawing;

namespace Particles
{
    /// <SUMMARY>
    /// Summary description for Particle.
    /// </SUMMARY>
    public class Particle
    {
        public static readonly int 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
        private int 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>
        /// <PARAM name="pos">Position 
        ///   <SEE cref="Vector" /> of newly created particle</PARAM>
        /// <PARAM name="vel">Velocity 
        ///   <SEE cref="Vector" /> of newly created particle</PARAM>
        /// <param name="col">Color of newly created particle</param>
        /// <PARAM name="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>
        public bool 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
                return false;
            return true;
        }
        #region Accesors

        /// <SUMMARY>
        /// Read Only - Position <SEE cref="Vector" /> of the particle
        /// </SUMMARY>
        public Vector Position
        {
            get { return m_Position; }
        }
        /// <SUMMARY>
        /// Read Only - Velocity <SEE cref="Vector" /> of the particle
        /// </SUMMARY>
        public Vector Velocity
        {
            get { return m_Velocity; }
        }
        /// <SUMMARY>
        /// Read Only - Age of the particle
        /// </SUMMARY>
        public int 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:

C#
// 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:

C#
using System;

namespace Particles
{
    /// <SUMMARY>
    /// Summary description for Enviroment.
    /// </SUMMARY>
    public class Environment
    {
        /// <SUMMARY>
        /// Single instance of the Environment
        /// </SUMMARY>
        private static 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
        public static 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:

C#
using System;
using System.Collections;
using System.Drawing;

namespace Particles
{
    /// <SUMMARY>
    /// Summary description for ParticlesList.
    /// </SUMMARY>
    public abstract class ParticlesSystem
    {
        // Array to keep all the particles of the system
        protected ArrayList m_Particles = new ArrayList();
        // Should the particles regenerate over time
        protected bool 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>
        protected abstract Particle GenerateParticle();

        /// <SUMMARY>
        /// Update all the particles in the system
        /// </SUMMARY>
        /// <RETURNS>False - if there are no more particles in system
        /// True - otherwise</RETURNS>
        public abstract bool Update();


        /// <SUMMARY>
        /// Draw all the particles in the system
        /// </SUMMARY>
        /// <PARAM name="g">Graphics object to be painted on</PARAM>
        public virtual void 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>
        public int CountParticles
        {
            get { return m_Particles.Count; }
        }

        /// <SUMMARY>
        /// Accessor to the maximum life of particles in the system
        /// </SUMMARY>
        public virtual int PARTICLES_MAX_LIFE
        {
            get { return particleMaxLife; }
        }
    }
}

The three constructors are easy to understand. The GenerateParticle() function will be used when a new particle is created, whether it's a completely new particle, or when a particle dies and we wish to replace it with a new one. The Update() will be used to update the particles in the system. Update() will need to decide if and when to create new particles. And last, Draw() will be used to display the particle system on a given Graphics object.

2 basic particle systems

Now that we've seen the basic interface that we need to implement, we need to start implementing particle systems. Two of the more basic systems are an explosion and a fountain. I'll demonstrate them here.

Explosion

In an explosion, particles just fly everywhere. This is quite easy to implement - we just set all the particles to start at the center of the system, and move to a random direction, with a random speed. Gravity will take care of everything else.

C#
using System;

namespace Particles
{
    /// <SUMMARY>
    /// Summary description for Explosion.
    /// </SUMMARY>
    public class PSExplosion : ParticlesSystem
    {

        private static readonly int DEFAULT_NUM_PARTICLES = 150;

        // Random numbers generator
        private Random m_rand = new Random();

        /// <SUMMARY>
        /// Default constructor
        /// </SUMMARY>
        public PSExplosion() : this(Vector.Zero, Color.Black)
        { }

        /// <SUMMARY>
        /// Constructor
        /// </SUMMARY>
        /// <PARAM name="pos">Starting position of system</PARAM>
        public PSExplosion(Vector pos) : this(pos, Color.Black)
        { }

        /// <SUMMARY>
        /// Constructor
        /// </SUMMARY>
        /// <PARAM name="pos">Starting position of system</PARAM>
        /// <PARAM name="col">Color of the particles in the system</PARAM>
        public PSExplosion(Vector pos, Color col)
        {
            // Set system's position at given position
            m_Position = pos;
            // Set system color to given color
            m_Color = col;
            // Create all the particles in the system
            for (int i = 0; i < DEFAULT_NUM_PARTICLES; i++)
            {
                // Create particle, and add it to the list of particles
                m_Particles.Add(GenerateParticle());
            }
        }


        /// <SUMMARY>
        /// Update all the particles in the system
        /// </SUMMARY>
        /// <RETURNS>False - if there are no more particles in system
        /// True - otherwise</RETURNS>
        public override bool Update()
        {
            Particle part;
            // Get number of particles in the system
            int count = m_Particles.Count;

            // For each particle
            for (int i=0; i < count; i++)
            {
                // Get particle from list
                part = (Particle)m_Particles[i];
                // Update particle and check age
                if ((!part.Update()) || (part.Life > 150))
                {
                    // Remove old particles
                    m_Particles.RemoveAt(i);
                    // Update counter and index
                    i--;
                    count = m_Particles.Count;
                }
            }
            // If there are no more particles in the system
            if (m_Particles.Count <= 0)
                return false;
            return true;
        }

        /// <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>
        protected override Particle GenerateParticle()
        {
            // Generate random direction & speed for new particle
            float rndX = 2 * ((float)m_rand.NextDouble() - 0.5f);
            float rndY = 2 * ((float)m_rand.NextDouble() - 0.5f);
            float rndZ = 2 * ((float)m_rand.NextDouble() - 0.5f);

            // Create new particle at system's starting position
            Particle part = new Particle(m_Position,
                // With generated direction and speed
                new Vector(rndX, rndY, rndZ),
                // And a random starting life
                m_rand.Next(50));

            // Return newly created particle
            return part;
        }
    }
}

In this example, we've created all the particles when the system was created. We've placed them all at exactly the starting point of the system, although for a more realistic look, we might have added a little bit of randomness there too. Each new particle is given a random age - this way the particles don’t die all at the same time. We've also decided to kill particles that are older than 150. We could have chosen another criteria, such as to kill particles only when they leave the display view, or they bumped into something.

Fountain

The fountain example is given here due to two reasons. First, the fountain regenerates particles that die, in order to continue "fountaining" or whatever else fountains do. Secondly, not all the particles are created at once - we first create a few particles, and as time goes on, we add more and more particles to the system.

C#
using System;

namespace Particles
{
    /// <SUMMARY>
    /// Summary description for Firework.
    /// </SUMMARY>
    public class PSFountain : ParticlesSystem
    {
        private static readonly int DEFAULT_NUM_PARTICLES = 250;

        // Random numbers generator
        private Random m_rand = new Random();

        /// <SUMMARY>
        /// Default constructor
        /// </SUMMARY>
        public PSFountain() : this(Vector.Zero, Color.Black)
        { }

        /// <SUMMARY>
        /// Constructor
        /// </SUMMARY>
        /// <PARAM name="pos">Starting position of system</PARAM>
        public PSFountain(Vector pos) : this(pos, Color.Black)
        { }

        /// <SUMMARY>
        /// Constructor
        /// </SUMMARY>
        /// <PARAM name="pos">Starting position of system</PARAM>
        /// <PARAM name="col">Color of the particles in the system</PARAM>
        public PSFountain(Vector pos, Color col)
        {
            // Mark that this system regenerates particles
            m_Regenerate = true;
            // Set system's position at given position
            m_Position = pos;
            // Set system color to given color
            m_Color = col;
            // Create ONLY 5 particles
            for (int i = 0; i < 5; i++)
            {
                // Create particle, and add it to the list of particles
                m_Particles.Add(GenerateParticle());
            }
        }

        /// <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>
        protected override Particle GenerateParticle()
        {
            // Generate random direction & speed for new particle
            // In a fountain, particles move almost straight up
            float rndX = 0.5f * ((float)m_rand.NextDouble() - 0.4f);
            float rndY = -1 - 1 * (float)m_rand.NextDouble();
            float rndZ = 2 * ((float)m_rand.NextDouble() - 0.4f);

            // Create new particle at system's starting position
            Particle part = new Particle(m_Position,
                // With generated direction and speed
                new Vector(rndX, rndY, rndZ),
                // And a random starting life
                m_rand.Next(50));

            // Return newly created particle
            return part;
        }

        /// <SUMMARY>
        /// Update all the particles in the system
        /// </SUMMARY>
        /// <RETURNS>False - if there are no more particles in system
        /// True - otherwise</RETURNS>
        public override bool Update()
        {
            Particle part; 
            // Get number of particles in the system
            int count = m_Particles.Count;

            // For each particle
            for (int i=0; i < count; i++)
            {
                // Get particle from list
                part = (Particle)m_Particles[i];
                // Update particle and check age
                if ((!part.Update()) || (part.Life > 150))
                {
                    // Remove old particles
                    m_Particles.RemoveAt(i);
                    // Update counter and index
                    i--;
                    count = m_Particles.Count;
                }
            }
            // If there aren't enough particles
            if  (m_Particles.Count < DEFAULT_NUM_PARTICLES)
                // Add another particles
                m_Particles.Add(GenerateParticle());

            // Always return true, since system is regenerating
            return true;
        }
    }
}

As you can see, the changes from the Explosion class are quite minor. Here we've created only a few particles when the system is created, and add a new particle every time the system is updated. We've also changed a bit the math for the movement of the particles - now they move almost straight up, and just a bit to the sides.

More systems

Creating more systems is quite simple. Examples of other systems include rain and snow, tornados, water flushing, falling leaves, smoke and more. The options are endless. In the attached demo, I've included another system - a firework.

Conclusion

I've included in the attached files a simple example of the described systems. The display I've used is very simple - single ellipse for each particle. But if you take into account each particle's age, you can come up with amazing effects just by changing the particles' size and transparency.

Creating new systems can be done in just minutes using the described model, and you are more than welcome to send me your own systems to add to this article.

History

  • 3rd April 2005 - Demo project updated (Thanks to Mark Treadwell).
  • 9th April 2005 - Fixed a bug in the Vector class. Added color to the particles. Included a Draw() function in the ParticlesSystem abstract class. Added firework system to attached project.
  • 13rd April 2005 - Performance issue fixed (Thanks to John Fisher).
  • 19th April 2005 - Constructors improvement suggested by Junai.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader
Israel Israel
I currently work as the development manager in a company called Tzunami Inc. that develops a content migration solution for Microsoft SharePoint . Our product, called Tzunami Deployer is developed using C#.

Comments and Discussions

 
GeneralMy vote of 5 Pin
GG_BYTE31-Mar-22 2:43
GG_BYTE31-Mar-22 2:43 
QuestionVery Nice : My vote of 5! Pin
Yang Kok Wah30-May-14 1:09
Yang Kok Wah30-May-14 1:09 
QuestionThank You! Pin
MacYaken5-Nov-12 4:11
MacYaken5-Nov-12 4:11 
QuestionQuestion about Formula in Fireworks.cs Pin
NikoWah31-Oct-12 6:02
NikoWah31-Oct-12 6:02 
QuestionAre these correct? Pin
Rob Achmann25-Jan-11 12:39
Rob Achmann25-Jan-11 12:39 
GeneralMy vote of 5 Pin
Rob Achmann25-Jan-11 12:33
Rob Achmann25-Jan-11 12:33 
GeneralVery cool Pin
Marcelo Ricardo de Oliveira5-Dec-09 3:45
mvaMarcelo Ricardo de Oliveira5-Dec-09 3:45 
GeneralPSMyFountain Code Pin
kirant40012-Jan-09 1:00
kirant40012-Jan-09 1:00 
GeneralControl wind direction with mouse pointer Pin
friederbluemle24-Nov-08 0:55
friederbluemle24-Nov-08 0:55 
Generalc++ free codes Pin
elooo6-Jul-08 1:59
elooo6-Jul-08 1:59 
QuestionCan this port to C# Direct3D? Pin
tohjs15-Mar-07 4:13
tohjs15-Mar-07 4:13 
AnswerRe: Can this port to C# Direct3D? Pin
#teve1-Jul-08 5:03
#teve1-Jul-08 5:03 
GeneralParticle fade out and color Pin
Arvaris115-Jun-05 9:18
Arvaris115-Jun-05 9:18 
GeneralSmoothing mode Pin
David Nissimoff29-Apr-05 14:22
David Nissimoff29-Apr-05 14:22 
GeneralVery nice and fast Pin
Niklas Ulvinge21-Apr-05 23:54
Niklas Ulvinge21-Apr-05 23:54 
GeneralRe: Very nice and fast Pin
tom_dx22-Apr-05 2:00
tom_dx22-Apr-05 2:00 
GeneralRe: Very nice and fast Pin
Niklas Ulvinge28-Apr-05 3:30
Niklas Ulvinge28-Apr-05 3:30 
GeneralRe: Very nice and fast Pin
toxcct20-Jun-05 5:47
toxcct20-Jun-05 5:47 
GeneralRe: Very nice and fast Pin
Niklas Ulvinge20-Jun-05 7:41
Niklas Ulvinge20-Jun-05 7:41 
Questionproblem in Vector class?? Pin
TRW20-Apr-05 9:22
TRW20-Apr-05 9:22 
AnswerRe: problem in Vector class?? Pin
igorss20-Apr-05 22:38
igorss20-Apr-05 22:38 
AnswerRe: problem in Vector class?? Pin
Indiana Jones21-Apr-05 0:15
Indiana Jones21-Apr-05 0:15 
GeneralRe: problem in Vector class?? Pin
Itay Sagui21-Apr-05 1:12
Itay Sagui21-Apr-05 1:12 
GeneralRe: problem in Vector class?? Pin
Indiana Jones21-Apr-05 1:44
Indiana Jones21-Apr-05 1:44 
GeneralRe: problem in Vector class?? Pin
TRW21-Apr-05 5:09
TRW21-Apr-05 5:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.