콘텐츠로 이동

UE5 World Partition & Level Streaming

World Partition은 UE5에서 도입된 오픈 월드 레벨 관리 시스템입니다. 기존 레벨 스트리밍이 레벨 단위로 로드·언로드했다면, World Partition은 월드를 셀(Cell) 단위로 자동 분할해 플레이어 위치 기반으로 스트리밍합니다.

기존 Level Streaming 대비 장점:

  • 레벨 경계 없는 하나의 퍼시스턴트 월드
  • 셀 크기·로딩 거리 자동 관리
  • One File Per Actor(OFPA)로 협업 충돌 감소
  • HLOD(계층적 LOD) 자동 생성

; DefaultEngine.ini
[/Script/Engine.WorldSettings]
bEnableWorldPartition=True
[WorldPartition]
RuntimeCellSize=12800 ; 셀 크기 (cm 단위, 128m)
LoadingRange=25600 ; 로딩 거리 (256m)

에디터에서: 월드 세팅 → World Partition → Enable World Partition 체크


Data Layers — 레이어 기반 콘텐츠 관리

섹션 제목: “Data Layers — 레이어 기반 콘텐츠 관리”

Data Layers는 World Partition의 오브젝트를 논리적 레이어로 분류합니다. 낮/밤 사이클, 퀘스트 단계, DLC 콘텐츠를 레이어로 관리할 수 있습니다.

// DataLayerAsset: 에디터에서 생성 후 C++에서 참조
#include "WorldPartition/DataLayer/DataLayerAsset.h"
#include "WorldPartition/DataLayer/WorldDataLayersSubsystem.h"
UCLASS()
class AWorldManager : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, Category = "DataLayers")
TObjectPtr<UDataLayerAsset> _nightLayer;
UPROPERTY(EditDefaultsOnly, Category = "DataLayers")
TObjectPtr<UDataLayerAsset> _questLayer;
// 낮/밤 전환 시 레이어 활성화
void SetNightTime(bool bIsNight)
{
auto* DataLayerSubsystem =
GetWorld()->GetSubsystem<UWorldDataLayersSubsystem>();
if (!DataLayerSubsystem) return;
EDataLayerRuntimeState TargetState = bIsNight
? EDataLayerRuntimeState::Activated
: EDataLayerRuntimeState::Unloaded;
DataLayerSubsystem->SetDataLayerRuntimeState(_nightLayer, TargetState);
}
// 퀘스트 완료 후 새 콘텐츠 로드
void OnQuestComplete(int32 QuestId)
{
auto* DataLayerSubsystem =
GetWorld()->GetSubsystem<UWorldDataLayersSubsystem>();
if (!DataLayerSubsystem) return;
DataLayerSubsystem->SetDataLayerRuntimeState(
_questLayer, EDataLayerRuntimeState::Activated);
}
};

#include "WorldPartition/WorldPartitionRuntimeCell.h"
#include "WorldPartition/WorldPartition.h"
// 특정 위치의 셀 강제 로드 (컷씬, 텔레포트 전)
void AGameMode::PreloadAreaAroundLocation(FVector Location, float Radius)
{
UWorldPartition* WorldPartition = GetWorld()->GetWorldPartition();
if (!WorldPartition) return;
// 스트리밍 소스 등록: 이 위치를 중심으로 로딩 강제
// (플레이어 카메라 외 추가 소스)
FWorldPartitionStreamingSource Source;
Source.Location = Location;
Source.Rotation = FRotator::ZeroRotator;
Source.TargetState = EStreamingSourceTargetState::Activated;
Source.Shapes.Add(FStreamingSourceShape::MakeSphere(Radius));
Source.bReplay = false;
Source.Priority = EStreamingSourcePriority::High;
// PlayerController에서 스트리밍 소스 추가
// GetWorld()->GetFirstPlayerController()->SetStreamingSource(Source);
}

HLOD는 멀리 있는 셀의 복잡한 지오메트리를 단순화된 메시로 대체해 원거리 렌더링 비용을 줄입니다.

// HLOD 레이어 설정 (에디터)
// 월드 아웃라이너 → HLOD Layers → New HLOD Layer
// 에셋 기반 HLOD 설정
#include "WorldPartition/HLOD/HLODLayer.h"
// C++에서 액터의 HLOD 레이어 지정
UCLASS()
class AMyStaticMesh : public AStaticMeshActor
{
GENERATED_BODY()
public:
AMyStaticMesh()
{
// HLOD 레이어 에셋 경로로 지정
// 에디터에서 설정하는 것이 일반적
}
};

HLOD Builder 타입:

타입설명
Merged Mesh여러 메시를 하나로 병합
Instanced인스턴스 스태틱 메시로 변환
NaniteNanite 지오메트리로 변환
Custom커스텀 HLOD 로직

World Partition 이전 방식도 여전히 유효합니다.

// 서브레벨 스트리밍 제어
#include "Engine/LevelStreamingDynamic.h"
UCLASS()
class ALevelStreamController : public AActor
{
GENERATED_BODY()
public:
// 동적으로 레벨 로드
void LoadLevel(const FString& LevelPath)
{
bool bSuccess = false;
ULevelStreamingDynamic* StreamingLevel =
ULevelStreamingDynamic::LoadLevelInstance(
GetWorld(),
LevelPath, // "/Game/Levels/Village"
GetActorLocation(),
GetActorRotation(),
bSuccess
);
if (bSuccess && StreamingLevel)
{
// 로드 완료 콜백
StreamingLevel->OnLevelLoaded.AddDynamic(
this, &ALevelStreamController::OnLevelLoaded);
StreamingLevel->SetShouldBeVisible(true);
}
}
// 레벨 언로드
void UnloadLevel(ULevelStreaming* StreamingLevel)
{
if (!StreamingLevel) return;
StreamingLevel->SetShouldBeLoaded(false);
StreamingLevel->SetShouldBeVisible(false);
}
UFUNCTION()
void OnLevelLoaded()
{
UE_LOG(LogTemp, Log, TEXT("Level loaded successfully"));
}
};

// 로딩 화면을 위한 스트리밍 진행률 확인
void ALoadingManager::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
UWorld* World = GetWorld();
if (!World) return;
// 레벨 스트리밍 상태 확인
bool bAllLoaded = true;
for (ULevelStreaming* Level : World->GetStreamingLevels())
{
if (Level->ShouldBeLoaded() && !Level->IsLevelLoaded())
{
bAllLoaded = false;
float Progress = Level->GetLoadedLevel()
? 1.f : 0.f; // 단순화된 예시
UE_LOG(LogTemp, Log,
TEXT("Loading level: %s (%.0f%%)"),
*Level->GetWorldAssetPackageName(), Progress * 100.f);
}
}
if (bAllLoaded && _bWasLoading)
{
_bWasLoading = false;
OnAllLevelsLoaded();
}
}
void ALoadingManager::OnAllLevelsLoaded()
{
// 로딩 화면 숨기기
UE_LOG(LogTemp, Log, TEXT("All levels loaded"));
}

콘솔 명령어:
wp.Runtime.ToggleDrawRuntimeCells — 셀 경계 시각화
wp.Runtime.ToggleDrawRuntimeHash — 해시 그리드 시각화
wp.Runtime.OverrideRuntimeSpatialHashLoadingRange 512 — 로딩 거리 오버라이드
stat worldpartition — 스트리밍 통계

기능설명
World Partition셀 기반 자동 스트리밍
Data Layers논리적 콘텐츠 레이어 (낮/밤, 퀘스트)
HLOD원거리 LOD 자동 생성
OFPA액터별 개별 파일 (협업 충돌 감소)
Level Streaming수동 서브레벨 로드/언로드

World Partition은 오픈 월드 개발의 핵심 시스템입니다. Data Layers로 게임 상태에 따라 월드를 동적으로 변형하고, HLOD로 원거리 렌더링 비용을 최소화하면 대규모 맵에서도 안정적인 성능을 유지할 수 있습니다.