코딩/유니티

[Unity] 3D 랜덤 맵 생성 1

Hun die 2023. 5. 23. 06:25

 

랜덤 맵 생성을 위해 PerlinNoise 를 활용 했다.

 

PerlinNoise 는 간단히 설명하면 float 값 두개를 받아 0 ~ 1 사이의 연속적인 난수값을 형성해준다.

 

참고

https://docs.unity3d.com/kr/530/ScriptReference/Mathf.PerlinNoise.html

 

Mathf-PerlinNoise - Unity 스크립팅 API

Generate 2D Perlin noise.

docs.unity3d.com

 

 

 

일단 간단하게 PerlinNoise를 활용해 맵을 만들어 보았다.

 

public class MapSetting : MonoBehaviour
{
    [Header("블록")]
    public List<GameObject> Block = new List<GameObject>();

    [Header("맵 사이즈")]
    public int WorldSizeX;
    public int WorldSizeZ;

    [Header("블록 간격")]
    public float GridOffset;

    private void Start()
    {
        for (int x = 0; x < WorldSizeX; x++)
        {
            for (int z = 0; z < WorldSizeZ; z++)
            {
                Vector3 Pos = new Vector3(x * GridOffset, NoiseGenerater(x, z), z * GridOffset);

                GameObject NewBlock = Instantiate(Block[0], Pos, Quaternion.identity) as GameObject;

                NewBlock.transform.SetParent(transform);
            }
        }
    }

    float NoiseGenerater(int x, int z)
    {
        float xNoise = (x + this.transform.position.x) / 10;
        float zNoise = (z + this.transform.position.y) / 10;

        return Mathf.PerlinNoise(xNoise, zNoise);
    }
}

 

맵이 만들어 졌는데 미묘하다.

PerlinNoise는 연속되는 0 ~ 1 사이의 값을 반환하기에 높이의 차이가 거의 나지 않는 것을 볼 수 있다.

 

 

높이의 폭을 늘리기 위해 새로운 변수를 추가하고 높이에 값을 곱해 주었다.

Vector3 Pos = new Vector3(x * GridOffset, NoiseGenerater(x, z) * NoiseHeight, z * GridOffset);

 

NoiseHeight 값 10
NoiseHeight 값 20

NoiseHeight 값을 넣어주고 높낮이가 차이가 나자 맵이 그럴듯하게 보이기 시작했다.

NoiseHeight 값이 크기에 따라 맵의 유동성이 커진 것을 볼 수 있다.

이대로 써도 그럴듯 하지만, 내가 원하는 건 마인크래프트와 값이 높이가 딱딱 맞아 떨어지는 맵을 원했다.

 

높이가 맞아 떨어지는 맵을 원해서 y 값을 Mathf.RoundToInt를 사용해 반올림 해봤다.

Vector3 Pos = new Vector3(x * GridOffset, Mathf.RoundToInt(NoiseGenerater(x, z) * NoiseHeight), z * GridOffset);

높이를 반올림 했을 때

그러자 마인크래프트에서나 보던 맵이 완성되었다.

하지만, 이걸로 완성이라고 하기엔 문제가 있다.

 

바로 랜덤으로 맵이 생성 되지 않는다는 것이다.

그래서 변수 Seed를 추가하고 PerlinNoise 값에 Seed값을 더해 주었다.

    float NoiseGenerater(int x, int z)
    {
        float xNoise = (x + this.transform.position.x) / 10 + Seed;
        float zNoise = (z + this.transform.position.y) / 10 + Seed;

        return Mathf.PerlinNoise(xNoise, zNoise);
    }

Seed 값 1
Seed값 2

시드값을 추가해주자 Seed에 따라 맵이 다르게 생성되었다.

이렇게 일단 맵의 겉모습을 완성했다.

 

이제 빈 속을 채우기 위해 for문을 추가해 y 값 넣어 주었고 깊이를 변수로 추가했다.

    private void Start()
    {
        for (int x = 0; x < WorldSizeX; x++)
        {
            for (int z = 0; z < WorldSizeZ; z++)
            {
                for (int y = WorldDepth; y < Mathf.RoundToInt(NoiseGenerater(x, z) * NoiseHeight); y++)
                {
                    Vector3 Pos = new Vector3(x * GridOffset, y, z * GridOffset);
                    
                    GameObject NewBlock = Instantiate(Block[0], Pos, Quaternion.identity) as GameObject;
                    NewBlock.transform.SetParent(transform);
                }

            }
        }
    }

깊이 0
깊이 -5

이제 맵을 생성할 때 깊은 곳 부터 맵을 쌓아 올려 맵을 만들었다.

 

 

 

속을 채우기 전
속을 채운 후

속을 채우기 전에는 NoiseHeight 값을 크게 만들면 빈 속이 다 보여 부자연스러웠으나 속을 채우자 NoiseHeight값이 커져도 그럴듯하게 보인다.

 

 

 

 

+ 뒤에 발견한 사실인데 Seed 값이 너무 크면 맵이 생성되지 않았다.

Seed값 1000000
Seed값 10000000

위 이미지를 보면 적당히 Seed 값 1000000 까지는 랜덤으로 생성되었으나, 10000000정도가 되면 맵이 생성되지 않는 것을 확인했다.

	float xNoise = (x + this.transform.position.x) / 10 + Seed;
        float zNoise = (z + this.transform.position.y) / 10 + Seed;

아마도 Seed를 더한 값을 나누고 PerlinNoise에 넣는 방식을 썼는데 너무 큰 값을 나누어 Noise 값이 0이 된 것 같다.