레벨 스트리밍과 World Partition
Level Streaming과 World Partition의 로드 단위를 비교하고 대규모 월드의 영역을 필요할 때 불러옵니다.
이전 절에서 액터의 생성과 관리에 대해 알아보며, 게임 월드 내의 객체들을 어떻게 효율적으로 다룰 수 있는지 이해했습니다.
하지만 게임 월드가 매우 커지고 복잡해질 경우, 모든 액터와 모든 지형 데이터를 동시에 메모리에 로드하는 것은 불가능에 가깝습니다.
이 문제를 해결하기 위해 언리얼 엔진은 레벨 스트리밍(Level Streaming)과 월드 파티션(World Partition)을 제공합니다.
UE5의 넓은 연속 월드에는 World Partition을 검토할 수 있고, 제작자가 명시한 서브 레벨 경계에는 기존 Level Streaming을 사용할 수 있습니다.
레벨 스트리밍 (Level Streaming)
레벨 스트리밍은 메인 레벨(Persistent Level)에 필요한 서브 레벨(Sub-levels)들을 동적으로 로드하거나 언로드하여 메모리 사용량을 최적화하고 로딩 시간을 단축하는 기술입니다.
플레이어가 월드를 탐색함에 따라, 현재 위치 주변의 레벨만 메모리에 로드하고, 멀리 떨어진 레벨은 언로드하여 리소스를 절약합니다.
레벨 스트리밍의 작동 방식
퍼시스턴트 레벨 (Persistent Level): 해당 월드가 유지되는 동안 기준이 되는 메인 레벨입니다.
게임의 시작 지점, 전역 게임 로직, 공통 리소스 등이 포함될 수 있습니다.
서브 레벨 (Sub-levels): 퍼시스턴트 레벨에 동적으로 스트리밍될 작은 레벨들입니다.
각각의 서브 레벨은 특정 지역, 건물 내부, 던전 등을 포함할 수 있습니다.
스트리밍 볼륨 (Streaming Volume): 플레이어가 특정 볼륨(영역)에 진입하거나 이탈할 때 서브 레벨의 로드/언로드를 트리거하는 투명한 볼륨입니다.
레벨 스트리밍의 장점
- 메모리 최적화: 모든 레벨 데이터를 동시에 로드할 필요가 없어 메모리 사용량을 크게 줄일 수 있습니다.
- 로딩 시간 단축: 게임 시작 시 모든 것을 로드하는 대신, 필요한 부분만 점진적으로 로드하므로 초기 로딩 시간이 짧아집니다.
- 끊김 없는 월드: 로딩 화면 없이 넓은 월드를 연속적으로 탐색하는 경험을 제공할 수 있습니다.
- 협업 용이: 여러 팀원이 각자의 서브 레벨을 독립적으로 작업하고 나중에 병합할 수 있습니다.
레벨 스트리밍 구현 (C++)
아래 예제는 World Partition 셀이 아니라 기존 서브 레벨 스트리밍을 제어합니다. Levels 창에서 대상 서브 레벨을 현재 월드에 등록하고 Streaming Method를 Blueprint로 설정한 구성을 전제로 합니다. 임의의 맵 이름만 넘겨 등록하는 예제는 아닙니다.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyLevelStreamer.generated.h"
UCLASS()
class MYPROJECT_API AMyLevelStreamer : public AActor
{
GENERATED_BODY()
public:
AMyLevelStreamer();
// 스트리밍할 서브 레벨의 이름 (에디터에서 설정)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Level Streaming")
FName LevelToStreamName;
protected:
virtual void BeginPlay() override;
public:
// 레벨을 로드하는 함수
UFUNCTION(BlueprintCallable, Category = "Level Streaming")
void LoadMyLevel();
// 레벨을 언로드하는 함수
UFUNCTION(BlueprintCallable, Category = "Level Streaming")
void UnloadMyLevel();
};#include "MyLevelStreamer.h"
#include "Kismet/GameplayStatics.h" // UGameplayStatics::LoadStreamLevel 등 포함
#include "Engine/LevelStreaming.h" // ULevelStreaming에 접근하기 위해 포함
AMyLevelStreamer::AMyLevelStreamer()
{
PrimaryActorTick.bCanEverTick = false; // 이 액터는 틱이 필요 없을 수 있음
}
void AMyLevelStreamer::BeginPlay()
{
Super::BeginPlay();
// 필요에 따라 BeginPlay에서 자동으로 레벨을 로드할 수도 있습니다.
}
void AMyLevelStreamer::LoadMyLevel()
{
if (LevelToStreamName.IsNone())
{
UE_LOG(LogTemp, Warning, TEXT("LevelToStreamName is not set for %s"), *GetName());
return;
}
// 레벨 스트리밍 로드
// LoadStreamLevel(월드 컨텍스트, 레벨 이름, 로드 후 표시, 로드 동안 대기, latent 정보)
UGameplayStatics::LoadStreamLevel(this, LevelToStreamName, true, true, FLatentActionInfo());
UE_LOG(LogTemp, Warning, TEXT("Attempting to load level: %s"), *LevelToStreamName.ToString());
}
void AMyLevelStreamer::UnloadMyLevel()
{
if (LevelToStreamName.IsNone())
{
UE_LOG(LogTemp, Warning, TEXT("LevelToStreamName is not set for %s"), *GetName());
return;
}
// 레벨 스트리밍 언로드
UGameplayStatics::UnloadStreamLevel(this, LevelToStreamName, FLatentActionInfo(), false);
UE_LOG(LogTemp, Warning, TEXT("Attempting to unload level: %s"), *LevelToStreamName.ToString());
}이 코드의 LoadStreamLevel은 bShouldBlockOnLoad=true를 전달하므로 로딩 중 프레임 정지가 생길 수 있습니다. UnloadStreamLevel은 비차단 요청이며, 두 Attempting 로그는 대상 준비 완료를 확인한 결과가 아닙니다. 기본 FLatentActionInfo에는 완료 콜백이 없으므로, 확장 시 완료 확인과 중복 요청 식별을 설계합니다.
사용법메인 레벨(Persistent Level)에서 AMyLevelStreamer 액터 블루프린트(BP_MyLevelStreamer)를 배치합니다.
BP_MyLevelStreamer의 디테일 패널에서 Level To Stream Name에 스트리밍할 서브 레벨의 이름을 지정합니다 (예: Level_Forest, Level_City).
레벨 블루프린트나 다른 액터의 로직에서 BP_MyLevelStreamer의 Load My Level 또는 Unload My Level 함수를 호출하여 스트리밍을 제어합니다.
일반적으로는 플레이어가 특정 트리거 볼륨(Trigger Volume)에 진입했을 때 이 함수들을 호출하도록 설정합니다.
월드 파티션 (World Partition, UE5 기준)
월드 파티션은 UE5에서 대규모 오픈 월드를 관리하는 기본 시스템입니다.
스트리밍을 활성화한 월드는 런타임 그리드와 Streaming Source의 범위, 액터의 Is Spatially Loaded 및 Runtime Data Layer 상태에 따라 로드 대상을 결정합니다. 플레이어만이 유일한 스트리밍 소스는 아닙니다.
월드 파티션의 주요 특징
- 자동 셀 분할: 하나의 월드를 셀로 나눠 필요한 공간을 로드할 수 있습니다. 셀 크기·참조·로드 범위가 메모리 사용량에 영향을 줍니다.
- 소스 기반 로드: 플레이어 등의 Streaming Source 주변을 로드합니다. 공간 로드를 사용하지 않는 액터는 거리 조건과 다르게 처리됩니다.
- Data Layer: 액터 집합을 편집용 또는 런타임 상태별로 나눕니다. 런타임 로드 제어에는 Runtime Data Layer의 상태를 사용합니다.
- One File Per Actor: 액터 단위 파일 분리로 충돌을 줄여 협업 효율이 높습니다.
HLOD는 먼 셀의 시각적 대리 표현을 만드는 기능이고, One File Per Actor는 편집 파일 분리를 위한 기능입니다. 둘은 같은 런타임 로드 상태를 뜻하지 않습니다.
월드 파티션 사용 절차 (UE5)
새로운 레벨을 생성하거나 기존 레벨을 엽니다.
Window > World Settings를 엽니다.
World Partition이 활성화된 템플릿인지 확인합니다. (새 UE5 오픈 월드 템플릿은 기본 활성화)
Window > World Partition 도구를 열어 셀 로드 범위, Data Layer 상태, HLOD 구성을 점검합니다.
UE4 참고: 구버전 프로젝트의 World Composition은 레거시 개념으로만 이해하면 충분합니다.
사용할 월드 구조는 공간 규모뿐 아니라 명시적 연출 제어와 팀의 제작 방식에 맞춰 선택합니다.
C++와 월드 파티션
월드 파티션은 엔진이 스트리밍을 자동 관리하므로, C++에서는 상태 확인과 예외 제어에 집중하는 방식이 일반적입니다.
- 상태 확인: GetCurrentLevelName은 현재 레벨 이름 하나를 반환합니다. GetStreamingLevels는 스트리밍 객체 목록이므로 각 항목의 로드·표시 상태를 따로 확인해야 합니다. World Partition은 해당 Streaming Source의 완료 상태 등 파티션 API를 사용해 필요한 영역의 준비를 판단합니다.
- 액터 존재 여부 확인: 스트리밍된 레벨 내의 액터에 접근하기 전에 해당 레벨이 제대로 로드되었는지 확인하는 로직을 추가하는 것이 중요합니다.
로드 단위와 준비 상태
요청을 보낸 것, 메모리에 올라온 것, 플레이에 사용하는 것을 구분합니다.
| 구분 | Level Streaming | World Partition |
|---|---|---|
| 로드 단위 | 현재 월드에 등록한 서브 레벨 | 런타임 그리드의 셀과 액터 집합 |
| 로드 조건 | 코드·블루프린트·볼륨과 Streaming Method | Streaming Source·공간 로드 설정·Runtime Data Layer |
| 준비 확인 | 로드 상태와 표시 상태를 구분 | 필요한 소스 범위의 스트리밍 완료와 활성 상태 확인 |
| 참조 경계 | 언로드될 액터를 사용할 때 유효성 확인 | 액터 간 참조로 함께 로드되는 묶음도 확인 |
| 남길 상태 | 언로드 전에 진행 상태의 별도 보관 결정 | 셀·액터 수명과 분리해 퀘스트 등 상태 보관 |
- 로드 단위
- Level Streaming: 현재 월드에 등록한 서브 레벨World Partition: 런타임 그리드의 셀과 액터 집합
- 로드 조건
- Level Streaming: 코드·블루프린트·볼륨과 Streaming MethodWorld Partition: Streaming Source·공간 로드 설정·Runtime Data Layer
- 준비 확인
- Level Streaming: 로드 상태와 표시 상태를 구분World Partition: 필요한 소스 범위의 스트리밍 완료와 활성 상태 확인
- 참조 경계
- Level Streaming: 언로드될 액터를 사용할 때 유효성 확인World Partition: 액터 간 참조로 함께 로드되는 묶음도 확인
- 남길 상태
- Level Streaming: 언로드 전에 진행 상태의 별도 보관 결정World Partition: 셀·액터 수명과 분리해 퀘스트 등 상태 보관
Loaded와 보이는 상태는 같지 않습니다. One File Per Actor는 편집 협업 기능이며, 런타임 준비 완료의 신호가 아닙니다.
고속 이동이나 텔레포트에서는 목적지의 Streaming Source로 미리 준비하는 방식을 검토합니다. 대상 기기에서 I/O 지연·프레임 정지·메모리, 충돌·내비게이션 준비와 HLOD 전환을 함께 확인해야 하며, 스트리밍 사용 자체가 끊김 없는 플레이를 보장하지는 않습니다.