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();
}
}
{
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;
}
}
{
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!!!
download link is broken
ReplyDeleteHi 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.
DeleteI should have this link fixed in a day or so after writing this response.