반복적인 무한맵은 하나의 오브젝트를 나열한다면 가능하다.
하지만 랜덤한 무한맵은 매번 다른 맵을 보여주어야하기에 특별한 알고리즘이 필요하다. 그러기에 Perlin Noise라는 것을 사용할 것이다.
일반적인 Random값을 이용해 Noise를 적용한다면 이렇게 확률에 따라서 한 곳에 점이 찍힐 뿐, 주변과 이어지는 느낌이 없다.
하지만 Perlin Noise를 사용한다면 각각의 픽셀들이 점진적으로 바뀌어 이어지는 듯한 모습을 확인할 수 있다. 그러기에 이를 통해서 지형의 높낮이, 지형에 따른 랜덤맵 등을 표현할 수 있는 것이다.
Perlin Noise에 대한 자세한 알고리즘은 이곳! 에서 확인 가능합니다.
float[,] noiseMap = new float[size, size];
(float xOffset, float yOffset) = (Random.Range(-10000f, 10000f), Random.Range(-10000f, 10000f));
for(int y = 0; y < size; y++) {
for(int x = 0; x < size; x++) {
float noiseValue = Mathf.PerlinNoise(x * scale + xOffset, y * scale + yOffset);
noiseMap[x, y] = noiseValue;
}
}
위의 코드를 토대로 PerlinNoise는 Scale, Offset의 값에 의해서 결정이 되는데 우리가 봐야되는 것은 Scale의 값이다. scale의 변수가 크면 클수록 점진적이지 않게되기에 일반적인 Noise와 같아진다. 그러기에 상황에 맞추어 설정을 잘 해주기를 바라며 일반적으로 0.1의 값을 놓고 사용한다.
위의 코드를 통해서 noiseMap이라는 이차원배열에 각각의 noiseValue가 채워졌고 noiseValue를 통해서 0.5f이하는 물, 0.5f 이상은 풀로써 출력을 한다면,
이러한 결과를 얻을 수 있습니다. 각각의 픽셀들이 점진적으로 변하기에 이어지는 형태를 얻어 마치 하나의 지형처럼 보입니다.
이제 Perlin Noise를 통해서 맵을 생성할 수 있지만 실행할 때마다 항상 똑같은 맵이 생성될 것 입니다. 그러지 않기위해 그리고 내가 원한다면 생성했던 맵을 기억하기위해 랜덤시드를 설정해줄 것입니다.
[SerializeField]
private int randomSeed = 1;
우선 사용자가 변경이 가능하게 randomSeed 변수를 하나 설정해줍니다.
Random.InitState(randomSeed);
그리고 랜덤시드를 통해서 Random함수를 초기화해준다고 생각하시면 되겠습니다. 이렇게 해준다면 Seed값에 따라서 항상 다른 맵이 생성되고 Seed가 같다면 동일한 맵을 불러올 수 있습니다.
랜덤지형 생성에 있어서 좋은 알고리즘이지만 저희의 프로젝트를 진행함에 있어 Noise의 단점은 확률이 높은 오브젝트가 항상 낮은 오브젝트를 감싸고 있는 형태로 나온다는 점입니다.
위의 예시는 제가 직접 생성한 맵이고 검정,회색,노랑,파랑 순으로 확률에 따라서 감싸져있는 모습을 확인하실 수 있습니다.
저희 프로젝트의 특성상 랜덤하게 분포되어있는 광물이여야 되기에 부적합을 느꼈고, 해결하기 위해서 Noise로 형성되어있는 맵에 한번더 Random함수를 사용하여서 광물을 입혔습니다. 그로인한 결과는
위와 같이 감싸져있지 않고 하나의 지형마다 랜덤하게 분포되어 있는 효과를 얻을 수 있었습니다.
이제 원할때 마다 랜덤한 맵을 생성을 할 수 있는 기술을 얻었습니다. 그러기에 원할때! 를 설정하게 된다면 마무리를 할 수 있습니다. 이번에 저희 프로젝트에서는 맵을 50x50의 크기를 통해서 유저의 움직임에 따라 맵을 그리는 방법을 채택했습니다.
그러기에 유저가 현재 지나고있는 맵을 나가려할 때, 나가려 하는 곳의 맵을 생성시켜줌으로써 유저는 맵이 무한히 있다고 생각하게끔 만들었습니다.
왼쪽은 맵이 생성되는 것을 보여주고 오른쪽은 실제 플레이어가 보는 화면입니다. 유저가 지나감에 따라 맵을 생성해주기에 유저는 맵이 무한하다고 느끼게 되는 것입니다.
위와 같은 방법은 Random Seed를 지속적으로 바꾸어주고 그러기에 Seed값을 Queue에 저장해놔야지 리플레이를 통해서 맵을 다시 똑같은 형태로 생성할 수 있습니다.
다른 방법으로는 하나의 Seed를 통해 기존의 맵에서 확장해나가는 방식인데 지속적으로 주변맵과의 비교를 해야되는 것이 다소 무거워질 거라 생각을 해서 Seed를 변하는 간단한 방법으로 표현했습니다.
랜덤 맵의 생성에 대한 소개이지만 제가 표현하면서 썼던 방식을 나열했을 뿐입니다. 다른 쉬운 방법이 있을 수도 있습니다!! :smile:
혹시라도 질문이 있으시다면 제가 아는 선에서 무한맵, 확장에 대한 부분은 답변드리겠습니다!