1
\$\begingroup\$

I use Mathf.PerlinNoise to generate grass/water in a 2d square. It works as intended, however I am not really getting very interesting water, and at times it can look very bare or unrealistic, here is a sample of a bad generation:

enter image description here

Unfortunately it took me about 20 tries to find an interesting one, what I want is something along these lines:

enter image description here

Or something like this, but I don't want any tiny areas of water:

enter image description here

You can copy/paste the components below and try urself with 1x1 cubes (if you don't have Odin just delete that part):

using UnityEngine;
using Sirenix.OdinInspector;

public class GenerateMap : MonoBehaviour
{
    [Range(0, 40)]
    public int mapWidth = 20;
    [Range(0, 40)]
    public int mapHeight = 20;
    [Range(0, 20)]
    public float noiseScale;

    [Space]
    public GameObject parent;
    public GameObject grassTile, waterTile;

    [Button("Generate map")]
    public void GenerateMap()
    {
        for (int i = 0; i < parent.transform.childCount; i++)
        {
            Destroy(parent.transform.GetChild(i).gameObject);
        }

        float[,] noiseMap = Noise.GenerateNoiseMap(mapWidth, mapHeight, noiseScale);

        for (int x = 0; x < mapWidth; x++)
        {
            for (int y = 0; y < mapHeight; y++)
            {
                Debug.Log(noiseMap[x, y]);

                if (noiseMap[x, y] >= .3)
                    Instantiate(grassTile, new Vector3(x, 0, y), Quaternion.identity, parent.transform);
                else
                    Instantiate(waterTile, new Vector3(x, 0, y), Quaternion.identity, parent.transform);
            }
        }
    }
}

Noise:

using UnityEngine;

public static class Noise
{

    public static float[,] GenerateNoiseMap(int mapWidth, int mapHeight, float scale)
    {
        float[,] noiseMap = new float[mapWidth, mapHeight];

        Vector2 offset = new Vector2(Random.Range(0, 100), Random.Range(0, 100));

        if (scale <= 0)
            scale = .0001f;

        for (int y = 0; y < mapHeight; y++)
        {
            for (int x = 0; x < mapWidth; x++)
            {
                float sampleX = x / scale + offset.x;
                float sampleY = y / scale + offset.y;

                float perlinValue = Mathf.PerlinNoise(sampleX, sampleY);
                noiseMap[x, y] = perlinValue;
            }
        }

        return noiseMap;
    }
}
\$\endgroup\$
  • \$\begingroup\$ Can you help us understand what criteria you're using to evaluate how "interesting" a particular water pattern is? We understand that you want to avoid very small water areas, but it's unclear what other criteria are in play. \$\endgroup\$ – DMGregory Apr 27 at 18:25
  • \$\begingroup\$ @DMGregory I tried to post 2 images of what I meant. But rly all I mean is 1-3 medium-large formations of water. sometimes connected, sometimes not. Right now I end up with water just along the edges, or tiny puddles. Does that make sense? \$\endgroup\$ – Majs Apr 27 at 18:29
  • \$\begingroup\$ Have you tried scaling your perlin noise texture down and using a larger area of it? \$\endgroup\$ – Jay Apr 27 at 18:48
  • \$\begingroup\$ @Jake I have, but there is no combination of this current solution that guarantees the type of water bodies I want. i.e not random puddles, not just 1 line along the edge, etc. \$\endgroup\$ – Majs Apr 27 at 19:41
  • 1
    \$\begingroup\$ @Jake yeah I thought about that too, m ight end up doing that,. \$\endgroup\$ – Majs Apr 27 at 19:55
1
\$\begingroup\$

Try lowering the scale, and adding multiple octaves.

Also I wouldn't suggest Mathf.Perlin, because it generates its namesake Old Perlin noise, which has visible square alignment. I would import Unity.Mathematics and use Unity.Mathematics.noise.snoise(float2) for a good 2D Simplex noise implementation instead.

Take any tutorial or project, which uses "Perlin" noise, with a grain of salt. Most times Perlin is used, are because a library included the function, or because the author heard about its iconic-sounding name first. Rarely is Perlin chosen because it was carefully thought out to be the best algorithm for the purpose.

You could do the following:

  • Use a bigger number for scale
  • float2 pos = new float2(sampleX, sampleY);
  • float noiseValue = noise.snoise(pos) + 0.5f*noise.snoise(pos * 2f + new float2(35f, 66f) + 0.25f*noise.snoise(pos * 4f * 2f + new float2(75f, 104f); where the frequency doubles but the amplitude halves each time. The offset is to prevent the same features from overlapping at the origin of the noise. You can also experiment with frequency and amplitude values that aren't exactly double / half the previous.

EDIT: Re-reading the part of your post that discusses what you're looking for, maybe what you want to do is choose 1-3 randomized points in your map that designate water bodies, choose a radius, compute the squared euclidean distance from each (plus the radius), and add a simplex noise onto that to give it some interesting boundaries. Make sure the frequency is low enough and amplitude high enough to deviate it enough from circular. In the case of lakes overlapping, you can make them join together smoothly if you use a formula like float lake1 = (1f - (dx*dx + dy*dy) / (radius + smallPadding)); lake1 *= lake1; where dx = (input point).x - (lake center).x and the same for dy, then doing float totalLake = lake1 + lake2 + lake3 + snoise and block = totalLake > smallThreshold ? water : grass

| improve this answer | |
\$\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.