3
\$\begingroup\$

I have made a map generation in my tile based game. It almost exactly does what I want, except for 1 thing. The rivers look like this: enter image description here

The problem with this is, the water is not realistic. It are just some spots random over the world. I want to make a realistic river generation. Which would include different sizes (between 5 and 20 block wide), branches, lakes and different depths (by changing the colour of the water). What is the best way to do that?

Here is an Simcity 4 example of what I mean with realistic rivers: enter image description here

My code:

waterOffsetX = Random.Range (10f, 300000f);
waterOffsetX = Mathf.Round (waterOffsetX) + 0.5f;
waterOffsetY = Random.Range (10f, 300000f);
waterOffsetY = Mathf.Round (waterOffsetY) + 0.5f;

for (float x = -gridX; x < gridX; x += 1)
{
    for (float y = -gridY; y < gridY; y += 1) 
    {
        Vector3 spawnPos = new Vector3 (x, y, 0);
        float perlinWater = Mathf.PerlinNoise (x * seedWaterX + waterOffsetX , y * seedWaterY + waterOffsetY);

        if (perlinWater > perlinThresholdWater) {
        GameObject waterTile = Instantiate (water, spawnPos, Quaternion.identity) as GameObject;
    }
    }
}

Values:
seedWater (X & Y) = 0.1
Grid (X & Y) = 50
perlinThresholdWater = 0.6
\$\endgroup\$
  • \$\begingroup\$ you can use GetPixel from map's picture so Implement it on tile map \$\endgroup\$ – Seyed Morteza Kamali Jul 31 '16 at 14:15
  • \$\begingroup\$ Yeah but in this case I wanted to show what the rivers looked like and why I don't like it this way. For that purpose I didn't use GetPixel \$\endgroup\$ – FlorisdG Jul 31 '16 at 14:34
  • \$\begingroup\$ Is your map perfectly flat, or is there some sort of upward slope at different points? \$\endgroup\$ – HDE 226868 Mar 15 '17 at 18:59
  • \$\begingroup\$ It is perfectly flat. There might be some hills, but that would be just a change of texture \$\endgroup\$ – FlorisdG Mar 15 '17 at 19:52
2
\$\begingroup\$

I wouldn't use straight perlin noise for this. The closest I would get would be to use something like Perlin Worms to generate the data for plotting the rivers.

However, I'd take a procedural generation approach. My answer here is where I'd start. You'll build the river backwards. Pick an end point for your river at some point along the edge of your map. Choose an end width, like your 20 tiles for a wide river. From there, you'll step away from that edge of the map, plotting the course of the river. Each step, there's a chance of:

  1. Change direction. Weighted chance. Higher weight to continue straight, then to turn left or right, with a preference towards the opposite direction of the end.

  2. Branch. The river splits its current path into two, dividing its current width between the two paths. Each new path takes its own steps.

  3. Reduce in width. This accounts for water that's being added to the river that's too small for a tile.

Each of these options can happen more or less frequently depending on the width of the river. For example, you may want to change direction much less often when the river is wide, and more frequent direction changes when it's narrow.

Path
{
    width
    startingPoint

    pointWidths
    lastPointWidth
    currentDirection

    Step()
    {
         stepChoice = GetStepChoice(width) //weighted chance to decide what to do
         switch(stepChoice)
             changeDirection: //change the current direction
                 currentDirection = GetNewDirection(currentDirection)
             reduceWidth:
                 width--
             branch:
                 newWidth = divideWidth(width)
                 width = width - newWidth
                 RiverMaker.addPath(new path(newWidth, lastPoint))
             noChange:
                 //Do nothing, just step forward below

          lastPointWidth = (lastPoint+currentDirection, width)
          pointWidths.add(lastPointWidth)
    }
}

RiverMaker
{
    Path[] paths
    Path[] donePaths
    Start()
    {
        paths.add(new path(20, randomEdgeStartPoint)
        MakeRiver()
    }

    MakeRiver()
    {
        //process each path until there are none
        while(paths.count > 0)
        {
            for each path in paths
            {
                 if(path.isNotEnded)
                    path.Step()
                 else
                    donePaths.add(path)
                    paths.remove(path)
            }
        }
        for each path in donePaths
        {
            //Step through each path, using the width and position
            ApplyPathToWorld(path)
        }
    }
}
\$\endgroup\$
  • \$\begingroup\$ That seems to be the answer, but could you add some code in for me on how to use this within my code? \$\endgroup\$ – FlorisdG Jul 31 '16 at 16:55
  • 1
    \$\begingroup\$ The code for this type of algorithm is pretty in-depth. I added some pseudo code for you to implement. There are many more details you'll have to figure out. If you need code specific to your project, you can contract me at, say $80 an hour? \$\endgroup\$ – MichaelHouse Jul 31 '16 at 17:35
  • \$\begingroup\$ Well, I don't want to invest too much in this game, money wise, so that is a no for me ;) I won't set this as the answer just yet, I'll wait and see if someone else comes with a answer \$\endgroup\$ – FlorisdG Jul 31 '16 at 17:37
2
\$\begingroup\$

There is a well-know article on how to generate terrain from 2010, here:

http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/

with an update, here:

https://www.redblobgames.com/maps/mapgen2/

Now, for beginners, you achieve natural rivers without using Voronoi diagrams, if you generate a height map and then plot the river from the river source to the sea or ocean (basically the lowest point on your height map, which is the river end, so to speak), like so:

  • a) start location, direction and height
  • b) if same direction as last iteration: random direction, but only within 180° angle (to prevent turning back)
  • c) find the lower level (height) and move there, if possible
  • d) every few steps increase river width (so it gets wider to the sea)
  • e) if you hit the water level: done
  • f) if you do not hit water level: repeat b), c), d) and e)

Lakes and ponds are different and done in a second step. It means, when you look closer, you see that the SimCity rivers work without lakes, which means that lakes were either added before or after the river creation.

There's more to it, of course. For instance, if you change the direction every turn, well, your river feels weird, so you should change the direction only after a random number of steps. But I hope it helps.

I always work with the combination of height, rainfall and temperature (and wind too, actually). This way, the rainfall map shows where lakes will be. And, lakes and random "river sources" are then where I start rivers. What is great about rivers is that they make your landscape feel less flat, as if there is some hidden height, even if everything is flat.

If you lack a height map, because you are in flat lands, well, just create a fake one and see how the rivers behave. I am sure that SimCity generates additional height maps in order to make rivers more natural.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.