I am trying to have an explosion appear when a player lands on a mine. I checked out the particle example on the XNA website but it seemed to over complicate it a lot. So any simpler neater explosion particle effect would be cool to be linked to ;)
But on to the problem, i can't even see the explosion though i cannot see what is going wrong. Here's my explosion class (a lot of this is from the particle example):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics.PackedVector;
namespace StayUp
{
struct ExplosionVertex
{
public Vector3 Position;
public Short2 Corner;
public Color Random;
public float Time;
public Vector3 Velocity;
public ExplosionVertex(Vector3 position, Short2 Corner, Vector3 velocity, Color random, float Time)
{
this.Position = position;
this.Corner = Corner;
this.Velocity = velocity;
this.Random = random;
this.Time = Time;
}
public static readonly VertexDeclaration VertexDeclaration = new VertexDeclaration
(
new VertexElement(0, VertexElementFormat.Short2,
VertexElementUsage.Position, 0),
new VertexElement(4, VertexElementFormat.Vector3,
VertexElementUsage.Position, 1),
new VertexElement(16, VertexElementFormat.Vector3,
VertexElementUsage.Normal, 0),
new VertexElement(28, VertexElementFormat.Color,
VertexElementUsage.Color, 0),
new VertexElement(32, VertexElementFormat.Single,
VertexElementUsage.TextureCoordinate, 0)
);
// Describe the size of this vertex structure.
public const int SizeInBytes = 36;
}
public class Explosion : DrawableGameComponent
{
#region Fields
string TextureName = "explosion";
int MaxParticles = 100;
TimeSpan Duration = TimeSpan.FromSeconds(2);
float DurationRandomness = 1;
float MinHorizontalVelocity = 0.02f;
float MaxHorizontalVelocity = 0.03f;
float MinVerticalVelocity = -0.02f;
float MaxVerticalVelocity = 0.02f;
float EndVelocity = 0;
Color MinColor = Color.DarkGray;
Color MaxColor = Color.Gray;
float MinRotateSpeed = -1;
float MaxRotateSpeed = 1;
float MinStartSize = 0.07f;
float MaxStartSize = 0.07f;
float MinEndSize = 0.7f;
float MaxEndSize = 1.4f;
BlendState blendState = BlendState.Additive;
// For loading the effect and particle texture.
ContentManager content;
// Custom effect for drawing particles. This computes the particle
// animation entirely in the vertex shader: no per-particle CPU work required!
Effect particleEffect;
// Shortcuts for accessing frequently changed effect parameters.
EffectParameter effectViewParameter;
EffectParameter effectProjectionParameter;
EffectParameter effectViewportScaleParameter;
EffectParameter effectTimeParameter;
// An array of particles, treated as a circular queue.
ExplosionVertex[] particles;
// A vertex buffer holding our particles. This contains the same data as
// the particles array, but copied across to where the GPU can access it.
DynamicVertexBuffer vertexBuffer;
// Index buffer turns sets of four vertices into particle quads (pairs of triangles).
IndexBuffer indexBuffer;
int firstActiveParticle;
int firstNewParticle;
int firstFreeParticle;
int firstRetiredParticle;
// Store the current time, in seconds.
float currentTime;
// Count how many times Draw has been called. This is used to know
// when it is safe to retire old particles back into the free list.
int drawCounter;
// Shared random number generator.
static Random random = new Random();
#endregion
#region Initialization
/// <summary>
/// Constructor.
/// </summary>
public Explosion(Game game, ContentManager content)
: base(game)
{
this.content = content;
}
/// <summary>
/// Initializes the component.
/// </summary>
public override void Initialize()
{
// Allocate the particle array, and fill in the Corner fields (which never change).
particles = new ExplosionVertex[MaxParticles * 4];
for (int i = 0; i < MaxParticles; i++)
{
particles[i * 4 + 0].Corner = new Short2(-1, -1);
particles[i * 4 + 1].Corner = new Short2(1, -1);
particles[i * 4 + 2].Corner = new Short2(1, 1);
particles[i * 4 + 3].Corner = new Short2(-1, 1);
}
base.Initialize();
}
/// <summary>
/// Loads graphics for the particle system.
/// </summary>
protected override void LoadContent()
{
LoadParticleEffect();
// Create a dynamic vertex buffer.
vertexBuffer = new DynamicVertexBuffer(GraphicsDevice, ExplosionVertex.VertexDeclaration,
MaxParticles * 4, BufferUsage.WriteOnly);
// Create and populate the index buffer.
ushort[] indices = new ushort[MaxParticles * 6];
for (int i = 0; i < MaxParticles; i++)
{
indices[i * 6 + 0] = (ushort)(i * 4 + 0);
indices[i * 6 + 1] = (ushort)(i * 4 + 1);
indices[i * 6 + 2] = (ushort)(i * 4 + 2);
indices[i * 6 + 3] = (ushort)(i * 4 + 0);
indices[i * 6 + 4] = (ushort)(i * 4 + 2);
indices[i * 6 + 5] = (ushort)(i * 4 + 3);
}
indexBuffer = new IndexBuffer(GraphicsDevice, typeof(ushort), indices.Length, BufferUsage.WriteOnly);
indexBuffer.SetData(indices);
}
/// <summary>
/// Helper for loading and initializing the particle effect.
/// </summary>
void LoadParticleEffect()
{
Effect effect = content.Load<Effect>("ParticleEffect");
// If we have several particle systems, the content manager will return
// a single shared effect instance to them all. But we want to preconfigure
// the effect with parameters that are specific to this particular
// particle system. By cloning the effect, we prevent one particle system
// from stomping over the parameter settings of another.
particleEffect = effect.Clone();
EffectParameterCollection parameters = particleEffect.Parameters;
// Look up shortcuts for parameters that change every frame.
effectViewParameter = parameters["View"];
effectProjectionParameter = parameters["Projection"];
effectViewportScaleParameter = parameters["ViewportScale"];
effectTimeParameter = parameters["CurrentTime"];
// Set the values of parameters that do not change.
parameters["Duration"].SetValue((float)Duration.TotalSeconds);
parameters["DurationRandomness"].SetValue(DurationRandomness);
parameters["Gravity"].SetValue(Vector3.Zero);
parameters["EndVelocity"].SetValue(EndVelocity);
parameters["MinColor"].SetValue(MinColor.ToVector4());
parameters["MaxColor"].SetValue(MaxColor.ToVector4());
parameters["RotateSpeed"].SetValue(
new Vector2(MinRotateSpeed, MaxRotateSpeed));
parameters["StartSize"].SetValue(
new Vector2(MinStartSize, MaxStartSize));
parameters["EndSize"].SetValue(
new Vector2(MinEndSize, MaxEndSize));
// Load the particle texture, and set it onto the effect.
Texture2D texture = content.Load<Texture2D>(TextureName);
parameters["Tex"].SetValue(texture);
}
#endregion
#region Update and Draw
/// <summary>
/// Updates the particle system.
/// </summary>
public override void Update(GameTime gameTime)
{
if (gameTime == null)
throw new ArgumentNullException("gameTime");
currentTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
RetireActiveParticles();
FreeRetiredParticles();
// If we let our timer go on increasing for ever, it would eventually
// run out of floating point precision, at which point the particles
// would render incorrectly. An easy way to prevent this is to notice
// that the time value doesn't matter when no particles are being drawn,
// so we can reset it back to zero any time the active queue is empty.
if (firstActiveParticle == firstFreeParticle)
currentTime = 0;
if (firstRetiredParticle == firstActiveParticle)
drawCounter = 0;
}
/// <summary>
/// Helper for checking when active particles have reached the end of
/// their life. It moves old particles from the active area of the queue
/// to the retired section.
/// </summary>
void RetireActiveParticles()
{
float particleDuration = (float)Duration.TotalSeconds;
while (firstActiveParticle != firstNewParticle)
{
// Is this particle old enough to retire?
// We multiply the active particle index by four, because each
// particle consists of a quad that is made up of four vertices.
float particleAge = currentTime - particles[firstActiveParticle * 4].Time;
if (particleAge < particleDuration)
break;
// Remember the time at which we retired this particle.
particles[firstActiveParticle * 4].Time = drawCounter;
// Move the particle from the active to the retired queue.
firstActiveParticle++;
if (firstActiveParticle >= MaxParticles)
firstActiveParticle = 0;
}
}
/// <summary>
/// Helper for checking when retired particles have been kept around long
/// enough that we can be sure the GPU is no longer using them. It moves
/// old particles from the retired area of the queue to the free section.
/// </summary>
void FreeRetiredParticles()
{
while (firstRetiredParticle != firstActiveParticle)
{
// Has this particle been unused long enough that
// the GPU is sure to be finished with it?
// We multiply the retired particle index by four, because each
// particle consists of a quad that is made up of four vertices.
int age = drawCounter - (int)particles[firstRetiredParticle * 4].Time;
// The GPU is never supposed to get more than 2 frames behind the CPU.
// We add 1 to that, just to be safe in case of buggy drivers that
// might bend the rules and let the GPU get further behind.
if (age < 3)
break;
// Move the particle from the retired to the free queue.
firstRetiredParticle++;
if (firstRetiredParticle >= MaxParticles)
firstRetiredParticle = 0;
}
}
/// <summary>
/// Draws the particle system.
/// </summary>
public override void Draw(GameTime gameTime)
{
GraphicsDevice device = GraphicsDevice;
// Restore the vertex buffer contents if the graphics device was lost.
if (vertexBuffer.IsContentLost)
{
vertexBuffer.SetData(particles);
}
// If there are any particles waiting in the newly added queue,
// we'd better upload them to the GPU ready for drawing.
if (firstNewParticle != firstFreeParticle)
{
AddNewParticlesToVertexBuffer();
}
// If there are any active particles, draw them now!
if (firstActiveParticle != firstFreeParticle)
{
device.BlendState = blendState;
device.DepthStencilState = DepthStencilState.DepthRead;
effectViewportScaleParameter.SetValue(new Vector2(0.5f / device.Viewport.AspectRatio, -0.5f));
effectTimeParameter.SetValue(currentTime);
device.SetVertexBuffer(vertexBuffer);
device.Indices = indexBuffer;
foreach (EffectPass pass in particleEffect.CurrentTechnique.Passes)
{
pass.Apply();
if (firstActiveParticle < firstFreeParticle)
{
device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0,
firstActiveParticle * 4, (firstFreeParticle - firstActiveParticle) * 4,
firstActiveParticle * 6, (firstFreeParticle - firstActiveParticle) * 2);
}
else
{
device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0,
firstActiveParticle * 4, (MaxParticles - firstActiveParticle) * 4,
firstActiveParticle * 6, (MaxParticles - firstActiveParticle) * 2);
if (firstFreeParticle > 0)
{
device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0,
0, firstFreeParticle * 4,
0, firstFreeParticle * 2);
}
}
}
device.DepthStencilState = DepthStencilState.Default;
}
drawCounter++;
}
/// <summary>
/// Helper for uploading new particles from our managed
/// array to the GPU vertex buffer.
/// </summary>
void AddNewParticlesToVertexBuffer()
{
int stride = ExplosionVertex.SizeInBytes;
if (firstNewParticle < firstFreeParticle)
{
// If the new particles are all in one consecutive range,
// we can upload them all in a single call.
vertexBuffer.SetData(firstNewParticle * stride * 4, particles,
firstNewParticle * 4,
(firstFreeParticle - firstNewParticle) * 4,
stride, SetDataOptions.NoOverwrite);
}
else
{
// If the new particle range wraps past the end of the queue
// back to the start, we must split them over two upload calls.
vertexBuffer.SetData(firstNewParticle * stride * 4, particles,
firstNewParticle * 4,
(MaxParticles - firstNewParticle) * 4,
stride, SetDataOptions.NoOverwrite);
if (firstFreeParticle > 0)
{
vertexBuffer.SetData(0, particles,
0, firstFreeParticle * 4,
stride, SetDataOptions.NoOverwrite);
}
}
// Move the particles we just uploaded from the new to the active queue.
firstNewParticle = firstFreeParticle;
}
#endregion
#region Public Methods
/// <summary>
/// Sets the camera view and projection matrices
/// that will be used to draw this particle system.
/// </summary>
public void SetCamera(Matrix view, Matrix projection)
{
effectViewParameter.SetValue(view);
effectProjectionParameter.SetValue(projection);
}
/// <summary>
/// Adds a new particle to the system.
/// </summary>
public void AddParticle(Vector3 position, Vector3 velocity)
{
// Figure out where in the circular queue to allocate the new particle.
int nextFreeParticle = firstFreeParticle + 1;
if (nextFreeParticle >= MaxParticles)
nextFreeParticle = 0;
// If there are no free particles, we just have to give up.
if (nextFreeParticle == firstRetiredParticle)
return;
// Add in some random amount of horizontal velocity.
float horizontalVelocity = MathHelper.Lerp(MinHorizontalVelocity,
MaxHorizontalVelocity,
(float)random.NextDouble());
double horizontalAngle = random.NextDouble() * MathHelper.TwoPi;
velocity.X += horizontalVelocity * (float)Math.Cos(horizontalAngle);
velocity.Z += horizontalVelocity * (float)Math.Sin(horizontalAngle);
// Add in some random amount of vertical velocity.
velocity.Y += MathHelper.Lerp(MinVerticalVelocity,
MaxVerticalVelocity,
(float)random.NextDouble());
// Choose four random control values. These will be used by the vertex
// shader to give each particle a different size, rotation, and color.
Color randomValues = new Color((byte)random.Next(255),
(byte)random.Next(255),
(byte)random.Next(255),
(byte)random.Next(255));
// Fill in the particle vertex structure.
for (int i = 0; i < 4; i++)
{
particles[firstFreeParticle * 4 + i].Position = position;
particles[firstFreeParticle * 4 + i].Velocity = velocity;
particles[firstFreeParticle * 4 + i].Random = randomValues;
particles[firstFreeParticle * 4 + i].Time = currentTime;
}
firstFreeParticle = nextFreeParticle;
}
#endregion
}
}
And here is my HLSL (also HLSL from example)
float4x4 View;
float4x4 Projection;
float2 ViewportScale;
float CurrentTime;
float Duration;
float DurationRandomness;
float3 Gravity;
float EndVelocity;
float4 MinColor;
float4 MaxColor;
float2 RotateSpeed;
float2 StartSize;
float2 EndSize;
texture Tex;
sampler TexSampler = sampler_state
{
Texture = <Tex>;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = Clamp;
AddressV = Clamp;
};
struct VertexShaderInput
{
float2 Corner : POSITION0;
float3 Position : POSITION1;
float3 Velocity : NORMAL0;
float4 Random : COLOR0;
float Time : TEXCOORD0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float4 Color : COLOR0;
float2 TextureCoordinate : COLOR1;
};
float4 ComputeParticlePosition(float3 position, float3 velocity, float age, float normalizedAge)
{
float startVelocity = length(velocity);
float endVelocity = startVelocity * EndVelocity;
float velocityIntegral = startVelocity * normalizedAge +
(endVelocity - startVelocity) * normalizedAge *
normalizedAge / 2;
position += normalize(velocity) * velocityIntegral * Duration;
position += Gravity * age * normalizedAge;
return mul(mul(float4(position, 1), View), Projection);
}
float ComputeParticleSize(float randomValue, float normalizedAge)
{
float startSize = lerp(StartSize.x, StartSize.y, randomValue);
float endSize = lerp(EndSize.x, EndSize.y, randomValue);
float size = lerp(startSize, endSize, normalizedAge);
return size * Projection._m11;
}
float4 ComputeParticleColor(float4 projectedPosition, float randomValue, float normalizedAge)
{
float4 color = lerp(MinColor, MaxColor, randomValue);
color.a *= normalizedAge * (1-normalizedAge) * (1-normalizedAge) * 6.7;
return color;
}
float2x2 ComputeParticleRotation(float randomValue, float age)
{
// Apply a random factor to make each particle rotate at a different speed.
float rotateSpeed = lerp(RotateSpeed.x, RotateSpeed.y, randomValue);
float rotation = rotateSpeed * age;
// Compute a 2x2 rotation matrix.
float c = cos(rotation);
float s = sin(rotation);
return float2x2(c, -s, s, c);
}
VertexShaderOutput ParticleVertexShader(VertexShaderInput input)
{
VertexShaderOutput output;
float age = CurrentTime - input.Time;
age *= 1 + input.Random.x * DurationRandomness;
float normalizedAge = saturate(age / Duration);
output.Position = ComputeParticlePosition(input.Position, input.Velocity, age, normalizedAge);
float size = ComputeParticleSize(input.Random.y, normalizedAge);
float2x2 rotation = ComputeParticleRotation(input.Random.w, age);
output.Position.xy += mul(input.Corner, rotation) * size * ViewportScale;
output.Color = ComputeParticleColor(output.Position, input.Random.z, normalizedAge);
output.TextureCoordinate = (input.Corner + 1) / 2;
return output;
}
float4 ParticlePixelShader(VertexShaderOutput input) : COLOR0
{
float4 output = tex2D(TexSampler, input.TextureCoordinate) * input.Color;
return output;
}
technique Particles
{
pass P0
{
VertexShader = compile vs_2_0 ParticleVertexShader();
PixelShader = compile ps_2_0 ParticlePixelShader();
}
}