Saturday, November 01, 2008

XNA Series - A Simple Particle System

Sometimes you need to create a an effect like smoke or an explosion. This is when you can turn to a particle system. You can think of a particle system as an emitter. It emits points on given velocities, and each of those points has a lifetime. Once that lifetime is over they are no longer updated or drawn. This example will be a simple "fountain"

First, we will create a class to represent each particle. We will call it Particle

public class Particle
{
public bool alive = false;
public Vector2 position = Vector2.Zero;
public Vector2 velocity = Vector2.Zero;
public int lifeTime = 0;
}

So each particle has a field that shows if it is to be drawn an updated. A current position and velocity as well as a counter for how many updates it has been alive.


public class ParticleSystem
{
public int totalParticles = 500;
public Vector2 position = Vector2.Zero;
public Texture2D sprite;
public Particle[] parts;
public int current = 0;
public int end = 60;
public bool started = false;


In our particle system class we keep track of how many particles are in the system, the position of the emitter, the texture of the particles, an array of particles, a counter of how many updates the system has been through and if the system is running.


public void Init(){
Random r = new Random();
parts = new Particle[totalParticles];
for (int i = 0; i < totalParticles; i++)
{
parts[i] = new Particle();
parts[i].velocity = new Vector2(0 +(float)r.Next(-100,100)/100f,
-3 + (float)r.Next(-100,100)/100f);
}
}


Here is an init function. This will instantiate the particle array and set each member to new particle. I was also lazy here and set the initial velocity to [0,-3] with each component getting a number between -1 and 1 added to it for some randomness.


public void Start()
{
started = true;
}


This method simply starts the system

public void Update(GameTime gameTime)
{
if (started)
{
current++;
if (current < totalParticles)
{
parts[current].alive = true;
parts[current].position = position;
}
for (int i = 0; i < totalParticles; i++)
{
if (parts[i].alive)
{
parts[i].lifeTime++;
if (parts[i].lifeTime > end)
{
parts[i].alive = false;
} else
{
parts[i].velocity.Y += 9.8f/50f;
parts[i].position = parts[i].position + parts[i].velocity;

}
}
}
}
}


The update method first checks to see if the system is started. If so, it increments the total updates for the system and if we still have particles left, we set the next one to alive and set it's position to that of the emitter. Then for each alive particle, we increment it's life, if its been alive too long we kill it, if not, we add some gravity to it's velocity (taking into account elapsed time, and then add it's velocity to it's position.




public void Draw(SpriteBatch sb)
{
for (int i = 0; i < totalParticles; i++)
{
if (parts[i].alive)
{
Color c = Color.White;
c.A = (byte)((1-(parts[i].lifeTime / (float)end))*255f);
sb.Draw( sprite,
parts[i].position,
null,
c,
0f,
new Vector2(12, 12),
parts[i].lifeTime / (float)end,
SpriteEffects.None,
0
);
}
}
}
}


The draw method is very simple. I have added a couple extra things in it to make it more interesting. First we simply iterate over the particles and if they are alive, we draw them to the spritebatch. Now to make it more interesting I divide the life of that particle by it's total time to live. Then use that as a percentage to alter the alpha value of the drawing color and also to alter the scale of the object. I just to them in reverse so that the scale goes from 0 to 1 and the then the alpha goes from 1 to 0 (then I multiply it by 255 to convert it to a byte).

So yes, this is a VERY simple particle system, In the project files you will see that I update the position of the system based on keyboard controls, so you can move the emitter around.



The Project Files
Post a Comment