Tuesday, 3 July 2012

2D Particle Tutorial V



In this tutorial I am going to bind the emitter to a game agent, now you will have to forgive me as I have been very lazy, I have taken textures from the Creators Club particle example as well as some of the facing methods, have not bothered to write a new agent class, rather just derived it from the Particle2D class and it's texture I have taken from a game I was writing for this years DBP warm up challenge and never finished. BUT, having said all that I think it should give you enough to see how you can bolt the emitters into your own game agents.

Where to start, well as I want to setup and draw the emitters from my Agent, I have removed the base class from it and made it's protected methods public. In doing so I have had to add a Game object to the class, and as I wanted to have two emitters per agent, one for smoke and one for fire and one to have a larger particle than the other so introduced a baseScale. Also added a texture asset parameter to the constructor as well as the new baseScale. I have also replaced the targetPos with a Particle2D object so we can bind to the agent, this also has been added to the constructor. So save me confusing you any more, here is the class as it stands now.

public class ParticleEmitter2D
{
SpriteBatch spriteBatch;

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

int nextParticle = 0;
Particle2D targetObj;
Vector2 myLastpos;

string myTextureAsset;
float baseScale = 1;

Game Game;

public ParticleEmitter2D(Game game, int particleCount, string textureAsset, float scale, Particle2D target)
{
Game = game;
this.position = Vector2.Zero;
this.particleCount = particleCount;
particleColor = Color.White;
myTextureAsset = textureAsset;
baseScale = scale;

targetObj = target;
}
public void LoadContent()
{
spriteBatch = new SpriteBatch(Game.GraphicsDevice);

Texture2D tmpTexture = Game.Content.Load<Texture2D>(myTextureAsset);

LoadParticles(tmpTexture);

myLastpos = targetObj.position;

}
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].scale = baseScale;
particles[p].width = (int)(32 * baseScale);
particles[p].height = (int)(32 * baseScale);
}
}
public 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, targetObj.position) / 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 = targetObj.position;
}
public void Draw(GameTime gameTime)
{
spriteBatch.Begin(SpriteBlendMode.Additive, SpriteSortMode.Immediate, SaveStateMode.SaveState);

for (int p = 0; p < particles.Length; p++)
{
if (particles[p].position != targetObj.position)
particles[p].Draw(spriteBatch);
}

spriteBatch.End();
}
}


Now we create our Agent class, this is derived from the Particle2D class, I add a Game object, a velocity and two ParticleEmitter2Ds, one for smoke and one for fire. A reasonably simple constructor to set the base values, a LoadContent to, well load the content, an Update method where I can code the behaviour of the agent, a Draw method and then three methods acquired from the Creators Club samples. This gives me a class that looks like this

public class Agent : Particle2D
{
Game game;
public Vector2 velocity;

ParticleEmitter2D emitterFire;
ParticleEmitter2D emitterSmoke;


public Agent(Game game, Vector2 velocity)
{
this.game = game;
scale = 1;
height = 42;
width = 42;

this.velocity = velocity;

emitterFire = new ParticleEmitter2D(game, 75, "Textures/fire", 1, this);
emitterSmoke = new ParticleEmitter2D(game, 75, "Textures/smoke", 1.5f, this);

}

public void LoadContent()
{
emitterFire.LoadContent();
emitterSmoke.LoadContent();
Random rnd = new Random(DateTime.Now.Millisecond);
position = new Vector2(game.GraphicsDevice.Viewport.Width * (float)rnd.NextDouble(), game.GraphicsDevice.Viewport.Height * (float)rnd.NextDouble());
}

public void Update(GameTime gameTime)
{
position += velocity;

if (velocity != Vector2.Zero)
LookAt(position + velocity, 1f);

if (position.X + width / 2 > game.GraphicsDevice.Viewport.Width)
velocity = Vector2.Reflect(velocity, new Vector2(-1, 0));

if (position.Y + height / 2 > game.GraphicsDevice.Viewport.Height)
velocity = Vector2.Reflect(velocity, new Vector2(0, -1));

if (position.X - width / 2 <= 0)
velocity = Vector2.Reflect(velocity, new Vector2(1, 0));

if (position.Y - height / 2 <= 0)
velocity = Vector2.Reflect(velocity, new Vector2(0, 1));

emitterFire.Update(gameTime);
emitterSmoke.Update(gameTime);
}

public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
emitterFire.Draw(gameTime);
emitterSmoke.Draw(gameTime);
spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);
base.Draw(spriteBatch);
spriteBatch.End();
}

public void LookAt(Vector2 target, float speed)
{
rotation = TurnToFace(position, 2 * target - position, rotation, 1f);
}

private static float TurnToFace(Vector2 position, Vector2 faceThis, float currentAngle, float turnSpeed)
{

float x = faceThis.X - position.X;
float y = faceThis.Y - position.Y;

float desiredAngle = (float)Math.Atan2(y, x);

float difference = WrapAngle(desiredAngle - currentAngle);

difference = MathHelper.Clamp(difference, -turnSpeed, turnSpeed);

return WrapAngle(currentAngle + difference);
}
private static float WrapAngle(float radians)
{
while (radians < -MathHelper.Pi)
{
radians += MathHelper.TwoPi;
}
while (radians > MathHelper.Pi)
{
radians -= MathHelper.TwoPi;
}
return radians;
}
}


So with that all in place I create an Agent array and in each key Game method (LoadContent,Update and Draw) I loop through my agents and call the respective method.

Now the code around the whole Agent and related emitters is not great, best thing would be to move ALL this into a single draw call, as I said at the top, I have been very lazy with this tutorial. I have not even done a 3D version, but the principles will carry over just the same.

Now what we end up with is the clip you see at the top of this post, yes that's right ROCKET SHEEP!!!

Solution project can be downloaded here.

2 comments:

  1. Replies
    1. Hi max, almost all the links here are not working as this is an archive from another blog that no longer exists. I do still have the dld's though, and I am fixing the links as folk find them.

      I should have this link fixed in a day or so after writing this response.

      Delete