[OpenGL] 오픈월드 구현

gest·5일 전

OpenGL

목록 보기
9/11

이번 시간에는 오픈월드 게임처럼 맵을 무한정 탐험할 수 있는 시스템을 구현해 볼 거다.
거기서 우리는 청크 시스템으로 구현할 생각이다.


ChunkManager

플레이어의 위치를 실시간으로 추적하며, 현재 위치를 기준으로 로드/언로드할 청크를 계속 계산한다.

1. 메인 업데이트 로직

계속 자신의 위치 기준 언로드 / 로드 할 청크를 계산한다.


// 메인 함수
void ChunkManager::update(const glm::vec3& playerPos, std::vector<GameObject*>& objects)
{
    glm::ivec2 newCenter = worldToChunk(playerPos.x, playerPos.z);

    //이미 같은 중심 청크면 작업 없음 — 매 프레임 호출되지만 99% 케이스에서 즉시 리턴
    if (initialized && newCenter == currentCenter) return;

    initialized = true;
    currentCenter = newCenter;

    //=== 1) 원하는 청크 인덱스 집합 만들기 ===
    //(중심 ± viewRadius 범위)
    std::vector<glm::ivec2> desired; //청크 (x,z) 3x3 => 즉 9개.
    setDesired(desired, newCenter);


    //2) 멀어진 청크 unload
    std::vector<glm::ivec2> unloadedList = unloadFarChunks(desired, objects);

    //3) 새 청크 load
    std::vector<glm::ivec2> loadedList = loadChunks(desired, objects);   //로그용

    //4) 청크 정보 출력
    printChunkInfo(newCenter, unloadedList, loadedList);
}

2. 메모리 언로드

desired 목록(현재 있어야 할 청크)에 포함되지 않은 청크들은 더 이상 필요 없으므로 메모리를 반납한다.

// desired에 없는 청크들 메모리 반납.
// 역순 순회 이유: 중간에서 erase하면 앞쪽 인덱스는 그대로 유지돼서 안전함.
std::vector<glm::ivec2> ChunkManager::unloadFarChunks(const std::vector<glm::ivec2>& desired,
    std::vector<GameObject*>& objects)
{
    std::vector<glm::ivec2> unloadedList;   //로그용
    for (int i = (int)chunks.size() - 1; i >= 0; --i)
    {
        glm::ivec2 idx = chunkIndices[i];

        //desired에 있는지 검사
        bool inDesired = false;
        for (const auto& d : desired)
        {
            if (d == idx)
            {
                inDesired = true; 
                break; 
            }
        }
        //자기 기준으로 내부 청크라면 무시
        if (inDesired) 
            continue;

        //밖을 벗어났다면

        Terrain* old = chunks[i];

        //objects 벡터에서 해당 포인터 찾아 제거
        auto it = std::find(objects.begin(), objects.end(), static_cast<GameObject*>(old));
        if (it != objects.end()) objects.erase(it);

        delete old;

        chunks.erase(chunks.begin() + i);
        chunkIndices.erase(chunkIndices.begin() + i);
        
        unloadedList.push_back(idx);
    }
    return unloadedList;
}

3. 메모리 로드 (Load)

새롭게 시야에 들어온 청크를 로드한다. 기존에 로드된 적이 없는 인덱스라면 새로운 Terrain 객체를 생성한다.

//청크 메모리 가져오는 함수
std::vector<glm::ivec2> ChunkManager::loadChunks(const std::vector<glm::ivec2>& desired,
    std::vector<GameObject*>& objects)
{
    std::vector<glm::ivec2> loadedList;
    for (const auto& d : desired)
    {
        if (findChunk(d) >= 0) continue;   //이미 있음

        //새 청크 만들기
        glm::vec2 worldCenter(d.x * chunkSize, d.y * chunkSize);
        Terrain* chunk = new Terrain(*shader, color, worldCenter);

        //저장된 텍스처 ID 적용
        if (textureId)   chunk->setTexture(textureId);
        if (normalMapId) chunk->setNormalMap(normalMapId);
        if (shadowMapId) chunk->setShadowMap(shadowMapId);

        chunks.push_back(chunk);
        chunkIndices.push_back(d);
        objects.push_back(chunk);
        loadedList.push_back(d);
    }
    return loadedList;
}

결과


0개의 댓글