Issues with 2D tile-based Sidescroller Water
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 )
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 ) 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
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]
Enjoy!