Simple Particle Effects

I was playing around with some particle effects, and decided to write a post about it.

yt. zxN71bGAaSY

This is a particle simulation in C#/XNA using a simplified version of Euler Integration . There’s nothing ground-breaking about this demo, but it is fun to play with, so I decided to write a blog post about it while I wait for my replacement motherboard to arrive.

Overview

This demo consists of a Particle class and the Game1 class that is generated when making a new XNA project. The Particle class holds all of the data for each particle, while the majority of the logic occurs in Game1’s Update and Draw methods.

The demo allows you to create and destroy particles using the keyboard, and manipulate their forces with the mouse.

Particle Class

The Particle class holds the properties of each particle.

public class Particle
{
    public Color color;
    public Vector2 position;
    public Vector2 oldPosition;
    public Vector2 velocity;
    public float relativeLength;
    public float relativeLengthSq;
    public float angle;

    public Particle(Color color, Vector2 position, Vector2 velocity)
    {
        this.color = color;
        this.position = position;
        oldPosition = position;
        this.velocity = velocity;
    }
}

The two most important properties for the particle are position and velocity. The particle’s velocity is modified each frame by adding forces like gravity, or forces created by mouse input. The particle’s position is modified by adding the particle’s velocity.

The rest of the variables in Particle are used for rendering. color is the base color used to render the particle. oldPosition stores the position of the particle in the previous frame. The relativeLength and relativeLengthSq variables hold the distance and squared distance between the current and previous position. angle is the angle between the current and previous position.

The particle’s previous position is stored in order to draw a line from it to the current position. Instead of drawing an actual line, I just stretch the particle texture using the relativeLength variable.

I also want the particle color to change depending on the speed. The relativeLength variable is larger the faster the particle is moving, so I could use that as the speed and multiply it by the particle’s color. In the end, however, I multiplied the relativeLengthSq by the particle’s color, so the transition between the base color and white is more abrupt.

Basic Logic

There are three main parts to the logic: initialization (happens once), updating (happens every frame), and drawing (also happens every frame).

Initialization

I stored the Particle objects in an array in Game1. I decided on a maximum amount of particles for the simulation, and initialized all of the Particle objects at once. The number of particles in the simulation are changed by modifying a numActiveParticles variable, which controls how many particles are updated/drawn.

public class Game1 : Microsoft.Xna.Framework.Game
{
    public const int MAX_ACTIVE_PARTICLES = 45000;
    private int _numActiveParticles;
    private Particle[] _particles;
    
    // ...
    
    protected override void Initialize()
    {
        Random random = new Random();
        _particles = new Particle[MAX_ACTIVE_PARTICLES];

        for (int i = 0; i < MAX_ACTIVE_PARTICLES; i++)
        {
            _particles[i] = new Particle(
                new Color(0.25f, 0.25f, 1f) * 0.25f,
                new Vector2(200f, 200f),
                new Vector2(
                    (float)random.NextDouble(),
                    (float)random.NextDouble()));
        }

        base.Initialize();
    }
}

Updating Particles

First, I want to handle input. I want the plus and minus keys to increase and decrease the amount of active particles. To do that, I need the KeyboardState . So, I’ll add KeyboardState _keyState as a member variable to Game1. I’ll also add KeyboardState _oldKeyState, so I can differentiate between a key being held down and a key being pressed once.

For now, I only want to apply a gravitational force to the particles. So add a Vector2 _gravity member variable to Game1 . I use (0, 0.15f) as the gravitational force.

Now, for the update loop:

protected override void Update(GameTime gameTime)
{
    // Get keyboard state
    _keyState = Keyboard.GetState();

    // Modify the number of active particles by either 10 or 100 depending on whether or not shift is pressed
    int increment = _keyState.IsKeyDown(Keys.LeftShift) ? 100 : 10;

    // Modify the number of active particles
    if (_keyState.IsKeyDown(Keys.OemMinus))
        _numActiveParticles = Math.Max(0, _numActiveParticles - increment);
    if (_keyState.IsKeyDown(Keys.OemPlus))
        _numActiveParticles = Math.Min(MAX_ACTIVE_PARTICLES - 1, _numActiveParticles + increment);
        
    for (int i = 0; i < _numActiveParticles; i++)
    {
        Particle particle = _particles[i];
        Vector2 force = Vector2.Zero;    // this will be used later

        // Update particle's velocity and position
        particle.oldPosition = particle.position;
        particle.velocity += force + _gravity;
        particle.position += particle.velocity;

        // Calculate other properties that will be used to render
        Vector2 relative = particle.oldPosition - particle.position;
        particle.relativeLengthSq = relative.LengthSquared();
        particle.relativeLength = (float)Math.Sqrt(particle.relativeLengthSq);
        particle.angle = (float)Math.Atan2(relative.Y, relative.X);
    }

    // Store the current key state as the old keystate, in preparation for the next Update() call
    _oldKeyState = _keyState;

    base.Update(gameTime);
}

Drawing Particles

All the properties necessary for rendering the individual particles were calculated at the end of the Update method. The only thing missing is a particle texture, so import a particle texture into your Content project, and load it (in the LoadContent method) into a variable called _particle . If you want to draw debug text information, you should load a SpriteFont into the _font variable and uncomment the DrawString calls.

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.Black);

    _spriteBatch.Begin();

    for (int i = 0; i < _numActiveParticles; i++)
    {
        Particle particle = _particles[i];

        // Select a speed between 0.1f and 1f
        float speed = (float)Math.Max(0.1f, (float)Math.Max(particle.relativeLengthSq * 0.05f, 1f));

        _spriteBatch.Draw(_particle, particle.position, _particle.Bounds, particle.color * speed, particle.angle, new Vector2(0, 1), new Vector2(particle.relativeLength / 2f, 1f), SpriteEffects.None, 0f);
    }

    _spriteBatch.DrawString(_font, String.Format("Particle count: {0}", _numActiveParticles), new Vector2(17, 17), Color.Black, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.2f);
    _spriteBatch.DrawString(_font, String.Format("Particle count: {0}", _numActiveParticles), new Vector2(16, 16), Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.3f);

    _spriteBatch.End();

    base.Draw(gameTime);
}

Now when you compile the program and increase the number of active particles (plus key), you should have something that resembles this:

The particles are just falling off the screen, so I’ll add some boundary conditions. The easiest way to do that is with Vector2 ’s Min and Max methods. First, I need to define the boundaries. Add the two member variables to Game1:

private Vector2 _topLeft;
private Vector2 _bottomRight;

Now I set those bounds in the Initialize method. The _topLeft boundary is inset a little from the corner of the screen at (64, 64) . The _bottomRight boundary is (64, 64) less than the screen’s width and height:

_topLeft = new Vector2(64, 64);
_bottomRight = new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height) - new Vector2(64, 64);

In Update, enforce the boundary conditions (after the particle’s position has been calculated) with this line:

particle.position = Vector2.Min(_bottomRight, Vector2.Max(_topLeft, _particles[i].position));

Now that the particles are staying within a specified boundary, it’s time to apply some external forces!

External Forces

I want to add some external forces by using the mouse input. I want the left mouse button to attract particles, and the right mouse button to repel them.

First, I added member variables to Game1 to track all these states:

private MouseState _mouseState;
private Vector2 _mouse;
private bool _mouseDown;
private bool _reverseForce;

Then I set the mouse-related variables in the beginning of the Update method:

_mouseState = Mouse.GetState();
_mouse = new Vector2(_mouseState.X, _mouseState.Y);
_mouseDown = _mouseState.LeftButton == ButtonState.Pressed;
_reverseForce = _mouseState.RightButton == ButtonState.Pressed;

Now I can use the mouse position to calculate the forces to apply to each particle. In the for loop in Update, calculate the following values:

Vector2 relativeMouse = _mouse - particle.position;
float distanceSq = relativeMouse.LengthSquared();

Before the particle’s position and velocity is applied, calculate the force:

if (_mouseDown || _reverseForce)
{
    // This line is probably horribly incorrect from a physics stand-point, but it works well enough for this demo
    force = relativeMouse * (1 / distanceSq) * 100f;
    if (_reverseForce)
        force *= -1;
}

You should now be able to apply forces to the particles by clicking the left and right mouse buttons:

One problem with the code so far, is that the velocity of particles continue to grow even though their positions are being restricted by the boundary. So I decided to use reflection to enforce the boundaries.

Improving the Boundaries

The equation for a reflected vector is:

reflection = vector - restitution * dot(normal, vector2) * normal

vector is the vector to be reflected, normal is the normal of the surface being hit, and restitution is the “bounciness” of the reflection. If you consider the reflection process as a ball hitting a surface, a restitution of 2 means that the “ball” will “bounce” back to its original height. I don’t want the particles to bounce that much when they hit the walls, so I allow the restitution to be specified when calling the reflect method:

private Vector2 reflect(ref Vector2 vector, ref Vector2 normal, float restitution)
{
    return vector - restitution * Vector2.Dot(normal, vector) * normal;
}

I need to define the normals for each edge. Add the following member variables to Game1 :

private Vector2 _leftEdgeNormal = new Vector2(1, 0);
private Vector2 _rightEdgeNormal = new Vector2(-1, 0);
private Vector2 _topEdgeNormal = new Vector2(0, 1);
private Vector2 _bottomEdgeNormal = new Vector2(0, -1);

Every time a particle is updated, I need to store its new position and velocity in temporary variables that can be corrected if necessary. I just call those newPosition and newVelocity.

I need to check which boundaries the new position is violating, and modify the velocity using the reflect method. After the boundary check, the particle’s position is updated using the newVelocity variable:

newVelocity = particle.velocity + force + _gravity;
newPosition = particle.position + newVelocity;

// If the new position is crossing a boundary, reflect the velocity against the normal of that edge.
if (newPosition.X < _topLeft.X)
{
    newVelocity = reflect(ref newVelocity, ref _leftEdgeNormal, 1.1f);
}
else if (newPosition.X > _bottomRight.X)
{
    newVelocity = reflect(ref newVelocity, ref _rightEdgeNormal, 1.1f);
}
if (newPosition.Y < _topLeft.Y)
{
    newVelocity = reflect(ref newVelocity, ref _topEdgeNormal, 1.1f);
}
else if (newPosition.Y > _bottomRight.Y)
{
    newVelocity = reflect(ref newVelocity, ref _bottomEdgeNormal, 1.1f);
}

// Update the particle
particle.oldPosition = particle.position;
particle.velocity = newVelocity;
particle.position += particle.velocity;

// Sometimes particles hitting corners can get stuck past boundaries, so I keep this line to enforce the boundary in that case
particle.position = Vector2.Min(_bottomRight, Vector2.Max(_topLeft, _particles[i].position));

That’s everything! If I left anything out or if you have any questions, feel free to contact me

Full source code: ParticleEffects.zip