Implementando efeitos da água (salpicos) no jogo XNA 4.0 [fechado]

8

Estou criando um jogo 2D XNA e me deparei com um tutorial sobre como adicionar efeitos de água (salpicos) a um jogo XNA, mas depois de implementá-lo no meu jogo, não consigo reduzi-lo. Atualmente ocupa a tela inteira.

A classe Water fica assim

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();
    }
}

Então no Game1.cs eu tenho o seguinte 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);
        .
        .
        .
    }

No meu método de atualização, tenho o seguinte (junto com outro código do jogo, estou apenas mostrando a parte da água)

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;
        }

Então, no método Draw, tenho o seguinte (quando o enum ativo é 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;

Meu problema é que isso obviamente ocupa a tela inteira. Percebo por que ocupa a tela inteira, mas não consigo descobrir como reduzi-la e defini-la em um local fixo no jogo. Se alguém pudesse ler isso e me orientar sobre como eu reduziria isso com êxito, eu apreciaria muito.

UserBruiser
fonte
4
Eu escrevi uma resposta bastante detalhada sobre uma pergunta semelhante antes. O código de amostra pode ajudá-lo. Qual parte do seu código é o problema? Como você tentou consertá-lo?
Anko 24/03
Não tenho certeza ... no Water.cs, estou tentando alterar a escala, pois atualmente ele está sendo desenhado para cobrir a largura da tela. Sempre que tento alterar isso, saio das exceções fora dos limites.
UserBruiser
11
Esta pergunta parece estar fora do tópico porque é muito localizada. Essencialmente, você está pedindo para alguém reescrever este código de tutorial para que ele funcione no seu jogo.
MichaelHouse

Respostas:

0

Bem, existem duas maneiras de fazer isso:

  1. Desenhe o Waterobjeto inteiro em um alvo de renderização e, em seguida, desenhe o alvo final da água na escala e na posição desejada. (esta é a maneira mais simples).
  2. Desenhe o Waterem uma área restrita. Para isso, você precisará fazer algo assim:
    • Crie Waterespecificando a Rectanglecomo destino no construtor.
    • Toda referência a GraphicsDevice.Viewportdeve ser substituída pelo retângulo de destino
    • Suponho que você fornecerá a posição 'right' - real como o Splashparâmetro method ( xPosition), para que tudo fique bem quando desenhado.

PS: Você disse algo sobre OutOfBoundsException. Por favor, nos dê mais detalhes, se esse for o caso.

Timóteo
fonte