Issues with 2D tile-based Sidescroller Water

Started by
12 comments, last by smorgasbord 11 years, 5 months ago
If its a flat surface, there will be pieces moving around as long as you use integers or similiar for water level.

The moving can get fixed by writing to a different grid than you read from (so it doesnt change while updating)

But if you want a smooth surface you need to use floats for water level OR not display water amounts equal to the minimum water amount.


So, i suggest making your cide not render blocks with exactly 1 unit of water, which hides the moving water pieces so there will be no moving thingies on top AND no thingies on top (of course :P)

o3o

Advertisement
But if you want a smooth surface you need to use floats for water level OR not display water amounts equal to the minimum water amount.


Hey again. So I tried implementing your suggestions, by not drawing tiles with the minimum (or close to) amount of water.. it helped, but it still wasn't there. I also tried changing all the bytes to floats (which gave me a close to 20mb size increase on my large world save files tongue.png ) this made the process much smoother, but I still ended up with uneven surfaces. I am sure if I continued I would've found a fitting solution based on your ideas...

Last night however, I found this article: http://w-shadow.com/...uid-simulation/ - Basically you create water using the Cellular Automata method. I don't fully understand the whole concept of Cellular Automata, but at least for my needs his explanations are good enough.
It was actually your suggestion about using floats, that made me decide to go with this method.
I read the article a few times, until I had a better understanding of what he was doing- afterwards I went down to the coding, basically following his code and changing it to my needs. The results are... decent.
The water now evens out eventually, but as with the other approach, this also takes some time, albeit a bit faster. This method also includes presure, at least a little bit. For example, if we have a U shaped pipe and I pour water into the left side of the pipe there water would, given enough water, start to fill up the right end of the pipe.

I am hoping in the future that I will be able to make this somewhat faster but for now it suits my needs. I will post a video in this thread when I get the graphics sorted proberly, showing what this method allows, and also the downsides of it.

If anyone wants to see the code (at least the current code) here it is:


		private const float MAX_WATER_LEVEL = 1.0f;
		private const float MAX_COMPRESS = 0.02f;
		private const float MIN_WATER_LEVEL = 0.0001f;
		private const float MIN_FLOW = 0.01f;
		private const float MAX_SPEED = 1f;
		private void UpdateWater(GameTime gameTime)
		{
			float flow = 0;
			float remaining_mass;
			for (int x = (int)(_pc.Position.X / StaticTileInfo.Size.X) - UPDATE_RADIUS_X; x < (int)(_pc.Position.X / StaticTileInfo.Size.X) + UPDATE_RADIUS_X; x++)
			{
				for (int y = (int)(_pc.Position.Y / StaticTileInfo.Size.Y) - UPDATE_RADIUS_Y; y < (int)(_pc.Position.Y / StaticTileInfo.Size.Y) + UPDATE_RADIUS_Y; y++)
				{
					if (x - 1 < 0 || y - 1 < 0 || x + 1 > tg.World.GetLength(0) || y + 1 > tg.World.GetLength(1))
						continue;
					//Ignore blocks with little to no water.
					if (tg.World[x, y].Collision == TileCollision.Impassable || tg.World[x,y].WaterLevel < MIN_WATER_LEVEL)
						continue;
					//Push flow
					flow = 0;
					remaining_mass = tg.World[x, y].WaterLevel;
					if (remaining_mass <= 0)
						continue;
				  
					//The tile below this one
					if (tg.World[x, y + 1].Collision != TileCollision.Impassable)
					{
						flow = GetVerticalAdjacentMaxWater(remaining_mass + tg.World[x, y + 1].WaterLevel) - tg.World[x, y + 1].WaterLevel;
						if(flow > MIN_FLOW)
							flow *= .5f; //Smooths the flow
						flow = MathHelper.Clamp(flow, 0, MathHelper.Min(MAX_SPEED, remaining_mass));
						tg.World[x, y].WaterLevel -= flow;
						tg.World[x, y + 1].WaterLevel += flow;
						remaining_mass -= flow;
					}
					if (remaining_mass <= 0)
						continue;
					//Left
					if (tg.World[x - 1, y].Collision != TileCollision.Impassable)
					{
						//Equalize amount of water in this block and the neighbour
						flow = (tg.World[x, y].WaterLevel - tg.World[x - 1, y].WaterLevel) / 4;
						if (flow > MIN_FLOW)
							flow *= 0.5f;
						flow = MathHelper.Clamp(flow, 0, remaining_mass);
						tg.World[x, y].WaterLevel -= flow;
						tg.World[x - 1, y].WaterLevel += flow;
						remaining_mass -= flow;
					}
					if (remaining_mass <= 0)
						continue;
					//Right
					if (tg.World[x + 1, y].Collision != TileCollision.Impassable)
					{
						//Equalize amount of water in this block and the neighbour
						flow = (tg.World[x, y].WaterLevel - tg.World[x + 1, y].WaterLevel) / 4;
						if (flow > MIN_FLOW)
							flow *= 0.5f;
						flow = MathHelper.Clamp(flow, 0, remaining_mass);
						tg.World[x, y].WaterLevel -= flow;
						tg.World[x + 1, y].WaterLevel += flow;
						remaining_mass -= flow;
					}
					if (remaining_mass <= 0)
						continue;
					//Up - Only compressed water tiles flow upwards.
					if (tg.World[x, y - 1].Collision != TileCollision.Impassable)
					{
						flow = remaining_mass - GetVerticalAdjacentMaxWater(remaining_mass + tg.World[x, y - 1].WaterLevel);
						if (flow > MIN_FLOW)
							flow *= 0.5f;
						flow = MathHelper.Clamp(flow, 0, MathHelper.Min(MAX_SPEED, remaining_mass));
						tg.World[x, y].WaterLevel -= flow;
						tg.World[x, y - 1].WaterLevel += flow;
						remaining_mass -= flow;
					}
				}
			}
		}
		private float GetVerticalAdjacentMaxWater(float total_mass)
		{
			if (total_mass <= 1)
				return 1;
			else if (total_mass < 2 * MAX_WATER_LEVEL + MAX_COMPRESS)
				return (MAX_WATER_LEVEL * MAX_WATER_LEVEL + total_mass * MAX_COMPRESS) / (MAX_WATER_LEVEL + MAX_COMPRESS);
			else
				return (total_mass + MAX_COMPRESS) / 2;
		}


Keep in mind this code is specifically made for my game, but I believe that it should be fairly easy to understand what to change to make it suit your needs. Notice that in my game minus on the Y axis is up, plus is down. In the article I linked, this is the other way around.
I thank you guys for all the help, and I hope that my code here might help someone in the right direction sometime. If anyone has ideas for speed optimizations, please do post them, and I'll update the code accordingly.

Thank you for reading smile.png

Check out the game I am making here - http://www.youtube.com/user/NasarethMekuri

As I promised I'd throw the video with my results here. There's still some tweaking to do, especially with the drawing of the water part.. And some optimization on the horisontal spread would definitely improve this.

Anyways I just wanted to thank you guys for your help again, and post the video.

If you're not interested in the other stuff in the video just fastforward to around 0:55.

[media]
[/media]

Enjoy!

Check out the game I am making here - http://www.youtube.com/user/NasarethMekuri

Looks good! Wow.
~ Not that I really have a clue

This topic is closed to new replies.

Advertisement