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);
}
}
{
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);
}
}
{
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.
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