Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A basic Particles System

0.00/5 (No votes)
19 Apr 2005 2  
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:

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:

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

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:

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.

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.

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here