Tuesday 3 July 2012

2D Particle Tutorial III


Well now onto something I know even less about, 2D particles. As you may or may not have seen I have had a play with these in my GSM Library where I have put a star field in as an optional background, but I said I would post my current knowledge of particles both 3D and 2D so here is my tutorial on 2D particles.

Like the previous tutorial I am going to create a simple trail particle emitter and as before I am going to start by creating my particle definition. So created a new source file called Particle2D.cs and again setup what I think will be the very basic elements you will want in a 2D particle, created a default constructor and a Draw method.

public class Particle2D
{
private Texture2D texture;
public Vector2 position;
public float rotation;
public float scale;
public Color color;
public int width;
public int height;
public Vector2 origin;
public int depth;

public Texture2D Texture
{
get { return texture; }
set
{
texture = value;
origin = new Vector2(texture.Width / 2, texture.Height / 2);
}
}

public Particle2D()
{
position = Vector2.Zero;
rotation = 0;
scale = 1;
color = Color.White;
origin = Vector2.Zero;
depth = 0;
}

public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture, new Rectangle((int)position.X, (int)position.Y, (int)(width * scale), (int)(height * scale)), new Rectangle(0, 0, texture.Width, texture.Height), color, rotation, origin, SpriteEffects.None, depth);
}
}

So what we have in this class is the texture to be used when drawing our particle, the position it is to be drawn at, it's rotation, scale, color, width and height. The origin is the point on the texture that is going to be used when drawing the default of this is 0,0, so the top left hand corner of the texture, I am going to place this in the centre of our particle, the depth is used when you want layers of texture on the screen, I have included this as you may want particles on different levels. Most of this stuff we probably wont use in this tutorial but thought I would show what can be done with a texture in 2D.

You will also notice a property in there called Texture, I have wrapped the texture field in a property as I will need to re calculate the origin if this texture changes, also in here if you were using something like my GGR implementation you may want to calculate the bounds of the particle in here to.

The constructor is pretty simple, it just sets all my fields to some default values. The draw method you will see is a little different, it is passed a SpriteBatch object and uses this to draw the particle. This is because you don't want to go doing a SpriteBatch.Begin() for each particle as this would be very expensive on FPS so we bundle them all into one draw call. In a later tutorial I am going to show how you can apply 2D pixel shader effects to your particles and using this method makes it easy to apply a single effect to many particles.
 
Now we have a particle definition we need an emitter, so I create another new source file in the project called ParticleEmitter2D.cs (I know, inventive names eh..). Like in the 3D tutorials I am going to create a trail emitter, so we need similar fields in our class but naturally for a 2D system.

public class ParticleEmitter2D : DrawableGameComponent
{
SpriteBatch spriteBatch;

public Particle2D[] particles;
public Vector2 position;
public int particleCount;
public Color particleColor;

int nextParticle = 0;
Vector2 targetPos;
Vector2 myLastpos;

public ParticleEmitter2D(Game game, int particleCount)
: base(game)
{
this.position = Vector2.Zero;
this.particleCount = particleCount;
particleColor = Color.White;
}
protected override void LoadContent()
{
position = new Vector2(Game.GraphicsDevice.Viewport.Width / 2, Game.GraphicsDevice.Viewport.Height / 2);
spriteBatch = new SpriteBatch(Game.GraphicsDevice);

Texture2D tmpTexture = Game.Content.Load<Texture2D>("Textures/particle");

LoadParticles(tmpTexture);

targetPos = position;
myLastpos = targetPos;

base.LoadContent();
}
public void LoadParticles(Texture2D tmpTexture)
{
particles = new Particle2D[particleCount];

for (int p = 0; p < particles.Length; p++)
{
particles[p] = new Particle2D();
particles[p].position = position;
particles[p].Texture = tmpTexture;
particles[p].color = new Color(particles[p].color.R, particles[p].color.G, particles[p].color.B, 0);
particles[p].width = 32;
particles[p].height = 32;
}
}
public override void Update(GameTime gameTime)
{
for (int p = 0; p < particles.Length; p++)
{
if (p == nextParticle && myLastpos != position)
{
particles[p].position = myLastpos;
particles[p].color = particleColor;
}
if (particles[p].position != position) // Particle is in use
{
particles[p].scale = 1 - (Vector2.Distance(particles[p].position, targetPos) / 400);
particles[p].color = new Color(particles[p].color.R, particles[p].color.G, particles[p].color.B, (byte)(particles[p].scale * 255));
}
}
nextParticle++;

if (nextParticle >= particles.Length)
nextParticle = 0;

myLastpos = targetPos;
targetPos = new Vector2((float)Math.Cos(gameTime.TotalGameTime.TotalSeconds) * 200, (float)Math.Sin(gameTime.TotalGameTime.TotalSeconds) * 200) + position;

base.Update(gameTime);
}
public override void Draw(GameTime gameTime)
{
spriteBatch.Begin(SpriteBlendMode.Additive, SpriteSortMode.Immediate, SaveStateMode.SaveState);

for (int p = 0; p < particles.Length; p++)
particles[p].Draw(spriteBatch);

spriteBatch.End();

base.Draw(gameTime);
}
}

As you can see there are a few differences here, first of all is the addition of a SpriteBatch object, this is going to be used to draw our batch of particles. As before we have an array of our particles, a position for the emitter as well as a particle count and color. We also have the three variables used to manage out trail. A simple constructor to set the emitters values.

In the LoadContent method I am setting the position of the emitter to the centre of my game screen, initialising my sprite batch object, loading a temporary Texture2D with the texture to e used in the particles, calling my LoadParticle method and then setting the trail management variables.
In my LoadParticles method I set the particle array size, then move through the array setting each particle up ready for use. You can see here I have set the particle width and height to 32.

The Update method is pretty much the same as before, setting the next particles position, color, scale and opacity.

The Draw method begins the sprite batch (note this is doing an Additive blend, for no blending set to None and for AlphaBlend, guess what, set it to normal AlphaBlend) and moves through the particle array and draws each particle and then ends the sprite batch draw.
So, now just as before all we need to do is add the component to the Game.Components collection and away it goes.

In the next tutorial I am going to show how you can bind these emitters to a game agent (ship/missile etc..), will also introduce a basic explosion particle effect to show how I instance an effect.

Correction, the next tutorial will show how to apply pixel shaders to your particle emitter, then I will follow this with binding the emitters to game agents.

You can download the solution project here.

No comments:

Post a Comment