Search code examples
c#3dxna-4.0

XNA Updating the position of vertices in a VertexBuffer on update


I'm trying to create a particle system in a game I'm making in XNA 4.0. The particles are rendered on in the world where the emitter is initialized. The problem is that I want to update the position of the particle on Update() but it won't work, the particles are rendered but only in the center of the world at position 0, 0, 0.

Each particle consists of a group of vertices with a texture on it.

Can anyone help me out? Here is the class for the particle emitter:

public class ParticleEmitter : RenderableGameObject
{
    VertexBuffer vertexBuffer;
    VertexDeclaration vertexDeclaration;
    Effect bbEffect;

    VertexPositionTexture[] vertices;

    Texture2D texture;
    int noVertices = 0;

    struct Particle
    {
        public Vector3 position;
    }
    Particle[] particles = new Particle[5];

    public ParticleEmitter(Game1 game)
        : base(game)
    {
        SetupParticles();

        SetupVertices();

        bbEffect = game.Content.Load<Effect>("Effects/Particle");
        texture = game.Content.Load<Texture2D>("Textures/fire");
    }

    private void SetupVertices()
    {
        int vertexIndex = 0;
        vertices = new VertexPositionTexture[particles.Length * 6];

        for (int i = 0; i < particles.Length; ++i)
        {
            vertices[vertexIndex++] = new VertexPositionTexture(particles[i].position, new Vector2(0, 0));
            vertices[vertexIndex++] = new VertexPositionTexture(particles[i].position, new Vector2(1, 0));
            vertices[vertexIndex++] = new VertexPositionTexture(particles[i].position, new Vector2(1, 1));

            vertices[vertexIndex++] = new VertexPositionTexture(particles[i].position, new Vector2(0, 0));
            vertices[vertexIndex++] = new VertexPositionTexture(particles[i].position, new Vector2(1, 1));
            vertices[vertexIndex++] = new VertexPositionTexture(particles[i].position, new Vector2(0, 1));
        }

        noVertices = vertexIndex;

        vertexBuffer = new VertexBuffer(game.GraphicsDevice, typeof(VertexPositionTexture), vertices.Length, BufferUsage.WriteOnly);
        vertexBuffer.SetData(vertices);
        vertexDeclaration = new VertexDeclaration(VertexPositionTexture.VertexDeclaration.GetVertexElements());
    }

    private void SetupParticles()
    {
        for (int i = 0; i < particles.Length; ++i)
        {
            particles[i] = new Particle();
            particles[i].position = Position * i;
        }
    }

    private void UpdateVertices()
    {
        for (int i = 0; i < particles.Length; ++i)
        {
            vertices[i] = new VertexPositionTexture(particles[i].position, new Vector2(0, 0));
            vertices[i] = new VertexPositionTexture(particles[i].position, new Vector2(1, 0));
            vertices[i] = new VertexPositionTexture(particles[i].position, new Vector2(1, 1));

            vertices[i] = new VertexPositionTexture(particles[i].position, new Vector2(0, 0));
            vertices[i] = new VertexPositionTexture(particles[i].position, new Vector2(1, 1));
            vertices[i] = new VertexPositionTexture(particles[i].position, new Vector2(0, 1));
        }

        game.GraphicsDevice.SetVertexBuffer(null);
        vertexBuffer.SetData(vertices);
    }

    private void UpdateParticles()
    {
        for (int i = 0; i < particles.Length; ++i)
        {
            particles[i].position = Position;
        }
    }

    public override void Update(GameTime gameTime)
    {
        UpdateParticles();
        UpdateVertices();

        base.Update(gameTime);
    }

    public override void Draw()
    {
        DrawParticles(game.camera.viewMatrix);
    }

    private void DrawParticles(Matrix currentViewMatrix)
    {
        bbEffect.CurrentTechnique = bbEffect.Techniques["CylBillboard"];
        bbEffect.Parameters["xWorld"].SetValue(Matrix.Identity);
        bbEffect.Parameters["xView"].SetValue(currentViewMatrix);
        bbEffect.Parameters["xProjection"].SetValue(game.camera.projectionMatrix);
        bbEffect.Parameters["xCamPos"].SetValue(game.camera.Position);
        bbEffect.Parameters["xAllowedRotDir"].SetValue(new Vector3(0, 1, 0));
        bbEffect.Parameters["xBillboardTexture"].SetValue(texture);

        foreach (EffectPass pass in bbEffect.CurrentTechnique.Passes)
        {
            game.GraphicsDevice.BlendState = BlendState.AlphaBlend;

            pass.Apply();
            game.GraphicsDevice.SetVertexBuffer(vertexBuffer);
            int noTriangles = noVertices / 3;
            game.GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, noTriangles);
        }
    }
}

Solution

  • You're not actually changing the particle positions at all, in this method:

    private void UpdateParticles()
    {
        for (int i = 0; i < particles.Length; ++i)
        {
            particles[i].position = Position;
        }
    }
    

    You're setting every particle to the same position, presumably the origin.

    What you probably want to do is something like add a velocity to each particle:

    struct Particle
    {
        public Vector3 position;
        public Vector3 velocity;
    }
    

    And then initialise each particle with a random velocity:

    // Pass a shared, per-thread instance of Random into this method
    private void SetupParticles(Random random)
    {
        for (int i = 0; i < particles.Length; ++i)
        {
            particles[i] = new Particle() {
                position = Vector.Zero,
                velocity = new Vector3((float)(random.NextDouble() * 2 - 1),
                                       (float)(random.NextDouble() * 2 - 1),
                                       (float)(random.NextDouble() * 2 - 1))
            };
        }
    }
    

    And then update them according to some physical model (ie: velocity and acceleration due to gravity, over time).

    Vector3 gravity = new Vector3(0, -9.8f, 0);
    
    private void UpdateParticles(float time)
    {
        for (int i = 0; i < particles.Length; ++i)
        {
            particles[i].velocity += gravity * time;
            particles[i].position += particles[i].velocity * time;
        }
    }
    

    (Note: all code in this answer was written by hand - beware syntax errors, etc.)