이번 시간에는 오픈월드 게임처럼 맵을 무한정 탐험할 수 있는 시스템을 구현해 볼 거다.
거기서 우리는 청크 시스템으로 구현할 생각이다.
플레이어의 위치를 실시간으로 추적하며, 현재 위치를 기준으로 로드/언로드할 청크를 계속 계산한다.
계속 자신의 위치 기준 언로드 / 로드 할 청크를 계산한다.
// 메인 함수
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);
}
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;
}
새롭게 시야에 들어온 청크를 로드한다. 기존에 로드된 적이 없는 인덱스라면 새로운 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;
}

