# 2D Particles in XNA – Part 2 (of 3)

As you can see from the title, this article is a follow up of 2D particles in XNA – Part 1 article. It assumes that you’ve read and understood it.

First, I’ll give a brief recap of the previous part, then we’ll continue making a working application with a particle effect in it.

The Particle Class:

Contains enough information to draw itself and change its position and other properties (such as color and size) as a function of time (Δt or dt)

The Emitter Class:

Contains information to create a particle and add noise to the particle properties. It should also be able to loop trough the list (LinkedList) of particles and call its update and draw methods. When needed, it should delete a particle out of the list and when it’s time and if there is enough space left, it should create a  new particle and add it to the list. The emitter should also have information about its relative position to its parent (Particle System).

The ParticleSystem Class:

This class is used to group several emitters. This helps keeping your code clean and helps moving several emitters at once (will discuss the advantages at a later stage). It should be able to add emitters to a list and loop trough them and call their update and draw methods when needed.

Now the new stuff:

Before we can fire up Visual Studio, we’ll need to discuss 1 more thing. It’s a little bit of math. It’s called Linear Interpolation and what it does is it takes a weighted average of two values. The weighs of both values are between the 0.0f and 1.0f. And the sum of the two weights is 1.0f. This means that one of the values is X and the other is 1-X, which means that we only need to pass one weight to the linear interpolation method.

For example: We want to linearly interpolate the floats 2.3 and 6.5 and give the first number a weight of 0.3f. The method would give: 2.3*0.3+6.5*(1-0.3)=5.24.

You might be thinking: What does this have to do with the particle system? Well the following:

Imagine you want to add a noise to the scale of the particle in the particle creation code (in the Emitter) you want the scales to be between 6 and 14. Then you can do: LinearInterpolate(4, 14, random.NextDouble()) Random.NextDouble() gives you a random number between 0 and 1, so this is perfect for the weight parameter of the method.

Another great use of this is the following: You aren’t restricted to linearly interpolating numbers such as ints, floats and bytes, you can also interpolate colors by interpolating the properties (r, g, b and a) separately and then making a new color out of them. This is great for generating a random color that lies between two colors you specify. Yet another use is to make the weight dependent on the life of the particle (0.0 means full life and 1.0 means life has expired) and do something like this: LinearInterpolate(StartColor, EndColor, life) which can be used to make a particle fade away when its getting older by making the alpha value of the EndColor equal to 0.

I’ve made a library with a few overloads (methods with same name but different parameters/return type) of the LinearInterpolate method, made them static and put them in my class called MathLib. Now, whenever I want to do a linear interpolation (doesn’t matter whether it’s with colors, floats, vector2, vector4 or bytes) I just call newvalue = MathLib.LinearInterpolate(val1, val2, weight)

Here is my MathLib class (modified for web):

```class MathLib
{
/* Begin Linear Interpolation method overloads */
public static byte LinearInterpolate(byte a, byte b, double t)
{
return (byte)(a * (1 - t) + b * t);
}
public static float LinearInterpolate(float a, float b, double t)
{
return (float)(a * (1 - t) + b * t);
}
public static Vector2 LinearInterpolate(Vector2 a, Vector2 b, double t)
{
return new Vector2(LinearInterpolate(a.X, b.X, t),
LinearInterpolate(a.Y, b.Y, t));
}
public static Vector4 LinearInterpolate(Vector4 a, Vector4 b, double t)
{
return new Vector4(LinearInterpolate(a.X, b.X, t),
LinearInterpolate(a.Y, b.Y, t),
LinearInterpolate(a.Z, b.Z, t),
LinearInterpolate(a.W, b.W, t));
}
public static Color LinearInterpolate(Color a, Color b, double t)
{
return new Color(LinearInterpolate(a.R, b.R, t),
LinearInterpolate(a.G, b.G, t),
LinearInterpolate(a.B, b.B, t),
LinearInterpolate(a.A, b.A, t));
}
/* End Linear Interpolation method overloads */
}
```

When I want to specify a range of a values for the emitter to choose one randomly using the LinearInterpolate method, I usually use the Vector2 structure. I use the x-component for one and the y-component for the other. I’ll also use this in the examples in this tutorial.

As mentioned by Paul in the comments, most XNA data structures already have a static method that helps you linearly interpolate values.

For example: Color.Lerp(c1, c2, t);

See this link for its documentation. These methods will probably work just fine if you want to use those instead of rolling up your own.

Particle class – Details:

Let’s discuss the Particle class in real detail (a real working one): We have the following properties (not the same properties as C# properties, just… properties):

• Position – A Vector2 that indicates the current position of that particular particle.
• StartDirection – This is the speed vector (Vector2) that the particle has when newly created.
• EndDirection – This is the speed vector (Vector2) that the particle has when its life has expired. As you might have guessed, we use a linear interpolation of the two speeds to get the right speed for that particular life phase.
• LifeLeft – This variable (a float) will be reduced at every update call (LifeLeft -=  dt) and at the beginning it will be equal to  StartingLife.
• StartingLife – Indicates how many seconds the particle will live in total (a float as well)
• lifePhase – This isn’t really a property of the particle, since it is recalculated every time (at the update) But it is a useful tool, and used in both he draw as well as the update methods, that’s why its a member variable. What it represents is the phase of the life in a 0-1 range (0 indicating end of life and 1 indicating the begin) and can be used in the LinearInterpolate methods. It is calculated by: lifePhase = LifeLeft/StartingLife.
• ScaleBegin – A float that represents the size (diameter) of the sprite at the start of its life.
• ScaleEnd – A float that represents the size (diameter) of the sprite at the end of its life. I’ve found it useful to make the particles smaller as they get older.
• StartColor – Same as above, but with color.
• EndColor – Same as above, but with color. I’ve found it useful to make the alpha of the EndColor equal to 0, so that when a particle dies and disappears, it isn’t really seen since its alpha is close to 0 already.
• Parent – This is just a pointer to the parent (Emitter) so you can access useful variables that are shared by all particles (for example the ParticleBase texture and a few others to be discussed)

That’s about it. Now we need a constructor, an update method and a draw method.

The constructor is very simple, it takes all the member variables (except for lifePhase) as parameters and stores them in the member variables. I’ll still post it for those who are lazy:

```    public Vector2 Position;
Vector2 StartDirection;
Vector2 EndDirection;
float LifeLeft;
float StartingLife;
float ScaleBegin;
float ScaleEnd;
Color StartColor;
Color EndColor;
Emitter Parent;
float lifePhase;

public Particle(Vector2 Position, Vector2 StartDirection, Vector2 EndDirection, float StartingLife, float ScaleBegin, float ScaleEnd, Color StartColor, Color EndColor, Emitter Yourself)
{
this.Position = Position;
this.StartDirection = StartDirection;
this.EndDirection = EndDirection;
this.StartingLife = StartingLife;
this.LifeLeft = StartingLife;
this.ScaleBegin = ScaleBegin;
this.ScaleEnd = ScaleEnd;
this.StartColor = StartColor;
this.EndColor = EndColor;
this.Parent = Yourself;
}
```

The Update method is a little bit different from the one shown in Part 1. It now uses a linear interpolate for the start and end directions and also it calculates the lifePhase variable needed for those the interpolation.

```public bool Update(float dt)
{
LifeLeft -= dt;
if (LifeLeft <= 0)
return false;
lifePhase = LifeLeft / StartingLife;  // 1 means newly created 0 means dead.
Position += MathLib.LinearInterpolate(EndDirection, StartDirection, lifePhase)*dt;
return true;
}
```

The remaining code is the same as that of Part 1

In the Draw method, we calculate the current scale using a linear interpolation of the starting scale, ending scale and the life phase. We do the same with the current color and then we draw it on the screen using the SpriteBatch.Draw method.

The draw method we use is one of the complicated versions (overloads) since we need to scale the sprite and do some other things that we’ll discuss later. I’ve also added two parameters to the draw method, since I found it useful to scale a particle effect and/or give it an offset. You can keep Scale to 1 and the offset to Vector2.Zero or new Vector2(0,0) if you don’t need these things.

Here is the code, you should be able to understand this:

```public void Draw(SpriteBatch spriteBatch, int Scale, Vector2 Offset)
{
float currScale = MathLib.LinearInterpolate(ScaleEnd, ScaleBegin, lifePhase);
Color currCol = MathLib.LinearInterpolate(EndColor, StartColor, lifePhase);
spriteBatch.Draw(Parent.ParticleSprite,
new Rectangle((int)((Position.X - 0.5f * currScale)*Scale+Offset.X),
(int)((Position.Y - 0.5f * currScale)*Scale+Offset.Y),
(int)(currScale*Scale),
(int)(currScale*Scale)),
null, currCol, 0, Vector2.Zero, SpriteEffects.None, 0);
}
```

Well, that’s it for the particle class.

Emitter class – Details:

Let’s continue with the Emitter class, this one is going to do most of the work. Expect lots of linear interpolations and a lot of member variables.

First of all, the emitter has a relative position (relative to the particle system) a budget (maximum particles alive at the same time) a linked list of active particles, a random number generator and the particle base sprite, two timing variables (NextSpawnIn and SecPassed)

These members have already been covered in the previous section.

Then, there is a laundry list of members that define the creation of a new particle:

• SecPerSpawn – A Vector2, since it is used to specify a range of floats (discussed right before  “Particle class – Details”) Obviously, the lower the value, the faster the particles will spawn.
• SpawnDirection – This is a vector (Vector2) that defines the spawn direction. It has nothing to say about the speed, so the length of this vector doesn’t really matter. In order to introduce noise to (the spawn) direction we need to define a vector and how many angles it can be off that vector.
• SpawnNoiseAngle – Two angles that define the noise. To Illustrate this point, take a look at the image below. Say vector C is the SpawnDirection, and the spawnnoiseangles were -α and β then the range of the directions of the particles could be any between vector A and vector B. I’ll show how to implement this in C# soon. • StartLife – A Vector2. Should be self-explanatory.
• StartScale – Same as above.
• EndScale – Same as above.
• StartColor1 – Since we can’t put two color values inside a vector2, we’ll use two variables so that we can add noise to the colors aswell.
• StartColor2 – See above.
• EndColor1 – See above.
• EndColor2 – See above.
• StartSpeed – Same as above.
• EndSpeed – Same as above.

All these values should be made public so that we can change the look of our effect while the effect is active (the emitter has been created)

We should also keep a pointer to the parent (type is ParticleSystem) so that all the emitters can share common variables like the position of the particle system.

The constructor isn’t going to do much of the magic in the emitter. Just like in the Particle class, it mostly passes parameters directly to its member variables.

There are 3 exceptions though.

1. The linked list will need to be initialized in the constructor (explained in part 1)
2. The SecPassed needs to be set to 0;
3. And the NextSpawnIn variable value needs to be generated for the first particle. This is done by linearly interpolating the two SecPerSpawn values with a random.NextDouble().

Here is the complete code of the member variables and constructor:

```public Vector2 RelPosition;             // Position relative to collection.
public int Budget;                      // Max number of alive particles.
float NextSpawnIn;                      // This is a random number generated using the SecPerSpawn.
float SecPassed;                        // Time pased since last spawn.
LinkedList<Particle> ActiveParticles;   // A list of all the active particles.
public Texture2D ParticleSprite;        // This is what the particle looks like.
public Random random;                   // Pointer to a random object passed trough constructor.

public Vector2 SecPerSpawn;
public Vector2 SpawnDirection;
public Vector2 SpawnNoiseAngle;
public Vector2 StartLife;
public Vector2 StartScale;
public Vector2 EndScale;
public Color StartColor1;
public Color StartColor2;
public Color EndColor1;
public Color EndColor2;
public Vector2 StartSpeed;
public Vector2 EndSpeed;

public ParticleSystem Parent;

public Emitter(Vector2 SecPerSpawn, Vector2 SpawnDirection, Vector2 SpawnNoiseAngle,
Vector2 StartLife, Vector2 StartScale, Vector2 EndScale, Color StartColor1,
Color StartColor2, Color EndColor1, Color EndColor2, Vector2 StartSpeed,
Vector2 EndSpeed, int Budget, Vector2 RelPosition, Texture2D ParticleSprite,
Random random, ParticleSystem parent)
{
this.SecPerSpawn = SecPerSpawn;
this.SpawnDirection = SpawnDirection;
this.SpawnNoiseAngle = SpawnNoiseAngle;
this.StartLife = StartLife;
this.StartScale = StartScale;
this.EndScale = EndScale;
this.StartColor1 = StartColor1;
this.StartColor2 = StartColor2;
this.EndColor1 = EndColor1;
this.EndColor2 = EndColor2;
this.StartSpeed = StartSpeed;
this.EndSpeed = EndSpeed;
this.Budget = Budget;
this.RelPosition = RelPosition;
this.ParticleSprite = ParticleSprite;
this.random = random;
this.Parent = parent;
ActiveParticles = new LinkedList<Particle>();
this.NextSpawnIn = MathLib.LinearInterpolate(SecPerSpawn.X, SecPerSpawn.Y, random.NextDouble());
this.SecPassed = 0.0f;
}
```

I’ll leave the update method for last, since the other two methods are a lot shorter. The Draw and Clear methods.

The draw method is just a little bit different from the one in Part 1. It has two extra parameters, the scale and offset and it passes those two parameters into the Particle.draw call. The rest has already been discussed in Part 1.
The clear method is even simpler, it just clears the active particle list making sure that the particle count starts at 0 again.

```public void Draw(SpriteBatch spriteBatch, int Scale, Vector2 Offset)
{
LinkedListNode<Particle> node = ActiveParticles.First;
while (node != null)
{
node.Value.Draw(spriteBatch, Scale, Offset);
node = node.Next;
}
}
public void Clear()
{
ActiveParticles.Clear();
}
```

Allright, now comes the part where all the magic happens.

The Update method consists of two while loops. One keeps running while we should be spawning new particles and the other one loops trought the particle list and calls their update method. The second while loop has already been discussed in the first part, so we’ll only discuss the particle creation here.

Particles should be created both of these expressions are true:

a) The number of active particles is lower than the budget.

b) It is time to create a new particle. That is, we’ve waited long enough for the next (few) particle(s) to spawn.

The first one is simple, if(ActiveParticles.Count < budget)

For the second one, we need to keep a timer. We need the SecPassed variable (seconds passed after last particle spawn) and the NextSpawnIn variable (number of seconds that need to pass before the next spawn)

We increase the SecPassed every update call (SecPassed += dt) and we check in a while loop whether SecPassed is larger than NextSpawnIn. We do this in a while loop, because it is very well possible to add multiple particles in one update call if your particle spawn rate is faster than the frame rate (we’ll get deeper into this in the next part). Furthermore, every time a particle is spawned, we need to generate a new NextSpawnIn, since not every particle is spawned at the same rate, the user (programmer) can add noise to it. And every time a particle is spawned, we also need to decrease the SecPassed with NextSpawnIn (SecPassed -= NextSpawnIn) instead of setting SecPassed to 0, because as I said it before, we might spawn more than one particle in one update call.

The creation of the new particle is quite straightforward, mostly. You need to call the LinkedList.AddLast method and put a particle as parameter (using new and the constructor).  As I said, most of the parameters values are easy to calculate by using the LinearInterpolate methods of the MathLib class we created.

The only tricky part is the creation of the StartDirection and EndDirection vectors. This involves a little bit of linear algebra, but don’t worry, XNA does most of the work for us.

First of all, we need to rotate the SpawnDirection vector with a random angle specified in SpawnNoiseAngle. Rotating vectors in linear algebra is done by using rotation matrices (ooh, scary!). But we don’t have to create these matrices by hand, since there is a nice method called Matrix.CreateRotationZ(float radians) that does the job for us. We can just pass our linear interpolate method of the two angles in the parameter, and we get a rotation matrix as a result:

`Matrix m = Matrix.CreateRotationZ(MathLib.LinearInterpolate(SpawnNoiseAnge.X, SpawnNoiseAngle.Y, random.NextDouble());`

For those who forgot what radians were, 180 degrees is π radians.

Furthermore, to transform the SpawnDirection vector, we call the method Vector2.Transform(Vector2 vec, Matrix m) and we get our nicely transformed vector as a result.

Since we are only interested in the direction of the vector, we want the length to be equal to 1 (so that we can then multiply the vector by its speed and get our desired results).

A vector of the length 1 is called a unit vector. Changing a vector into a unit vector is called normalizing. You can normalize a vector by hand by dividing every components with the length of the vector. Or you can use yet another method in XNA that does this for you. The method is called Normalize(). When calling normalize on a vector, it normalizes itself, so it doesn’t have a return value.

After we have our normalized vector that points to the right direction, we want to give it the right length. We do this by multiplying it with the speed we want it to have. We get the speed by linearly interpolating the two speed value.

I think this should be enough to understand the code of the method:

```public void Update(float dt)
{
SecPassed += dt;
while (SecPassed > NextSpawnIn)
{
if (ActiveParticles.Count < Budget)
{
// Spawn a particle
Vector2 StartDirection = Vector2.Transform(SpawnDirection, Matrix.CreateRotationZ(MathLib.LinearInterpolate(SpawnNoiseAngle.X, SpawnNoiseAngle.Y, random.NextDouble())));
StartDirection.Normalize();
Vector2 EndDirection = StartDirection * MathLib.LinearInterpolate(EndSpeed.X, EndSpeed.Y, random.NextDouble());
StartDirection *= MathLib.LinearInterpolate(StartSpeed.X, StartSpeed.Y, random.NextDouble());
ActiveParticles.AddLast(new Particle(
RelPosition + Parent.Position,
StartDirection,
EndDirection,
MathLib.LinearInterpolate(StartLife.X, StartLife.Y, random.NextDouble()),
MathLib.LinearInterpolate(StartScale.X, StartScale.Y, random.NextDouble()),
MathLib.LinearInterpolate(EndScale.X, EndScale.Y, random.NextDouble()),
MathLib.LinearInterpolate(StartColor1, StartColor2, random.NextDouble()),
MathLib.LinearInterpolate(EndColor1, EndColor2, random.NextDouble()),
this)
);
}
SecPassed -= NextSpawnIn;
NextSpawnIn = MathLib.LinearInterpolate(SecPerSpawn.X, SecPerSpawn.Y, random.NextDouble());
}

LinkedListNode<Particle> node = ActiveParticles.First;
while (node != null)
{
bool isAlive = node.Value.Update(dt);
node = node.Next;
if (!isAlive)
{
if (node == null)
{
ActiveParticles.RemoveLast();
}
else
{
ActiveParticles.Remove(node.Previous);
}
}
}
}
```

Well, that’s about it for the Emitter class.

ParticleSystem class – Details:

To be honest, there isn’t that much to tell about the particle system class. It has 3 member variables. A List<Emitter> which is an array of Emitter that can resize, has a method to add new elements (Add()) and also has a Count property showing how many elements there are in the list. The List container can do more, but we won’t be discussing that here, since we don’t use the other capabilities. Ow yeah, you can also access list elements the same way as array elements with the [].

The other two members are a position vector (Vector2) and a random object (Random).

Its constructor has one parameter, Position which is passed to the member variable, it also initializes the list and the random object.

The Update method is simple as well, it just loops trough the EmitterList and calls the update method of every emitter.

The Draw method does the same, but then with the Draw method of the emitters.

So does the Clear method with the Clear method of the emitters.

And the AddEmitter method has loads of parameters, but all of these are directly passed to the constructor of the emitter (along with the random object and the this pointer) and the add method of the EmitterList is called.

That’s about it. Here is the code:

```public class ParticleSystem
{
public List<Emitter> EmitterList;
public Vector2 Position;
Random random;

public ParticleSystem(Vector2 Position)
{
this.Position = Position;
random = new Random();
EmitterList = new List<Emitter>();
}

public void Update(float dt)
{
for (int i = 0; i < EmitterList.Count; i++)
{
if (EmitterList[i].Budget > 0)
{
EmitterList[i].Update(dt);
}
}
}

public void Draw(SpriteBatch spriteBatch, int Scale, Vector2 Offset)
{
for (int i = 0; i < EmitterList.Count; i++)
{
if (EmitterList[i].Budget > 0)
{
EmitterList[i].Draw(spriteBatch, Scale, Offset);
}
}
}

public void Clear()
{
for (int i = 0; i < EmitterList.Count; i++)
{
if (EmitterList[i].Budget > 0)
{
EmitterList[i].Clear();
}
}
}

public void AddEmitter(Vector2 SecPerSpawn, Vector2 SpawnDirection, Vector2 SpawnNoiseAngle, Vector2 StartLife, Vector2 StartScale,
Vector2 EndScale, Color StartColor1, Color StartColor2, Color EndColor1, Color EndColor2, Vector2 StartSpeed,
Vector2 EndSpeed, int Budget, Vector2 RelPosition, Texture2D ParticleSprite)
{
Emitter emitter = new Emitter(SecPerSpawn, SpawnDirection, SpawnNoiseAngle,
StartLife, StartScale, EndScale, StartColor1,
StartColor2, EndColor1, EndColor2, StartSpeed,
EndSpeed, Budget, RelPosition, ParticleSprite, this.random, this);
EmitterList.Add(emitter);
}
}
```

We’ll add some code to the ParticleSystem class in the next part.

The Implementation – A simple particle effect.

In order to convince you folks that this stuff actually works, and I’ve not wasted your time, at least not entirely, here we’ll implement the actual particle effect.

Start by making a standard empty XNA game. Delete everything in it except for program.cs (delete everything inside program.cs aswell) and then add the files ParticleSystem.cs, Particle.cs, Emitter.cs and MathLib.cs to the project.

Also add ParticleBase1.png (don’t worry, all these files are included in the zip file at the end of this article) to the content folder.

Then start with a regular class inheriting from the game class. Create the constructor and all the standard stuff (update method, draw method, initialize method) and make sure you can see a black screen when compiling and running the application.

Something like this:

```class CMain : Game
{
static void Main()
{
CMain game = new CMain();
game.Run();
}

SpriteBatch spriteBatch;

public CMain()
{
GraphicsDeviceManager gdm = new GraphicsDeviceManager(this);
this.Content.RootDirectory = "Content";
}

protected override void Initialize()
{
// We'll do loadcontent in initialize method
spriteBatch = new SpriteBatch(this.GraphicsDevice);
}

protected override void Update(GameTime gameTime)
{
}

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

// Drawing stuff here

spriteBatch.End();
}
}
```

Then add the members: ParticleSystem particleSystem and Texture2D ParticleBase.

In the initialize method, load the particle base and create the particle system (by calling the constructor)

After that, call the AddEmitter method of the particle system and fill in the laundry list (there is an example below)

After that, all you need to do is call the update method of the particle system in the update method and call the draw method of the particle system in the draw method.

Here is an example:

```protected override void Initialize()
{
// We'll do loadcontent in initialize method
spriteBatch = new SpriteBatch(this.GraphicsDevice);
ParticleBase = this.Content.Load<Texture2D>("ParticleBase1");
particleSystem = new ParticleSystem(new Vector2(400, 300));
particleSystem.AddEmitter(new Vector2(0.01f, 0.015f),
new Vector2(0, -1), new Vector2(0.1f*MathHelper.Pi, 0.1f*-MathHelper.Pi),
new Vector2(0.5f, 0.75f),
new Vector2(60, 70), new Vector2(15, 15f),
Color.Orange, Color.Crimson, new Color(Color.Orange, 0), new Color(Color.Orange, 0),
new Vector2(400, 500), new Vector2(100, 120), 1000, Vector2.Zero, ParticleBase);
}

protected override void Update(GameTime gameTime)
{
particleSystem.Update(gameTime.ElapsedGameTime.Milliseconds / 1000f);
}

protected override void Draw(GameTime gameTime)
{
this.GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
particleSystem.Draw(spriteBatch, 1, Vector2.Zero);
spriteBatch.End();
}
```

Hit compile and you’ll get a… not so impressive effect (however, it’s more impressive while running than in the screenshot): Don’t worry, we’ll work on that in the next part (which hopefully won’t be as long as this one) where we’ll add blending and other stuff that will make our particle effect really nice.

It doesn’t mean you can’t make nice effects with what you’ve got right now, I just didn’t fill in the parameters well enough. Post your own creations in the comments if you’d like.

Here is a zip of the project that will compile this: Project files!

The ParticleBase1.png is also in this zip file in the content folder.

I recommend you to play a bit with the particle system, change some variables, make your computer lag, and become familiar with all the parameters.

Try making the particle system follow your mouse for instance and make it create particles at a very fast rate. Move your mouse very fast across the screen, you’ll notice that something is wrong. We’ll fix that in the next part.

I’ll write the third part soon. You can follow me on twitter (I’ll tweet when i’ve finished the next article @TGasparian) or revisit my blog once in a while or both(!)

Part 3 of these tutorials will probably be the last one. There I’ll explain how to make your particle system even better with additive blending, layers and more (actually one more thing)

Please post questions, corrections or any other comments.

Here’s Part 3!