본문 바로가기
유니티/절차적 생성

[유니티] 절차적 지형 생성 2 - Chunk

by 개발자 박근영 2021. 11. 15.
 

목표

청크라는 단위 개념을 이용해 끝없이 로드되는 지형을 생성한다.


청크란?

하나의 거대한 덩어리라는 뜻으로 끝없는 세계를 구현할 때 맵 데이터를 관리하기 위해 사용된다. 대표적으로 마인크래프트에서 16(가로) * 16(세로) * 256(높이) 단위로 청크를 관리한다.


청크를 사용하는 이유는 무엇인가?

절차적으로 만들어진 세계는 시드가 동일한 이상 언제나 생성되는 결과물은 동일하다. 이러한 점을 이용하여 동적으로 메모리에 맵을 생성하고 지울 수 있는데, 맵을 청크 단위가 아닌 블록 단위(1 * 1 * 1)로 생성하게 되면 메모리에 부하가 생기고 소요되는 시간 또한 길어진다. 그래서 여러 개의 블록을 하나로 묶어 관리하게 되면 맵을 지우거나 로드할 때 빠른 속도로 처리할 수 있다.


청크를 어떻게 활용할 것인가?

플레이어의 화면에 비추어지는 영역에 청크를 생성하고 플레이어의 화면 밖 영역은 청크를 지우는 방식으로 설계하면 무한한 세계를 만들 수 있다. 여기서는 16 * 16 * 16 사이즈를 청크 단위로 사용하였다.


소스 코드

게임 시작 시 청크 생성

//플레이어를 중심으로 정해진 사이즈만큼 청크를 생성한다.
for (int x = -aroundChunkNum; x <= aroundChunkNum; x++)
{
    for (int z = -aroundChunkNum; z <= aroundChunkNum; z++)
    {
        Vector3 pos = new Vector3(width * x, 0, length * z);

        GameObject chunkObjClone = Instantiate(chunkObj, pos, Quaternion.identity, chunkHolder);
        ChunkLoader chunkLoader = chunkObjClone.GetComponent<ChunkLoader>();
        Chunk chunk = new Chunk();
        chunk.blocks = new Block[noiseWidth, height, noiseLength]; //청크 블록 데이터 초기화
        chunkLoader.SetChunk(chunk);

        CreateWorld(chunkLoader, pos); //청크의 데이터를 기반으로 월드 생성
    }
}

플레이어가 이동하는 방향으로 청크 생성

//현재 청크와 플레이어간 거리 계산
float xDistance = Mathf.Abs(player.transform.position.x - transform.position.x);
float zDistance = Mathf.Abs(player.transform.position.z - transform.position.z);

if (xDistance >= chunkLoadDistance) //일정 거리 이하일 시
{
    Vector3 dir = Vector3.zero;
    if (player.transform.position.x > transform.position.x) dir = Vector3.right;
    else if (player.transform.position.x < transform.position.x) dir = Vector3.left;

    transform.position += dir * chunkLoadDistance * 2; //플레이어 이동 방향으로 좌표 이동

    terrainGenerator.CreateWorld(this, transform.position); //맵 재 생성
}
if (zDistance >= chunkLoadDistance)
{
    Vector3 dir = Vector3.zero;
    if (player.transform.position.z > transform.position.z) dir = Vector3.forward;
    else if (player.transform.position.z < transform.position.z) dir = Vector3.back;

    transform.position += dir * chunkLoadDistance * 2;
    terrainGenerator.CreateWorld(this, transform.position);
}

메모리 최적화를 위해서 초기에 생성해 놓은 청크들을 플레이어 이동 방향에 따라 위치를 바꾼뒤 맵을 재 생성하는 방법을 이용했다. 매번 청크를 동적으로 생성하기 보다는 미리 생성해 놓은 청크를 재활용하는 방법이 최적화면에서 효과적이다.


최종 결과


문제점

청크 생성은 문제없이 잘 작동하였지만 동적으로 프레임 드랍없이 생성되는 건 아니었다. 청크가 로드될 때마다 약 0.5초 정도의 프레임 드랍이 발생하여 이를 해결하기 위해 기존 코드에서 문제점이 있는지 살펴보았다. 그 결과 프레임 드랍의 원인은 동적으로 메쉬를 생성하는 부분에서 메모리 부하가 발생했었다.


해결 과정

그래서 이 또한 메쉬를 동적으로 생성하는 것이 아닌 초기에 쿼드 오브젝트를 6면체 모양으로 생성해두고 SetActive를 이용하여 재활용하는 방식을 사용해 문제를 해결하였다.

 

댓글