4 min read

Particle Systems - Part 3

The ParticleSystem Class

We now want to repeat what we did when we made our Particle class to create a ParticleSystem class. Like before, right-click on your project in the Solution Explorer, and choose Add > New Item from the popup menu. The Add New Item dialog will appear. Select the Class template, and change the name (near the bottom) to ParticleSystem.cs. Click on the Add button when you have done this. The new class file will open up in the main editor window.

The first change we will want to make is to make the class public, so near the top of the file, find the line that says:

class ParticleEngine

and change it to say:

public class ParticleEngine

The ParticleEngine’s Properties and Instance Variables

We now need to add some properties and instance variables to the class to keep track of important information about our particle. Add the following as instance variables:

private Random random;
public Vector2 EmitterLocation { get; set; }
private List<Particle> particles;
private List<Texture2D> textures;

The first variable is a random number generator that we will use when we are generating our particles. The EmitterLocation allows the user to change the location that the particles are originating at. particles is a variable that contains all of the particles that are currently active in the particle system, and textures contains all of the textures available for use in the particle system.

The Constructor

Once again, our constructor only needs to prepare the instance variables for use. In the code below, a couple of the parameters are assigned by the user, and a couple are created with default values. We want the user to tell us what textures we can use, as well as the default location of the emitter, but the list of particles is for the particle system to manage, and the user should not have to worry about them. Also, random is given a default new random number generator. Add the following code to your ParticleEngine class:

public ParticleEngine(List<Texture2D> textures, Vector2 location)
{
    EmitterLocation = location;
    this.textures = textures;
    this.particles = new List<Particle>();
    random = new Random();
}

Generating Particles

One of the important things this class will need to be able to do is to create particles with parameters that make the engine behave as you want it. We are going to create a default method for generating particles below. However, there is more than one way to do this. When your particle system is complete and working, I would suggest immediately coming back to this method and changing things around to see what you can get. You can really do a lot with a particle system, simply by changing the way it generates particles. Add the following code to your ParticleEngine class:

private Particle GenerateNewParticle()
{
    Texture2D texture = textures[random.Next(textures.Count)];
    Vector2 position = EmitterLocation;
    Vector2 velocity = new Vector2(
            1f * (float)(random.NextDouble() * 2 - 1),
            1f * (float)(random.NextDouble() * 2 - 1));
    float angle = 0;
    float angularVelocity = 0.1f * (float)(random.NextDouble() * 2 - 1);
    Color color = new Color(
            (float)random.NextDouble(),
            (float)random.NextDouble(),
            (float)random.NextDouble());
    float size = (float)random.NextDouble();
    int ttl = 20 + random.Next(40);

    return new Particle(texture, position, velocity, angle, angularVelocity, color, size, ttl);
}

+++ The Update() Method

The Update() method here will do a few things: add new particles as needed, allow each of the particles to update themselves, and remove dead particles. This is done with the following code:

public void Update()
{
    int total = 10;

    for (int i = 0; i < total; i++)
    {
        particles.Add(GenerateNewParticle());
    }

    for (int particle = 0; particle < particles.Count; particle++)
    {
        particles[particle].Update();
        if (particles[particle].TTL <= 0)
        {
            particles.RemoveAt(particle);
            particle--;
        }
    }
}

In this code, we start off by adding 10 new particles each time. We then go through the list of particles and tell them to update themselves. If any of them have “died” (their time to live has reached 0), the particle is removed.

The Draw() Method

The Draw() method is a very simple method of this class since it is mostly just going to tell the particles to draw themselves. Below is the code for the Draw() method:

public void Draw(SpriteBatch spriteBatch)
{
    spriteBatch.Begin();
    for (int index = 0; index < particles.Count; index++)
    {
        particles[index].Draw(spriteBatch);
    }
    spriteBatch.End();
}

using Directives

Also, don’t forget to add the following two using directives so that your code will compile correctly to the top of your file:

using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

This should complete the entire ParticleEngine class, and we are now ready to use our particle system in our game. We will do this in the final section of this tutorial. Below is the entire code for this class, all assembled:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ParticleEngine2D
{
    public class ParticleEngine
    {
        private Random random;
        public Vector2 EmitterLocation { get; set; }
        private List<Particle> particles;
        private List<Texture2D> textures;

        public ParticleEngine(List<Texture2D> textures, Vector2 location)
        {
            EmitterLocation = location;
            this.textures = textures;
            this.particles = new List<Particle>();
            random = new Random();
        }

        public void Update()
        {
            int total = 10;

            for (int i = 0; i < total; i++)
            {
                particles.Add(GenerateNewParticle());
            }

            for (int particle = 0; particle < particles.Count; particle++)
            {
                particles[particle].Update();
                if (particles[particle].TTL <= 0)
                {
                    particles.RemoveAt(particle);
                    particle--;
                }
            }
        }

        private Particle GenerateNewParticle()
        {
            Texture2D texture = textures[random.Next(textures.Count)];
            Vector2 position = EmitterLocation;
            Vector2 velocity = new Vector2(
                                    1f * (float)(random.NextDouble() * 2 - 1),
                                    1f * (float)(random.NextDouble() * 2 - 1));
            float angle = 0;
            float angularVelocity = 0.1f * (float)(random.NextDouble() * 2 - 1);
            Color color = new Color(
                        (float)random.NextDouble(),
                        (float)random.NextDouble(),
                        (float)random.NextDouble());
            float size = (float)random.NextDouble();
            int ttl = 20 + random.Next(40);

            return new Particle(texture, position, velocity, angle, angularVelocity, color, size, ttl);
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Begin();
            for (int index = 0; index < particles.Count; index++)
            {
                particles[index].Draw(spriteBatch);
            }
            spriteBatch.End();
        }
    }
}