5 min read

Particle Systems - Part 2

Anatomy of a Particle System

The next thing we want to do is discuss a little bit about how particle systems work. A particle system has three major components: particles, a particle emitter, and the system itself. A particle is a small point in your game that will be drawn with an image. A particle often has a position in the game world, as well as a velocity, an angle that it is rotated at, an angular velocity which indicates how quickly the particle is spinning, and usually a color and texture for drawing. In our particle engine, we will have lots of particles, which all add up to produce an interesting effect. Also, particles almost always have some way of determining how long they should live for, so the particles can be removed as new ones are generated.

A particle emitter is essentially the location that the particles are coming from. Often the particle emitter is also responsible for determining how many particles will be created at any given time.

The particle engine manages the state of the previous two components and is responsible for removing dead particles from the system.

Particles

Our first task will be to create a Particle class, which will be used in our particle system. The first step will be to create the class itself. To create this class, 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 Particle.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 Particle

and change it to say:

public class Particle

The Particle’s Properties

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:

public Texture2D Texture { get; set; }		// The texture that will be drawn to represent the particle
public Vector2 Position { get; set; }		// The current position of the particle        
public Vector2 Velocity { get; set; }		// The speed of the particle at the current instance
public float Angle { get; set; }			// The current angle of rotation of the particle
public float AngularVelocity { get; set; }	// The speed that the angle is changing
public Color Color { get; set; }			// The color of the particle
public float Size { get; set; }				// The size of the particle
public int TTL { get; set; }				// The 'time to live' of the particle

All of these parameters should make sense to you, except perhaps the TTL property, which is short for “time to live”. Essentially, this variable will count down to zero, at which point, the particle is dead.

The Constructor

The constructor is simply going to take each of these values in and assign them to their respective fields, as shown below:

public Particle(Texture2D texture, Vector2 position, Vector2 velocity,
            float angle, float angularVelocity, Color color, float size, int ttl)
{
    Texture = texture;
    Position = position;
    Velocity = velocity;
    Angle = angle;
    AngularVelocity = angularVelocity;
    Color = color;
    Size = size;
    TTL = ttl;
}

The Update() Method

The Update() method has a few things that it needs to worry about. First, it needs to update both the position and angle by the velocity and angular velocities respectively. Additionally, it needs to decrement the TTL (time to live) variable to move it one step closer to expiring. You can do this by adding the following code to your Particle class:

public void Update()
{
    TTL--;
    Position += Velocity;
    Angle += AngularVelocity;
}

The Draw() Method

The Draw() method only needs to draw the particle’s texture with all of the necessary properties, which is done with the code below:

public void Draw(SpriteBatch spriteBatch)
{
    Rectangle sourceRectangle = new Rectangle(0, 0, Texture.Width, Texture.Height);
    Vector2 origin = new Vector2(Texture.Width / 2, Texture.Height / 2);

    spriteBatch.Draw(Texture, Position, sourceRectangle, Color,
        Angle, origin, Size, SpriteEffects.None, 0f);
}

We start off by doing a couple of quick calculations for the source rectangle (which we want to cover the entire texture) and the origin, which is the center of rotation, we want to be at the center of the texture. We then draw the texture scaled, rotated, colored, and placed according to the information of the particle. Notice that we don’t call spriteBatch.Begin() or spriteBatch.End() here, because we are going to be drawing a lot of these all at one time. We will call Begin() and End() later when we are writing the drawing code for the entire particle engine.

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;

That should get you a complete Particle class, and in part 3, we will move on and design our ParticleEngine class. Below is the entire code for this class, all assembled together:

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

namespace ParticleEngine2D
{
    public class Particle
    {
        public Texture2D Texture { get; set; }
        public Vector2 Position { get; set; }
        public Vector2 Velocity { get; set; }
        public float Angle { get; set; }
        public float AngularVelocity { get; set; }
        public Color Color { get; set; }
        public float Size { get; set; }
        public int TTL { get; set; }

        public Particle(Texture2D texture, Vector2 position, Vector2 velocity,
            float angle, float angularVelocity, Color color, float size, int ttl)
        {
            Texture = texture;
            Position = position;
            Velocity = velocity;
            Angle = angle;
            AngularVelocity = angularVelocity;
            Color = color;
            Size = size;
            TTL = ttl;
        }

        public void Update()
        {
            TTL--;
            Position += Velocity;
            Angle += AngularVelocity;
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            Rectangle sourceRectangle = new Rectangle(0, 0, Texture.Width, Texture.Height);
            Vector2 origin = new Vector2(Texture.Width / 2, Texture.Height / 2);

            spriteBatch.Draw(Texture, Position, sourceRectangle, Color,
                Angle, origin, Size, SpriteEffects.None, 0f);
        }
    }
}