Implementación de efectos de agua (salpicaduras) en el juego XNA 4.0 [cerrado]

8

Estoy creando un juego XNA 2D y encontré un tutorial sobre cómo agregar efectos de agua (salpicaduras) a un juego XNA, pero después de implementarlo en mi juego no puedo reducirlo. Actualmente ocupa toda la pantalla.

La clase de agua se ve así

class Water
{
    struct WaterColumn
    {
        public float TargetHeight;
        public float Height;
        public float Speed;

        public void Update(float dampening, float tension)
        {
            float x = TargetHeight - Height;
            Speed += tension * x - Speed * dampening;
            Height += Speed;
        }
    }

    PrimitiveBatch pb;
    WaterColumn[] columns = new WaterColumn[201];
    static Random rand = new Random();

    public float Tension = 0.025f;
    public float Dampening = 0.025f;
    public float Spread = 0.25f;

    RenderTarget2D metaballTarget, particlesTarget;
    SpriteBatch spriteBatch;
    AlphaTestEffect alphaTest;
    Texture2D particleTexture;

    private float Scale { get { return spriteBatch.GraphicsDevice.Viewport.Width / (columns.Length - 1f); } }

    List<Particle> particles = new List<Particle>();
    class Particle
    {
        public Vector2 Position;
        public Vector2 Velocity;
        public float Orientation;

        public Particle(Vector2 position, Vector2 velocity, float orientation)
        {
            Position = position;
            Velocity = velocity;
            Orientation = orientation;
        }
    }

    public Water(GraphicsDevice device, Texture2D particleTexture)
    {
        pb = new PrimitiveBatch(device);
        this.particleTexture = particleTexture;
        spriteBatch = new SpriteBatch(device);
        metaballTarget = new RenderTarget2D(device, device.Viewport.Width, device.Viewport.Height);
        particlesTarget = new RenderTarget2D(device, device.Viewport.Width, device.Viewport.Height);
        alphaTest = new AlphaTestEffect(device);
        alphaTest.ReferenceAlpha = 175;

        var view = device.Viewport;
        alphaTest.Projection = Matrix.CreateTranslation(-0.5f, -0.5f, 0) *
            Matrix.CreateOrthographicOffCenter(0, view.Width, view.Height, 0, 0, 1);

        for (int i = 0; i < columns.Length; i++)
        {
            columns[i] = new WaterColumn()
            {
                Height = 240,
                TargetHeight = 240,
                Speed = 0
            };
        }
    }

    // Returns the height of the water at a given x coordinate.
    public float GetHeight(float x)
    {
        if (x < 0 || x > 800)
            return 240;

        return columns[(int)(x / Scale)].Height;
    }

    void UpdateParticle(Particle particle)
    {
        const float Gravity = 0.3f;
        particle.Velocity.Y += Gravity;
        particle.Position += particle.Velocity;
        particle.Orientation = GetAngle(particle.Velocity);
    }

    public void Splash(float xPosition, float speed)
    {
        int index = (int)MathHelper.Clamp(xPosition / Scale, 0, columns.Length - 1);
        for (int i = Math.Max(0, index - 0); i < Math.Min(columns.Length - 1, index + 1); i++)
            columns[index].Speed = speed;

        CreateSplashParticles(xPosition, speed);
    }

    private void CreateSplashParticles(float xPosition, float speed)
    {
        float y = GetHeight(xPosition);

        if (speed > 120)
        {
            for (int i = 0; i < speed / 8; i++)
            {
                Vector2 pos = new Vector2(xPosition, y) + GetRandomVector2(40);
                Vector2 vel = FromPolar(MathHelper.ToRadians(GetRandomFloat(-150, -30)), GetRandomFloat(0, 0.5f * (float)Math.Sqrt(speed)));
                CreateParticle(pos, vel);
            }
        }
    }

    private void CreateParticle(Vector2 pos, Vector2 velocity)
    {
        particles.Add(new Particle(pos, velocity, 0));
    }

    private Vector2 FromPolar(float angle, float magnitude)
    {
        return magnitude * new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
    }

    private float GetRandomFloat(float min, float max)
    {
        return (float)rand.NextDouble() * (max - min) + min;
    }

    private Vector2 GetRandomVector2(float maxLength)
    {
        return FromPolar(GetRandomFloat(-MathHelper.Pi, MathHelper.Pi), GetRandomFloat(0, maxLength));
    }

    private float GetAngle(Vector2 vector)
    {
        return (float)Math.Atan2(vector.Y, vector.X);
    }

    public void Update()
    {
        for (int i = 0; i < columns.Length; i++)
            columns[i].Update(Dampening, Tension);

        float[] lDeltas = new float[columns.Length];
        float[] rDeltas = new float[columns.Length];

        // do some passes where columns pull on their neighbours
        for (int j = 0; j < 8; j++)
        {
            for (int i = 0; i < columns.Length; i++)
            {
                if (i > 0)
                {
                    lDeltas[i] = Spread * (columns[i].Height - columns[i - 1].Height);
                    columns[i - 1].Speed += lDeltas[i];
                }
                if (i < columns.Length - 1)
                {
                    rDeltas[i] = Spread * (columns[i].Height - columns[i + 1].Height);
                    columns[i + 1].Speed += rDeltas[i];
                }
            }

            for (int i = 0; i < columns.Length; i++)
            {
                if (i > 0)
                    columns[i - 1].Height += lDeltas[i];
                if (i < columns.Length - 1)
                    columns[i + 1].Height += rDeltas[i];
            }
        }

        foreach (var particle in particles)
            UpdateParticle(particle);

        particles = particles.Where(x => x.Position.X >= 0 && x.Position.X <= 800 && x.Position.Y - 5 <= GetHeight(x.Position.X)).ToList();
    }

    public void DrawToRenderTargets()
    {
        GraphicsDevice device = spriteBatch.GraphicsDevice;
        device.SetRenderTarget(metaballTarget);
        device.Clear(Color.Transparent);

        // draw particles to the metaball render target
        spriteBatch.Begin(0, BlendState.Additive);
        foreach (var particle in particles)
        {
            Vector2 origin = new Vector2(particleTexture.Width, particleTexture.Height) / 2f;
            spriteBatch.Draw(particleTexture, particle.Position, null, Color.White, particle.Orientation, origin, 2f, 0, 0);
        }
        spriteBatch.End();

        // draw a gradient above the water so the metaballs will fuse with the water's surface.
        pb.Begin(PrimitiveType.TriangleList);

        const float thickness = 20;
        float scale = Scale;
        for (int i = 1; i < columns.Length; i++)
        {
            Vector2 p1 = new Vector2((i - 1) * scale, columns[i - 1].Height);
            Vector2 p2 = new Vector2(i * scale, columns[i].Height);
            Vector2 p3 = new Vector2(p1.X, p1.Y - thickness);
            Vector2 p4 = new Vector2(p2.X, p2.Y - thickness);

            pb.AddVertex(p2, Color.White);
            pb.AddVertex(p1, Color.White);
            pb.AddVertex(p3, Color.Transparent);

            pb.AddVertex(p3, Color.Transparent);
            pb.AddVertex(p4, Color.Transparent);
            pb.AddVertex(p2, Color.White);
        }

        pb.End();

        // save the results in another render target (in particlesTarget)
        device.SetRenderTarget(particlesTarget);
        device.Clear(Color.Transparent);
        spriteBatch.Begin(0, null, null, null, null, alphaTest);
        spriteBatch.Draw(metaballTarget, Vector2.Zero, Color.White);
        spriteBatch.End();

        // switch back to drawing to the backbuffer.
        device.SetRenderTarget(null);
    }

    public void Draw()
    {
        Color lightBlue = new Color(0.2f, 0.5f, 1f);

        // draw the particles 3 times to create a bevelling effect
        spriteBatch.Begin();
        spriteBatch.Draw(particlesTarget, -Vector2.One, new Color(0.8f, 0.8f, 1f));
        spriteBatch.Draw(particlesTarget, Vector2.One, new Color(0f, 0f, 0.2f));
        spriteBatch.Draw(particlesTarget, Vector2.Zero, lightBlue);
        spriteBatch.End();

        // draw the waves
        pb.Begin(PrimitiveType.TriangleList);
        Color midnightBlue = new Color(0, 15, 40) * 0.9f;
        lightBlue *= 0.8f;

        float bottom = spriteBatch.GraphicsDevice.Viewport.Height;
        float scale = Scale;
        for (int i = 1; i < columns.Length; i++)
        {
            Vector2 p1 = new Vector2((i - 1) * scale, columns[i - 1].Height);
            Vector2 p2 = new Vector2(i * scale, columns[i].Height);
            Vector2 p3 = new Vector2(p2.X, bottom);
            Vector2 p4 = new Vector2(p1.X, bottom);

            pb.AddVertex(p1, lightBlue);
            pb.AddVertex(p2, lightBlue);
            pb.AddVertex(p3, midnightBlue);

            pb.AddVertex(p1, lightBlue);
            pb.AddVertex(p3, midnightBlue);
            pb.AddVertex(p4, midnightBlue);
        }

        pb.End();
    }
}

Luego, en Game1.cs tengo el siguiente método LoadContent

protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);
        font = Content.Load<SpriteFont>("Font");
        particleImage = Content.Load<Texture2D>("metaparticle");
        backgroundImage = Content.Load<Texture2D>("sky");
        rockImage = Content.Load<Texture2D>("rock");
        water = new Water(GraphicsDevice, particleImage);
        .
        .
        .
    }

En mi método de actualización tengo lo siguiente (junto con otro código para el juego, solo estoy mostrando parte del agua)

protected override void Update(GameTime gameTime)
    {
        lastKeyState = keyState;
        keyState = Keyboard.GetState();
        lastMouseState = mouseState;
        mouseState = Mouse.GetState();

        water.Update();

        Vector2 mousePos = new Vector2(mouseState.X, mouseState.Y);
        // if the user clicked down, create a rock.
        if (lastMouseState.LeftButton == ButtonState.Released && mouseState.LeftButton == ButtonState.Pressed)
        {
            rock = new Rock
            {
                Position = mousePos,
                Velocity = (mousePos - new Vector2(lastMouseState.X, lastMouseState.Y)) / 5f
            };
        }

        // update the rock if it exists
        if (rock != null)
        {
            if (rock.Position.Y < 240 && rock.Position.Y + rock.Velocity.Y >= 240)
                water.Splash(rock.Position.X, rock.Velocity.Y * rock.Velocity.Y * 5);

            rock.Update(water);

            if (rock.Position.Y > GraphicsDevice.Viewport.Height + rockImage.Height)
                rock = null;
        }

Luego, en el método Draw, tengo lo siguiente (cuando la enumeración activa es InGame)

case ActiveScreen.InGame:

                water.DrawToRenderTargets();
                level.Draw(gameTime, spriteBatch);
                DrawHud();
                spriteBatch.Begin();
                spriteBatch.Draw(backgroundImage, Vector2.Zero, Color.White);

                if (rock != null)
                    rock.Draw(spriteBatch, rockImage);
                spriteBatch.End();
                water.Draw();

                break;

Mi problema es que, obviamente, ocupa toda la pantalla. Me doy cuenta de por qué ocupa toda la pantalla, pero no puedo entender cómo reducirlo y configurarlo en una ubicación fija en el juego. Si alguien pudiera leer esto y orientarme sobre cómo reduciría esto con éxito, lo agradecería enormemente.

UserBruiser
fuente
44
Escribí una respuesta bastante detallada sobre una pregunta similar antes. El código de muestra puede ayudarte. ¿Qué parte de tu código es el problema? ¿Cómo has intentado arreglarlo?
Anko
No estoy seguro ... en Water.cs estoy tratando de alterar la escala ya que actualmente se está dibujando para cubrir el ancho de la pantalla. Cada vez que trato de alterar esto, salgo de las excepciones.
UserBruiser
1
Esta pregunta parece estar fuera de tema porque está demasiado localizada. Básicamente, estás pidiendo a alguien que reescriba este código tutorial para que funcione en tu juego.
MichaelHouse

Respuestas:

0

Bueno, hay dos formas de hacer esto:

  1. Dibuje todo el Waterobjeto en un objetivo de renderizado, luego dibuje el objetivo de agua final a escala y en la posición que desee. (esta es la forma más simple).
  2. Dibuje el Wateren un área restringida. Para esto necesitarás hacer algo como esto:
    • Cree el Watermediante la especificación de un Rectangleobjetivo como en el constructor.
    • Cada referencia a GraphicsDevice.Viewportdebe ser reemplazada por el rectángulo objetivo
    • Supongo que dará la posición 'correcta' - real - como el Splashparámetro del método ( xPosition), por lo que todo lo demás estará bien cuando se dibuje.

PD: Dijiste algo al respecto OutOfBoundsException. Por favor, danos más detalles si ese es el caso.

Timotei
fuente