Week4 - Particle effects
Week4.png

Start Project: Week4Start.zip

Finished Project: Week4Finish.zip

Notes

The majority of the work done for this workshop can be found at RB Whitaker's 2D Particle Engine Tutorial. I don't want to repeat the same information, so I recommend going through his particle engine tutorials before continuing.
The Week4Start.zip contains the starting project. It is almost exactly the same as Week3Finish.zip from last week. I made a few modifications to the Draw methods, added a sprite for the particle, and replaced the background with a plain old boring star field.
The Particle.cs class that we will create for this assignment is similar (if not the same) as RB's. Create a new class called Particle.cs and add the following code.

public class Particle
{
    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;
        origin = new Vector2(Texture.Width / 2, Texture.Height / 2);
    }
 
    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; }
    private Vector2 origin;
 
    public void Update()
    {
        TTL--;
        Position += Velocity;
        Angle += AngularVelocity;
    }
 
    public void Draw(SpriteBatch spriteBatch)
    {
        spriteBatch.Draw(Texture, Position, null, Color,
            Angle, origin, Size, SpriteEffects.None, 0f);
    }
}

Next thing we do is create the particle engine class. Our ParticleEngine.cs class will differ significantly from RB's, but I'll go over the changes.

public class ParticleEngine
{
    private readonly Random random;
    public Vector2 EmitterLocation { get; set; }
    private readonly List<Particle> particles;
    private readonly Texture2D texture;
 
    public ParticleEngine(Texture2D texture, Vector2 location)
    {
        EmitterLocation = location;
        this.texture = texture;
        particles = new List<Particle>();
        random = Game1.Random;
    }
 
    private Particle GenerateNewParticle()
    {
        Vector2 position = EmitterLocation;
        Vector2 velocity = new Vector2(
                2f * (float)(random.NextDouble() * 2 - 1),
                2f * (float)(random.NextDouble() * 2 - 1));
        const float angle = 0;
        float angularVelocity = 0.1f * (float)(random.NextDouble() * 2 - 1);
        Color color = new Color(
                1.0f,
                (float)random.NextDouble(),
                (float)random.NextDouble() * 0.2f);
        float size = (float)random.NextDouble() * 0.7f;
        int ttl = 20 + random.Next(20);
 
        return new Particle(texture, position, velocity, angle, angularVelocity, color, size, ttl);
    }
 
    public void GenerateNewParticles(int total)
    {
        for (int i = 0; i < total; i++)
        {
            particles.Add(GenerateNewParticle());
        }
    }
 
    public void Update()
    {
        for (int particle = 0; particle < particles.Count; particle++)
        {
            particles[particle].Update();
            if (particles[particle].TTL <= 0)
            {
                particles.RemoveAt(particle);
                particle--;
            }
        }
    }
 
    public void Draw(SpriteBatch spriteBatch)
    {
        spriteBatch.Begin(SpriteBlendMode.Additive, SpriteSortMode.Deferred, SaveStateMode.None);
        for (int index = 0; index < particles.Count; index++)
        {
            particles[index].Draw(spriteBatch);
        }
        spriteBatch.End();
    }
}

The differences are as follows:
  • Replaced List<Texture2D> textures with Texture2D texture because we will be using only one texture. I also removed all the random selection of textures from GenerateNewParticle().
  • random is set to Game1.Random. We will be using multiple ParticleEngine objects and we want them to get their randomness from just one random number generator.
  • Many of the variables in GenerateNewParticle() have different values. You can change these values to get different effects and I would encourage you to do so just to see what the different results are.
  • I added a public method called GenerateNewParticles(int total) that creates total number of new particles when it is called.
  • Update() no longer creates new particles. It only updates the currently existing ones.
  • In Draw(), the spriteBatch.Begin(); is overloaded to be spriteBatch.Begin(SpriteBlendMode.Additive, SpriteSortMode.Deferred, SaveStateMode.None);
  • In actuality, the default spriteBatch.Begin() method is exactly the same as spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred, SaveStateMode.None); so the only difference is we're replacing AlphaBlend with Additive. AlphaBlend allows transparency and blends them together to get the image. Additive adds all red, green, and blue values together and displays this instead. This means that if there are a lot of particles on top of each other, the additive color will be closer to white. If you get the chance, try replacing Additive with AlphaBlend and see what the change is.

Once we have these classes created, we need to put instances of ParticleEngine into Probe and GameObject. We'll put them in Probe first to get their explosions going. Before doing this, let's put in a global variable to hold the particle texture. In Game1.cs at the top (with all the other declarations) put the following line of code:

public static Texture2D ParticleTexture;

and then in LoadContent() at the top put
ParticleTexture = Content.Load<Texture2D>("Sprites/circle");

We now have access to the texture when we need it.
For Probe, we need to declare and initialize a ParticleEngine object. Right below the rest of the properties put this line:
private ParticleEngine particleEngine;

And then in the constructor at the bottom put:
particleEngine = new ParticleEngine(Game1.ParticleTexture, Position);

This creates a new ParticleEngine object with the ParticleTexture texture we just put into Game1.cs.
Once we have this in place we need to have it create particles every time the probe dies. In Probe.Die() put the following lines at the top:
particleEngine.EmitterLocation = Position;
particleEngine.GenerateNewParticles(50);

Here we set the position of the emitter location (note that this must be done before resetting the position) and then fire off some particles. After this we need to update the engine and draw the particles to the screen. In Probe.Update() put:
particleEngine.Update();

and in Probe.Draw() put at the beginning of the method:
particleEngine.Draw(spriteBatch);

In Draw() we draw the particles first and the ships second because we want the ships drawn on top. Now run the program and watch as the probes actually explode when shot.

The other place we want to put a particle engine is for the ship's engine. We'll create the particle engine the same way as before, so where the properties are declared in GameObject.cs add:

private ParticleEngine particleEngine;

and in the constructor put:
particleEngine = new ParticleEngine(Game1.ParticleTexture, Position);

Instead of generating the particles in a die method (which doesn't exist), we're going to put it in GameObject.Thrust() so that any time thrust is created, the particles will look like exhaust coming from the ship. At the top of GameObject.Thrust() add the following lines:
particleEngine.EmitterLocation = new Vector2(
    Position.X - (float)(Texture.Height / 2 * Scale * Math.Sin(Rotation)),
    Position.Y - (float)(Texture.Height / 2 * Scale * -Math.Cos(Rotation)));
particleEngine.GenerateNewParticles(5);

We want to create the particles where the engine of the ship is, so we need to do some math to position the emitter location there. If you look closely, you'll see that the way to do this is very similar to calculating the acceleration. If you remember, GameObject.Position gives us the center of the ship, so we start there. Then we need to move the emitter location half of the height of the texture in the GameObject.Rotation direction. This will give us the correct position.
Once we have that, all we need to do is update and draw the particle engine. Put this line of code in GameObject.Update():
particleEngine.Update();

and this line at the beginning of GameObject.Draw():
particleEngine.Draw(spriteBatch);

That's everything! Particles should now be generated every time the ship accelerates forward and any time a probe is destroyed.

back to SIGXNA

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License