Jump to content

  • Log In with Google      Sign In   
  • Create Account


Issues with 2D tile-based Sidescroller Water


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
13 replies to this topic

#1 Mekuri   Members   -  Reputation: 299

Like
0Likes
Like

Posted 23 October 2012 - 02:25 PM

Hey there. On my journey through my sidescroller game I've come to the point where I need to add water. I started out today, and very quickly ended up with decent water. There was an issue though- When the water is finished updating (which might take some time depending on the pool size) the water is rarely flat on top. Let me illustrate

What I would like is the water surface to be like this:

------------------------------ <--- surface

But what I might get is something like this:

---______________ <--- Undesirable surface.

Basically the surface is leaning in either direction (thought it appears to be leaning to the right mostly)
I've spend about eight hours working on this, and I am no closer to a good solution.

While searching the web I found this method for handling water:

1.From bottom to top, if a cell which contains water is over a cell which could contain water but is not full, move as much water as can be moved from the upper cell to the lower one.
2.From bottom to top, for each cell which contains water, if the cell below cannot accommodate any more water, transfer 1/4 of the 'starting amount' of water to each side if there's room.

One full tile of water contains 100 units of water. I even tried doing it by transfering 1 unit instead og 1/4.. but this gave extremely undesirable effects, like floating water covering large areas very quickly... strange stuff Posted Image

When I draw the water tiles (a tile is 16X16) I draw the depth of each tile like this - 16/100 * waterLevel. So a tile with 50 units of water will be drawn as 8 pixels deep. As you may notice in the code I keep the waterlevel value in bytes, to save memory and world file size.

Anyways, my current code will follow shortly - It is the simplest form I've made based on the method above- And also the most succesful one:

private void UpdateWater(GameTime gameTime)
		{
			/*_waterTimer -= (float)gameTime.ElapsedGameTime.TotalMilliseconds;
			if (_waterTimer > 0)
				return;
			_waterTimer = 34;*/
			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 < 0 || x > tg.World.GetLength(0) || y < 0 || y > tg.World.GetLength(1))
						continue;
					if (tg.World[x, y].Type != TileType.Water)
						continue;
					//Water flows downwards
					if (tg.World[x, y + 1].Type == TileType.Water)
					{
						if (((FluidTile)tg.World[x, y + 1]).WaterLevel < MAX_WATER_LEVEL)
						{
							if (((FluidTile)tg.World[x, y]).WaterLevel >= MAX_WATER_LEVEL - ((FluidTile)tg.World[x, y + 1]).WaterLevel)
							{
								((FluidTile)tg.World[x, y]).WaterLevel -= (byte)(MAX_WATER_LEVEL - ((FluidTile)tg.World[x, y + 1]).WaterLevel);
								((FluidTile)tg.World[x, y + 1]).WaterLevel = MAX_WATER_LEVEL;
								if (((FluidTile)tg.World[x, y]).WaterLevel == 0)
								{
									tg.World[x, y] = new Tile(TileType.None, tg.World[x, y].BackgroundType);
									continue;
								}
							}
							else
							{
								((FluidTile)tg.World[x, y + 1]).WaterLevel += ((FluidTile)tg.World[x, y]).WaterLevel;
								tg.World[x, y] = new Tile(TileType.None, tg.World[x, y].BackgroundType);
								continue;
							}
						}
					}
					else if (tg.World[x, y + 1].Collision != TileCollision.Impassable)
					{
						tg.World[x, y + 1] = new FluidTile(TileType.Water, tg.World[x, y + 1].BackgroundType, ((FluidTile)tg.World[x, y]).WaterLevel);
						tg.World[x, y] = new Tile(TileType.None, tg.World[x, y].BackgroundType);
						continue;
					}
					//Water flows leftwards
					byte onefourth = (byte)(((FluidTile)tg.World[x, y]).WaterLevel / 4);
				  
					if (tg.World[x - 1, y].Type == TileType.Water)
					{
						if (((FluidTile)tg.World[x - 1, y]).WaterLevel + onefourth <= MAX_WATER_LEVEL)
						{
							((FluidTile)tg.World[x - 1, y]).WaterLevel += onefourth;
							((FluidTile)tg.World[x, y]).WaterLevel -= onefourth;
						}
					}
					else if (tg.World[x - 1, y].Collision != TileCollision.Impassable)
					{
							tg.World[x - 1, y] = new FluidTile(TileType.Water, tg.World[x - 1, y].BackgroundType, onefourth);
							((FluidTile)tg.World[x, y]).WaterLevel -= onefourth;
					}
					//Water flows Rightwards
					if (tg.World[x + 1, y].Type == TileType.Water)
					{
						if (((FluidTile)tg.World[x + 1, y]).WaterLevel + onefourth <= MAX_WATER_LEVEL)
						{
							((FluidTile)tg.World[x + 1, y]).WaterLevel += onefourth;
							((FluidTile)tg.World[x, y]).WaterLevel -= onefourth;
						}
					}
					else if (tg.World[x + 1, y].Collision != TileCollision.Impassable)
					{
						tg.World[x + 1, y] = new FluidTile(TileType.Water, tg.World[x + 1, y].BackgroundType, onefourth);
						((FluidTile)tg.World[x, y]).WaterLevel -= onefourth;
					}
				}
			}
		}

The code in the top that is commented out is partly for controlling speed, but also because I feared performance issues, so I have it in place to limit the amount of cycles pr second used, if needed.

So my questions are:
- Did I stare myself blind on this and did I make an error I failed to find?
- If not- What changes to this method would possibly improve this?

If I am going about this the wrong way, don't hesitate to point it out- This game is supposed to be a learning experience after all Posted Image

Thank you for reading Posted Image

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


Sponsor:

#2 L. Spiro   Crossbones+   -  Reputation: 12218

Like
1Likes
Like

Posted 23 October 2012 - 02:29 PM

private void UpdateWater(GameTime gameTime)
		{
			/*_waterTimer -= (float)gameTime.ElapsedGameTime.TotalMilliseconds;
			if (_waterTimer > 0)
				return;
			_waterTimer = 34;*/
			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 < 0 || x > tg.World.GetLength(0) || y < 0 || y > tg.World.GetLength(1))
						continue;
					if (tg.World[x, y].Type != TileType.Water)
						continue;
					//Water flows downwards
					if (tg.World[x, y + 1].Type == TileType.Water)
					{
						if (((FluidTile)tg.World[x, y + 1]).WaterLevel < MAX_WATER_LEVEL)
						{
							if (((FluidTile)tg.World[x, y]).WaterLevel >= MAX_WATER_LEVEL - ((FluidTile)tg.World[x, y + 1]).WaterLevel)
							{
								((FluidTile)tg.World[x, y]).WaterLevel -= (byte)(MAX_WATER_LEVEL - ((FluidTile)tg.World[x, y + 1]).WaterLevel);
								((FluidTile)tg.World[x, y + 1]).WaterLevel = MAX_WATER_LEVEL;
								if (((FluidTile)tg.World[x, y]).WaterLevel == 0)
								{
									tg.World[x, y] = new Tile(TileType.None, tg.World[x, y].BackgroundType);
									continue;
								}
							}
							else
							{
								((FluidTile)tg.World[x, y + 1]).WaterLevel += ((FluidTile)tg.World[x, y]).WaterLevel;
								tg.World[x, y] = new Tile(TileType.None, tg.World[x, y].BackgroundType);
								continue;
							}
						}
					}
					else if (tg.World[x, y + 1].Collision != TileCollision.Impassable)
					{
						tg.World[x, y + 1] = new FluidTile(TileType.Water, tg.World[x, y + 1].BackgroundType, ((FluidTile)tg.World[x, y]).WaterLevel);
						tg.World[x, y] = new Tile(TileType.None, tg.World[x, y].BackgroundType);
						continue;
					}
					//Water flows leftwards
					byte onefourth = (byte)(((FluidTile)tg.World[x, y]).WaterLevel / 4);
				  
					if (tg.World[x - 1, y].Type == TileType.Water)
					{
						if (((FluidTile)tg.World[x - 1, y]).WaterLevel + onefourth <= MAX_WATER_LEVEL)
						{
							((FluidTile)tg.World[x - 1, y]).WaterLevel += onefourth;
							((FluidTile)tg.World[x, y]).WaterLevel -= onefourth;
						}
					}
					else if (tg.World[x - 1, y].Collision != TileCollision.Impassable)
					{
							tg.World[x - 1, y] = new FluidTile(TileType.Water, tg.World[x - 1, y].BackgroundType, onefourth);
							((FluidTile)tg.World[x, y]).WaterLevel -= onefourth;
					}
					//Water flows Rightwards
					if (tg.World[x + 1, y].Type == TileType.Water)
					{
						if (((FluidTile)tg.World[x + 1, y]).WaterLevel + onefourth <= MAX_WATER_LEVEL)
						{
							((FluidTile)tg.World[x + 1, y]).WaterLevel += onefourth;
							((FluidTile)tg.World[x, y]).WaterLevel -= onefourth;
						}
					}
					else if (tg.World[x + 1, y].Collision != TileCollision.Impassable)
					{
						tg.World[x + 1, y] = new FluidTile(TileType.Water, tg.World[x + 1, y].BackgroundType, onefourth);
						((FluidTile)tg.World[x, y]).WaterLevel -= onefourth;
					}
				}
			}
		}
Fixed.


L. Spiro
It is amazing how often people try to be unique, and yet they are always trying to make others be like them. - L. Spiro 2011
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums

#3 L. Spiro   Crossbones+   -  Reputation: 12218

Like
1Likes
Like

Posted 23 October 2012 - 02:36 PM

From the description of the results you get, the description of the algorithm you wanted to implement, and a very quick look at the code, there is a logical error that you should handle.
Whether or not it gives you correct results is yet to be seen, but it explains why the right buckets have less than the left sides.

That is because you handle filling to the left before filling to the right.
When you are spilling down from a higher bucket into a lower one, there should be an extra check for both the left and right sides needing to be filled.
Currently you check the left, fill it, then check the right. By that time it is possible that there is not enough water to spill over to the right because you used it all on the left.

Before checking for left spillage, add a check for both right and left spillage together, then spill by ⅛ in both directions instead of ¼.


L. Spiro
It is amazing how often people try to be unique, and yet they are always trying to make others be like them. - L. Spiro 2011
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums

#4 Mekuri   Members   -  Reputation: 299

Like
0Likes
Like

Posted 23 October 2012 - 03:08 PM

From the description of the results you get, the description of the algorithm you wanted to implement, and a very quick look at the code, there is a logical error that you should handle.
Whether or not it gives you correct results is yet to be seen, but it explains why the right buckets have less than the left sides.

That is because you handle filling to the left before filling to the right.
When you are spilling down from a higher bucket into a lower one, there should be an extra check for both the left and right sides needing to be filled.
Currently you check the left, fill it, then check the right. By that time it is possible that there is not enough water to spill over to the right because you used it all on the left.

Before checking for left spillage, add a check for both right and left spillage together, then spill by ⅛ in both directions instead of ¼.


L. Spiro


Thanks a lot for the quick response. It makes sense what you say. I won't be able to try it out until tomorrow night (it's over 11pm here). But it does make sense to check in both direction.. But I'm thinking I define ¼. before doing any of the checks, based on the current remaining water. Shouldn't there be enough no matter what? Since 2 * ¼. = ½ of the remaining water. I might be wrong, a bit tired now :)

But I will apply your logic no matter what, since it seems that it favors one side from my tests there must be truth in what you say :D

Again- Thanks a lot for a quick and good response.

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


#5 L. Spiro   Crossbones+   -  Reputation: 12218

Like
1Likes
Like

Posted 23 October 2012 - 05:01 PM

I'm thinking I define &frac14;. before doing any of the checks, based on the current remaining water. Shouldn't there be enough no matter what? Since 2 * &frac14;. = &frac12; of the remaining water.

That is technically correct and may even be natural (you could say the water is draining twice as fast because it has twice as many places to go) but it would be worth an investigation to see how it looks when adding only half to each direction.


There is something else that might need to be fixed.
Using only the left code in my example (this applies to both sides), you do this:
if (((FluidTile)tg.World[x - 1, y]).WaterLevel + onefourth <= MAX_WATER_LEVEL)
												{
														((FluidTile)tg.World[x - 1, y]).WaterLevel += onefourth;
														((FluidTile)tg.World[x, y]).WaterLevel -= onefourth;
												}

Due to clause #1 in which you move as much water from the top cell down to the bottom cell as you can, there may be the possibility that water is not distributed in even ¼’s to each cell.
This depends on unknown facts about the environment in which your cells/buckets live, so I can’t say for sure if this is a problem, but if there is any possibility that buckets are increased by anything other than even multiples of ¼, the code above needs to be fixed.
In that case you need to add as much to the bucket as will fit and add the rest to the bucket above it.
If you can guarantee your system always transfers multiples of ¼ each time, this is not a problem.


L. Spiro
It is amazing how often people try to be unique, and yet they are always trying to make others be like them. - L. Spiro 2011
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums

#6 Mekuri   Members   -  Reputation: 299

Like
0Likes
Like

Posted 24 October 2012 - 07:02 AM

Hey there.
So I had a few hours to continue with this problem, and I've made some huge changes to how I handle water in my world. Instead of having a specific tile type for water, I instead added a water variable to all tiles. This allows me to flood things, instead of removing them (because the water tile would take the place of the... tree tile for example)This also simplified the code a bit, since I no longer need to check if the neighbouring tile is of the right type or not, I merely check if it contains any water or not, so no more typecasting back and forth Posted Image
So this was the good news, I like it when there are positive side effects of problem solving...
Now, although I've been able to simplify my code, I don't feel any closer to a solution.
I tried the solution where I check both sides before pouring water in to them, but it gave the same results. I also tried to make sure that whenever water transfered downwards it was divideable with four. I might have messed something up by trying that, because I got very unpredictable results, but I was unable to see where, if so.
Anyways, now I have two code versions. The one is basically the one I posted earlier, a bit more simplified, which I will post below.
The other version I've made it so that a tile can contain no more than 16 units of water, and every time I transfer left or right, I only transfer 1 unit. This seems to be a very fast method, but the results are... crazy. So I decided to make a small video demonstrating both approaches, but first I'll post the code for them both

Code for 16 units max and 1 unit pr transfer:
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 < 0 || x > tg.World.GetLength(0) || y < 0 || y > tg.World.GetLength(1))
						continue;
					if (tg.World[x, y].WaterLevel == 0)
						continue;
					//Water flows downwards
					if (tg.World[x, y + 1].WaterLevel < MAX_WATER_LEVEL && tg.World[x, y + 1].Collision != TileCollision.Impassable)
					{
						if (tg.World[x, y].WaterLevel >= MAX_WATER_LEVEL - tg.World[x, y + 1].WaterLevel)
						{
							tg.World[x, y].WaterLevel -= (byte)(MAX_WATER_LEVEL - tg.World[x, y + 1].WaterLevel);
							tg.World[x, y + 1].WaterLevel = MAX_WATER_LEVEL;
						  
							if (tg.World[x, y].WaterLevel == 0)
								continue;
						}
						else
						{
							tg.World[x, y + 1].WaterLevel += tg.World[x, y].WaterLevel;
							tg.World[x, y].WaterLevel = 0;
							continue;
						}
					}

					//Water flows leftwards
					//byte onefourth = (byte)(tg.World[x, y].WaterLevel / 4);
					byte onefourth = 1;
					if (tg.World[x - 1, y].WaterLevel + onefourth <= MAX_WATER_LEVEL && tg.World[x - 1, y].Collision != TileCollision.Impassable)
					{
						tg.World[x - 1, y].WaterLevel += onefourth;
						tg.World[x, y].WaterLevel -= onefourth;
					}

					//Water flows Rightwards
					if (tg.World[x + 1, y].WaterLevel + onefourth <= MAX_WATER_LEVEL && tg.World[x + 1, y].Collision != TileCollision.Impassable)
					{
						tg.World[x + 1, y].WaterLevel += onefourth;
						tg.World[x, y].WaterLevel -= onefourth;
					}
				}
			}
		}


The "original code" - Max waterlevel = 100, and transfers 1/4 left and right.
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 < 0 || x > tg.World.GetLength(0) || y < 0 || y > tg.World.GetLength(1))
						continue;
					if (tg.World[x, y].WaterLevel == 0)
						continue;
					//Water flows downwards
					if (tg.World[x, y + 1].WaterLevel < MAX_WATER_LEVEL && tg.World[x, y + 1].Collision != TileCollision.Impassable)
					{
						if (tg.World[x, y].WaterLevel >= MAX_WATER_LEVEL - tg.World[x, y + 1].WaterLevel)
						{
							tg.World[x, y].WaterLevel -= (byte)(MAX_WATER_LEVEL - tg.World[x, y + 1].WaterLevel);
							tg.World[x, y + 1].WaterLevel = MAX_WATER_LEVEL;
						  
							if (tg.World[x, y].WaterLevel == 0)
								continue;
						}
						else
						{
							tg.World[x, y + 1].WaterLevel += tg.World[x, y].WaterLevel;
							tg.World[x, y].WaterLevel = 0;
							continue;
						}
					}
					//Water flows leftwards
					byte onefourth = (byte)(tg.World[x, y].WaterLevel / 4);[/b]
					if (tg.World[x - 1, y].WaterLevel + onefourth <= MAX_WATER_LEVEL && tg.World[x - 1, y].Collision != TileCollision.Impassable)
					{
						tg.World[x - 1, y].WaterLevel += onefourth;
						tg.World[x, y].WaterLevel -= onefourth;
					}
					else if (tg.World[x - 1, y].WaterLevel + (byte)(onefourth / 2) <= MAX_WATER_LEVEL && tg.World[x - 1, y].Collision != TileCollision.Impassable)
					{
						tg.World[x - 1, y].WaterLevel += (byte)(onefourth / 2);
						tg.World[x, y].WaterLevel -= (byte)(onefourth / 2);
					}
					//Water flows Rightwards
					if (tg.World[x + 1, y].WaterLevel + onefourth <= MAX_WATER_LEVEL && tg.World[x + 1, y].Collision != TileCollision.Impassable)
					{
						tg.World[x + 1, y].WaterLevel += onefourth;
						tg.World[x, y].WaterLevel -= onefourth;
					}
					else if (tg.World[x + 1, y].WaterLevel + (byte)(onefourth / 2) <= MAX_WATER_LEVEL && tg.World[x + 1, y].Collision != TileCollision.Impassable)
					{
						tg.World[x + 1, y].WaterLevel += (byte)(onefourth / 2);
						tg.World[x, y].WaterLevel -= (byte)(onefourth / 2);
					}
				}
			}

And here's the video:

http://youtu.be/EZ34EOnNID8

I'll throw in a few annotations in the video to help guide the attention. It's just a quick video with very little editing.

I really appreciate any time spend helping me out Posted Image I feel I'm soo close to getting water off my to do list Posted Image Thanks

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


#7 Waterlimon   Crossbones+   -  Reputation: 2355

Like
1Likes
Like

Posted 24 October 2012 - 07:54 AM

Take the update order into account. For each tile, BOTH push and pull water to balance the levels. Else the update order affects how the water moves.

You should probably divide the water-flow-speed by 2 because for each link between 2 tiles, there might be 1 pull and 1 push if it doesnt balance out in the first tile update.

Waterlimon (imagine this is handwritten please)


#8 Mekuri   Members   -  Reputation: 299

Like
0Likes
Like

Posted 24 October 2012 - 08:36 AM

Take the update order into account. For each tile, BOTH push and pull water to balance the levels. Else the update order affects how the water moves.

You should probably divide the water-flow-speed by 2 because for each link between 2 tiles, there might be 1 pull and 1 push if it doesnt balance out in the first tile update.

If I understood you correctly each time I push water to the other tiles, I then try to pull some back, in case the push caused the receiveing tile to become more filled than the pushing tile. Is this correct?
Assuming it is I will try this solution, thanks you Posted Image

Edit:
Or do you mean I should run through all the water twice, one forward and one backwards? Each taking their turn, so 30 updates for left to right, and 30 for right to left? - Left, right, left, right etc.?

Edited by Mekuri, 24 October 2012 - 08:41 AM.

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


#9 Waterlimon   Crossbones+   -  Reputation: 2355

Like
1Likes
Like

Posted 24 October 2012 - 10:02 AM

no, first move water down.

then on another run, for both sides of a water, if the side is higher water level you pull water to the middle, else you push water to it.

so each water pushes and/or pulls water from both sides depending on whether they have more or less water.

And when pushing or pulling make sure you dont overfill a water block

Waterlimon (imagine this is handwritten please)


#10 Mekuri   Members   -  Reputation: 299

Like
0Likes
Like

Posted 24 October 2012 - 12:50 PM

no, first move water down.

then on another run, for both sides of a water, if the side is higher water level you pull water to the middle, else you push water to it.

so each water pushes and/or pulls water from both sides depending on whether they have more or less water.

And when pushing or pulling make sure you dont overfill a water block


Hey there. Thanks for the advice. I've now tried what you suggested but the results aren't that good. I get this.. wobbly surface, as if small regions are pushing water back and forth between each other.
I've tried the following:
- One run where it only updates the water downwards, followed by another run, doing pushes and pulls both left and right for each tile.
- Two runs, where each run both updates water downwards and pushes and pulls in both direction. One run updating towards left, and the other towards right.
- Two runs, no pulling only pushing. Each update both updates downwards and left and right. One updating from left to right, and the other right to left.

The best result is the last method, but that doesn't necesarily makes it the most correct one, since none of them is doing it right.
My current code is based on your suggestion, so please do tell me if I misunderstood that as well Posted Image

private void UpdateWater(GameTime gameTime)
		{
			if (!_updateWaterDown)
			{
				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 < 0 || x > tg.World.GetLength(0) || y < 0 || y > tg.World.GetLength(1))
							continue;
						if (tg.World[x, y].WaterLevel == 0)
							continue;
						//Water flows to/from left
						byte onefourth = (byte)(tg.World[x, y].WaterLevel / 4);
						if (onefourth == 0)
							onefourth = 1;
						if (tg.World[x, y].WaterLevel > tg.World[x - 1, y].WaterLevel)
						{
							if (tg.World[x - 1, y].WaterLevel + onefourth <= MAX_WATER_LEVEL && tg.World[x - 1, y].Collision != TileCollision.Impassable)
							{
								tg.World[x - 1, y].WaterLevel += onefourth;
								tg.World[x, y].WaterLevel -= onefourth;
							}
							else if (tg.World[x - 1, y].WaterLevel + (byte)(onefourth / 2) <= MAX_WATER_LEVEL && tg.World[x - 1, y].Collision != TileCollision.Impassable)
							{
								tg.World[x - 1, y].WaterLevel += (byte)(onefourth / 2);
								tg.World[x, y].WaterLevel -= (byte)(onefourth / 2);
							}
						}
						else if (tg.World[x, y].WaterLevel < tg.World[x - 1, y].WaterLevel)
						{
							if (tg.World[x, y].WaterLevel + onefourth <= MAX_WATER_LEVEL)
							{
								tg.World[x, y].WaterLevel += onefourth;
								tg.World[x - 1, y].WaterLevel -= onefourth;
							}
							else if (tg.World[x, y].WaterLevel + (byte)(onefourth / 2) <= MAX_WATER_LEVEL)
							{
								tg.World[x, y].WaterLevel += (byte)(onefourth / 2);
								tg.World[x - 1, y].WaterLevel -= (byte)(onefourth / 2);
							}
						}
						//Water flows to/from right
						if (tg.World[x, y].WaterLevel > tg.World[x + 1, y].WaterLevel)
						{
							if (tg.World[x + 1, y].WaterLevel + onefourth <= MAX_WATER_LEVEL && tg.World[x + 1, y].Collision != TileCollision.Impassable)
							{
								tg.World[x + 1, y].WaterLevel += onefourth;
								tg.World[x, y].WaterLevel -= onefourth;
							}
							else if (tg.World[x + 1, y].WaterLevel + (byte)(onefourth / 2) <= MAX_WATER_LEVEL && tg.World[x + 1, y].Collision != TileCollision.Impassable)
							{
								tg.World[x + 1, y].WaterLevel += (byte)(onefourth / 2);
								tg.World[x, y].WaterLevel -= (byte)(onefourth / 2);
							}
						}
						else if (tg.World[x, y].WaterLevel < tg.World[x + 1, y].WaterLevel)
						{
							if (tg.World[x, y].WaterLevel + onefourth <= MAX_WATER_LEVEL)
							{
								tg.World[x, y].WaterLevel += onefourth;
								tg.World[x + 1, y].WaterLevel -= onefourth;
							}
							else if (tg.World[x, y].WaterLevel + (byte)(onefourth / 2) <= MAX_WATER_LEVEL)
							{
								tg.World[x, y].WaterLevel += (byte)(onefourth / 2);
								tg.World[x + 1, y].WaterLevel -= (byte)(onefourth / 2);
							}
						}
					}
				}
				_updateWaterDown = !_updateWaterDown;
			}
			else
			{
				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 < 0 || x > tg.World.GetLength(0) || y < 0 || y > tg.World.GetLength(1))
							continue;
						if (tg.World[x, y].WaterLevel == 0)
							continue;
						//Water flows downwards
						if (tg.World[x, y + 1].WaterLevel < MAX_WATER_LEVEL && tg.World[x, y + 1].Collision != TileCollision.Impassable)
						{
							if (tg.World[x, y].WaterLevel >= MAX_WATER_LEVEL - tg.World[x, y + 1].WaterLevel)
							{
								tg.World[x, y].WaterLevel -= (byte)(MAX_WATER_LEVEL - tg.World[x, y + 1].WaterLevel);
								tg.World[x, y + 1].WaterLevel = MAX_WATER_LEVEL;
								if (tg.World[x, y].WaterLevel == 0)
									continue;
							}
							else
							{
								tg.World[x, y + 1].WaterLevel += tg.World[x, y].WaterLevel;
								tg.World[x, y].WaterLevel = 0;
								continue;
							}
						}
					}
				}
				_updateWaterDown = !_updateWaterDown;
			}
		}

Am I doing it the right way?
Is there another approach I should consider?

This is a tough one, and I've failed to dig up much information about tile based water online. I really appreciate all the help I get here, and any suggestions are welcome.

Thank you Posted Image

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


#11 Waterlimon   Crossbones+   -  Reputation: 2355

Like
1Likes
Like

Posted 24 October 2012 - 01:54 PM

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)

Waterlimon (imagine this is handwritten please)


#12 Mekuri   Members   -  Reputation: 299

Like
0Likes
Like

Posted 25 October 2012 - 06:56 AM

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


Edited by Mekuri, 08 January 2013 - 02:33 AM.

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


#13 Mekuri   Members   -  Reputation: 299

Like
0Likes
Like

Posted 26 October 2012 - 04:15 PM

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.



Enjoy!

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


#14 smorgasbord   Members   -  Reputation: 371

Like
0Likes
Like

Posted 29 October 2012 - 03:45 AM

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




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS