10

Intro

I'm developing a real time game that have dynamically generated map and I'd like to add rivers going from mountains to the sea.

Brief info about the map

In my game, when a player moves to the edge of currently generated map, new chunks (small, square piece of map) of map are being loaded on server. If there are no chunks found for the requested location, they are generated using the same seed which is used in other parts of map. Currenly, there is no limit in map size or total amount of generated chunks. I'm using Perlin noise to generate the map. I treat it as a hight map, with every field having a range of values it occupy e.g. shallow water starts from -0.12 inclusive ending on 0 exclusive.

I already read about how to make the river look realistic:

Question

How do I add rivers to the map when I don't know how the whole maps look like? If I don't know where the mountains are I cannot place the river source. And if I have some part of the map already generated I cannot just add the river a little later when a player encounters a mountain chain.

Similarities

Similar scenario (I think) is in Minecraft. There too, map generates as player moves using a seed and there are rivers which continue to swirl while the map generates on. Sadly, I don't know the technique used there.

Screens from the game

enter image description here

enter image description here

Chunk loading temporarily disabled, to show the boundaries:

enter image description here

Prolog
  • 203
  • 1
  • 7
  • 1
    do new chunks take into consideration the adjacent chunks already generated? – Willk Aug 23 '20 at 22:33
  • Just verifying: If you are using noise to generate terrain then there will be local minima. Water will tend to gather in these minima if they are minimal in all directions. Water will also pour over the lowest lip of such minima. Is this a fair representation of how you do it? Do you distinguish between porous and non-porous ground or between rock an easily eroded soil? – chasly - supports Monica Aug 23 '20 at 23:00
  • @willk Currently, no but I could make them available during the generation process. Though, I 'm a bit worried about possible performance complications of such. When player moves, more than one chunk is loaded/generated. – Prolog Aug 23 '20 at 23:02
  • Okay but when you say a chunk, what is the resolution within a chunk. Presumably there must be some gradation between chunks or there will be sudden cliffs or drops along the lines between them. I think we need much more clarity about your algorithm to know how to answer. Is the image from your algorithm? – chasly - supports Monica Aug 23 '20 at 23:07
  • P.S. Is a chunk that whole image or is it one rectangle within the image? Can you zoom in to rectangles for finer detail? – chasly - supports Monica Aug 23 '20 at 23:14
  • @chasly-reinstateMonica Yes, I could create local lakes or ponds, but I don't follow how to then create a river from it. I don't distinguish between porous and non-porous ground, neither between rock and easily eroded soil. For now, I assume water can flow on every type of ground. Chunk size is configurable, right now it is set to 15x15 fields. Single field is a 20x20 pixel square visible on image included in question and yes it is from my algorithm. There are no cliffs or gradation, using the same seed keeps it all nicely consistent. Image in question loaded about 3300 fields with 15 chunks. – Prolog Aug 23 '20 at 23:18
  • Okay. I think I see the problem. You are assuming that water level is the same everywhere. This is not true on real terrain unless the terrain is totally porous. With non-porous rocks you will have pools that are isolated high up in mountains. Their level will be far above sea level. What you need to find is outlets from these higher pools. These will lead to lower pools and so on all the way down to sea-level.. This is what gives the rivers. Am I on the right track? – chasly - supports Monica Aug 23 '20 at 23:32
  • 1
    Following up on was @chasly said, what you could do is generate another (perhaps smaller--less computation) perlin noise map pertaining to water level. Your higher-up terrain generator would then reference this water level map to calculate the appropriate in-/exclusive region. – BMF Aug 23 '20 at 23:57
  • Last time I attempted world generation, I used a method of layering perlin noise "fields." Like, say, mountainous/plains/cavernous fields, whose values if meeting some conditions would govern the shape of the whole. The fields had their own behaviors and they all interacted among themselves. Mountain fields would be encouraged to follow paths that were nominally linear or subtly snaking. Mountain fields would also tend to govern the values of lower fields. You wouldn't want valleys to generate where a mountain was, so the mountain fields would magnetically repel and subdue them. – BMF Aug 24 '20 at 00:05
  • I think Minecraft actually generates a low-detail map in huge sections now. So it might decide "there's a river somewhere through this 128x128 area" and when it actually generates that area then it decides exactly where to put the river. Also Minecraft rivers aren't actually rivers, they're flat and they can even go in circles. – user253751 Aug 24 '20 at 11:26
  • If your world is 2D you can almost certainly afford to generate a huge amount up-front, like Dwarf Fortress. – user253751 Aug 24 '20 at 11:28
  • This question has nothing to do with "worldbuilding", it is a well-travelled question in game engineering. Simply ask this question on the excellent gamedev site. (Or just google for 100s of discussions about it.) – Fattie Aug 24 '20 at 15:25

4 Answers4

14

The problem is unsolvable as asked. You may end up generating a river which the player traces back to the top of a minor hill in the middle of a desert.

There are a few alternatives that can make this work:

LOD and physics:

You can sub- and super- sample perlin noise in order to create multi-resolution maps. If your tiles there are 1 tile per 1meter, you can create the entire map at a more manageable level, say 1 tile per 1km, calculate realistic spring points and a rough river path, and then subdivide down, to say 1 tile per 100m - at that level you know where the river flows to an accuracy of within 10 tiles, work out where the river flows along that path, and then where the river flows through those smaller tiles recursively, etc, until you arrive at your full detail level.

It's better to precalcualte this and store it, if possible. You can still run this on-demand, but it's not trivial to code.

LOD and 1D perlin

As above, on your low resolution map, find the high point where a spring would come from, find the closest sea level point, draw a straight line. Now use 1D perlin noise to make the river wind perpendicular to the line.

This is much easier to code - it can have some unrealistic artefacts, but for a 2D tile game like this you're unlikely to notice them.

Fake it

Have 2 styles of rivers. One going "uphill", one going "downhill". Create tiles to show the river coming from a spring and forming in to a full river, and one showing a full river draining into a small below ground feature.

If the perlin noise value for a coastal area is a very precise value, then it spawns an "uphill" river. This travels to the highest edge point in the tile which doesn't border an explored tile, which then when the next tile loads, goes to the next highest point. If it detects it's gone to a local max (there's no higher point) or all neighbouring tiles were already explored, backtrack to about the middle, put a tile which shows the river coming out of a cave or spring or something else below ground, so the user doesn't see the river starting from the top of a small hill.

If the perlin noise value for a mountain is a very precise value, then it spawns a "downhill" river, follow to the lowest other border of an unexplored tile, and if it gets to a local minimum, or a chunk with no unexplored neighbours, put a tile in which shows the river disappearing below ground.

This is usually good enough for a tile based 2D game.

2 (or more) perlin channels

Currently you're running the perlin generator once per tile, to give a single value. I'd suggest run it multiple times per tile (with different seeds of course, and usually different scaling factors). The use those multiple values to populate the tiles, instead of just one;

This can help break the expectation that beech is next to plains, plains is usually next to forest, forest is usually next to mountains, etc.

If you run it twice - I'd suggest humidity and elevation. If you run it 3 times, I'd suggest feature size (sand / rock, ice / snow, or baren -> grassland -> scrub -> trees), humidity (desert -> fertile -> river -> swamp), and elevation (water -> plains -> hills -> mountains). 4 times maybe would include a temperature too.

Rather than focus on running your river "downhill", try to run it where the humidity is within a particular contour range. While the river wont "realistically" flow downhill, in a tile game like what you're showing elevation is obscured, you're better off ensuring that trees grow near rivers, rivers aren't bordered by desert, and the land near rivers is fertile farmland.

Ash
  • 44,182
  • 5
  • 107
  • 219
  • 2
    While this answer is admirable, the question and answer should be moved to the gamedev site. This is unrelated to "worldbuilding" – Fattie Aug 24 '20 at 15:26
3

How do I add rivers to the map when I don't know how the whole maps look like? If I don't know where the mountains are I cannot place the river source. And if I have some part of the map already generated I cannot just add the river a little later when a player encounters a mountain chain.

You cannot do it entirely within a chunk. If you are on the edge of a chunk and there appears to be a local minimum you have two choices.

  1. Extend the terrain invisibly around the minimum until you find the perimeter of that minimum in the as yet unexplored chunk. You don't have to complete the whole of the new chunk because by then you know where the pool will drain to. This will either be back into the current chunk or to somewhere, as yet incomplete, in the not yet explored chunk. In other words you have to extend beyond what is visible/has been explored. You could call this just-in-case programming.

  2. Have a deterministic algorithm that deals specifically with this eventuality. I.e. deliberately close off any possible outlets into the new chunk. Effectively you have built a dam. This will almost certainly lead to fake-looking artefacts.

Note that using local minima in this way (assuming non-permeable terrain) will allow the grain to be as fine as you wish. This could even allow you to show springs and other small sources of the river.

chasly - supports Monica
  • 49,370
  • 15
  • 152
  • 305
2

Some years ago I wrote an algorithm for an RPG game that worked in similar way.
Think in "Minesweeper". When you discover a mountain square there is a x% probability there will be water. But that square influence probability of chunk next to it being mountain. Which influence the appearance of water. For each map you might impose water sources. Map is started from a range 0-100 number is chosen. It's the amount of water sources. The more the map is un-shadowed the more probability of water showing up rise.

Now, to aid you nature came with this nifty thing that brooks appear and then it seep into crack and dissapear. Nowaday with modern technology we can trace where it come back but in "discovery" phase one source might show as 3 or 4 different brooks. SO they don't need to be one continous line from discovered source to "somewhere". It might end after 4 or 5 chunks.

So you might just end the water generated chunks when player meet already discovered "dry" chunk.
OR

Create a fun change in your map. With time (player move) dry chunk became wet ones when generator try to link biggest water "puddle" (if lower). Generator might also have probability of creating creek so it won't go in straighest line.

Remeber that your player act a typical human. So they might discover river source, it might flow into undicovered regions but just by divocery and their presence they change how the water flow.

SZCZERZO KŁY
  • 21,906
  • 2
  • 33
  • 67
1

The simplest algorithm is for rivers to "snap" to a mountain valley whenever an existing river is within some threshold distance (and perhaps angle) to a valley that meets the minimum requirements.

Let's say you have a river generated by whatever algo you happen to be using. There is no existing source yet, and it ends at the "fog of war" on your map. But the player moves to reveal that, and you use the algo to again generate the missing pieces.

Presuming that your algo occasionally generates mountains, if mountains/valleys are created withing n units from the "end" of the river and at any angle shallower than +/-45 degrees of the tangent of the river, then generate a river path from that point to the mountain valley and place a glacier there.

If the river endpoint is farther away than n units, start to generate its path and if that path comes within n units of the valley, do as above.

It would be necessary to generate mountains first, and then overlay the river on top of such a map.

You might even manage to allow it to connect to several mountain/valleys if there are multiple potential sites, and this would give you tributaries feeding into a main sea-bound river.

This of course relies on a two-part map generation scheme, where most map features are generated, and rivers are "painted" onto the top of that. It would work for Minecraft, for instance, which I believe does this. If your game were more sophisticated and used elevation to determine the paths of rivers, this would be unviable.

John O
  • 12,056
  • 1
  • 23
  • 53