8
\$\begingroup\$

I am creating a 2D XNA game and came across a tutorial on adding water effects (splashes) to an XNA game but after implementing it into my game I can't get it to scale down. Currently it takes up the entire screen.

The Water class looks like this

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

Then in the Game1.cs I have the following LoadContent method

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

In my update method I have the following (along with other code for the game, i am just showing water part)

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

Then in the Draw method I have the following (when the active enum is 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; 

My problem is this obviously takes up the entire screen. I realise why it takes up the entire screen but I can't figure out how to scale it down and set it on a fixed location in the game. If anyone could read through this and direct me towards how I would go about scaling this down successfully, I'd greatly appreciate it.

\$\endgroup\$
3
  • 4
    \$\begingroup\$ I wrote up a pretty detailed answer on a similar question before. The sample code might help you. Which part of your code is the problem? How have you tried fixing it? \$\endgroup\$ Commented Mar 24, 2013 at 17:36
  • \$\begingroup\$ I'm not sure...in the Water.cs I am trying to alter the scale as currently its being draw to cover the screen width. Everytime I try to alter this, I get out of bounds exceptions. \$\endgroup\$ Commented Mar 24, 2013 at 17:43
  • 1
    \$\begingroup\$ This question appears to be off-topic because it is far too localized. Essentially you're asking for someone to rewrite this tutorial code for you so it works in your game. \$\endgroup\$ Commented Jun 29, 2013 at 19:34

1 Answer 1

0
\$\begingroup\$

Well, there are two ways of doing this:

  1. Draw the entire Water object to a render target, then draw the final water target scaled and on the position you want. (this is the simplest way).
  2. Draw the Water in a constrained area. For this you'll need do to something like this:
    • Create the Water by specifying a Rectangle as target in the constructor.
    • Every reference to GraphicsDevice.Viewport should be replaced with the target rectangle
    • I assume that you will give the 'right' - real - position as the Splash method parameter (xPosition) so everything else will be fine when drawn.

PS: You said something about OutOfBoundsException. Please gives us more details if that's the case anymore.

\$\endgroup\$

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.