바다와 나무를 생성함으로써 절차적 지형 생성을 마무리한다.
먼저 기존에는 공기와 흙, 이렇게 두가지 블록밖에 없었기에 별도의 블록 관리 시스템을 구현하지 않았었지만 지형을 생성함에 따라 필요한 블록들이 늘어나 우선적으로 블록 관리 시스템을 만들었다.
블록에는 아래 사진과 같은 머티리얼 데이터들이 등록되어 있고 이 블록 데이터들을 한데 모아 관리해주는 블록 데이터 매니저 클래스를 만들었다. 나중에 지형을 생성하면서 블록의 머티리얼을 바꿔줘야할 경우가 생길 때 블록 데이터 매니저의 함수를 호출해 머티리얼 데이터들을 참조할 수 있도록 하였다.
이로써 기본적인 준비가 다 되었고 본격적으로 바다를 생성할 단계이다.
바다는 간단하게 구현할 수 있다. 기존의 펄린노이즈를 받아 지형의 높낮이를 조절하는 함수 중간에 y값을 검사하는 코드를 넣어 특정 값 이하가 공기 블록일 경우 바다 블록으로 대체시키면 바다를 생성할 수 있다.
if (y < 8)
{
chunkLoader.chunk.blocks[x, y, z] = Block.water;
}
else
{
chunkLoader.chunk.blocks[x, y, z] = Block.air;
}
이제는 나무를 생성할 차례인데 나무의 경우는 조금 복잡하다.
나무는 블록 하나가 아닌 여러 블록이 모여 하나의 구조물을 이루는 형태이다. 이를 코드로 직접 좌표값을 계산해가며 블록을 설정하기에는 무리가 있기 때문에 다른 방법을 고안해내었다.
그 방법은 3차원 배열을 나무의 크기에 맞게 생성하고 마치 레고의 도면 처럼 하나씩 레이어를 쌓아가는 형태로 나무 블록들의 위치를 표시했다. 그리고 이 배열 값을 받아와 특정한 구역의 블록 데이터 값을 배열 값으로 대체하면 나무가 생성되는 구조이다.
public int[,,] tree =
{
{
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 4, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
},
{
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 4, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
},
{
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 4, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
},
{
{ 5, 5, 5, 5, 5 },
{ 5, 5, 5, 5, 5 },
{ 5, 5, 4, 5, 5 },
{ 5, 5, 5, 5, 5 },
{ 5, 5, 5, 5, 5 },
},
{
{ 5, 5, 5, 5, 5 },
{ 5, 5, 5, 5, 5 },
{ 5, 5, 4, 5, 5 },
{ 5, 5, 5, 5, 5 },
{ 5, 5, 5, 5, 5 },
},
{
{ 0, 0, 0, 0, 0 },
{ 0, 5, 5, 5, 0 },
{ 0, 5, 4, 5, 0 },
{ 0, 5, 5, 5, 0 },
{ 0, 0, 0, 0, 0 },
},
{
{ 0, 0, 0, 0, 0 },
{ 0, 0, 5, 0, 0 },
{ 0, 5, 5, 5, 0 },
{ 0, 0, 5, 0, 0 },
{ 0, 0, 0, 0, 0 },
}
};
private void BuildTree(Chunk chunk, int _x, int _y, int _z)
{
int width = Structure.instance.tree.GetLength(0);
int height = Structure.instance.tree.GetLength(1);
int length = Structure.instance.tree.GetLength(2);
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
for (int z = 0; z < length; z++)
{
chunk.SetBlock(Structure.instance.tree[x, y, z], -height / 2 + y + _x, x + _y, -length / 2 + z + _z);
}
}
}
}
나무가 잘 생성되는 것을 확인하였고 나무의 생성 빈도를 조절하기 위해 파라미터를 추가하여 인스펙터에서 언제든지 값을 조절할 수 있게끔 만들었다.
마지막으로 지금까지 생성된 지형을 다듬기 위해 지상으로부터 특정 값 이하일 시 돌블록으로 표현되게끔 만들었다.
for (int i = 0; i < ground.Count; i++) //2차원 평면좌표
{
int y = height; //최대 높이 값부터 시작하여 아래로 내려오면서 땅의 높이 값을 구함
while (y != 0 && chunkLoader.chunk.blocks[ground[i].x, y - 1, ground[i].y] != Block.stone) y--;
if (y < height)chunkLoader.chunk.blocks[ground[i].x, y, ground[i].y] = Block.grass;
for (int j = 1; j < 4; j++) //3칸 높이를 흙으로 채움
{
if (y - j >= 0) chunkLoader.chunk.blocks[ground[i].x, y - j, ground[i].y] = Block.dirt;
}
int rand = Random.Range(0, 101);
if (rand > treeProb) continue; //나무 생성 확률 정함
if (ground[i].x >= 2 && ground[i].x < noiseWidth - 3 && ground[i].y >= 2 && ground[i].y < noiseLength - 3)
{
if (y < height && y >= 0 && chunkLoader.chunk.blocks[ground[i].x, y + 1, ground[i].y] == Block.air)
BuildTree(chunkLoader.chunk, ground[i].x, y + 1, ground[i].y);
}
}