UE5 Level Streaming
Level Streaming은 큰 월드를 여러 서브레벨로 분할하고 필요할 때만 로드해 메모리와 CPU 비용을 줄이는 기법입니다. UE5에서는 World Partition이 자동 스트리밍을 제공하지만, 레거시 프로젝트나 세밀한 제어가 필요한 경우 여전히 수동 Level Streaming이 쓰입니다.
1. Level Streaming vs World Partition
섹션 제목: “1. Level Streaming vs World Partition”| 항목 | Level Streaming | World Partition |
|---|---|---|
| 설정 방식 | 서브레벨 수동 배치 | 에디터 자동 셀 분할 |
| 제어 단위 | 서브레벨 전체 | 셀(Cell) 단위 |
| 런타임 API | LoadStreamLevel / UnloadStreamLevel | 자동 (플레이어 위치 기반) |
| 적합 규모 | 중소형, 구획이 명확한 맵 | 오픈월드 |
| UE5 권장 | 레거시 / 특수 목적 | 신규 오픈월드 |
2. 서브레벨 등록 (에디터)
섹션 제목: “2. 서브레벨 등록 (에디터)”Windows > Levels 패널 → Levels 드롭다운 → Add Existing Level → 서브레벨 선택 후 Streaming Method: Blueprint (런타임 제어) 또는 Always LoadedStreaming Method를 Blueprint로 설정해야 런타임에 C++/Blueprint로 로드 상태를 제어할 수 있습니다.
3. C++로 서브레벨 로드/언로드
섹션 제목: “3. C++로 서브레벨 로드/언로드”#pragma once#include "CoreMinimal.h"#include "Subsystems/GameInstanceSubsystem.h"#include "LevelStreamingManager.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnLevelLoaded, FName, LevelName);
UCLASS()class MYGAME_API ULevelStreamingManager : public UGameInstanceSubsystem{ GENERATED_BODY()public: UPROPERTY(BlueprintAssignable) FOnLevelLoaded OnLevelLoaded;
UFUNCTION(BlueprintCallable) void LoadLevel(FName LevelName, bool bMakeVisible = true);
UFUNCTION(BlueprintCallable) void UnloadLevel(FName LevelName);
UFUNCTION(BlueprintCallable, BlueprintPure) bool IsLevelLoaded(FName LevelName) const;};#include "LevelStreamingManager.h"#include "Kismet/GameplayStatics.h"#include "Engine/LevelStreamingDynamic.h"
void ULevelStreamingManager::LoadLevel(FName LevelName, bool bMakeVisible){ UWorld* World = GetWorld(); if (!World) return;
FLatentActionInfo LatentInfo; LatentInfo.CallbackTarget = this; LatentInfo.ExecutionFunction = FName("OnLoadComplete"); LatentInfo.Linkage = 0; LatentInfo.UUID = GetTypeHash(LevelName);
UGameplayStatics::LoadStreamLevel( World, LevelName, bMakeVisible, /*bShouldBlockOnLoad=*/false, LatentInfo);}
void ULevelStreamingManager::UnloadLevel(FName LevelName){ UWorld* World = GetWorld(); if (!World) return;
FLatentActionInfo LatentInfo; LatentInfo.CallbackTarget = this; LatentInfo.ExecutionFunction = FName("OnUnloadComplete"); LatentInfo.Linkage = 0; LatentInfo.UUID = GetTypeHash(LevelName) + 1000;
UGameplayStatics::UnloadStreamLevel( World, LevelName, LatentInfo, /*bShouldBlockOnUnload=*/false);}
bool ULevelStreamingManager::IsLevelLoaded(FName LevelName) const{ UWorld* World = GetWorld(); if (!World) return false;
ULevelStreaming* StreamingLevel = UGameplayStatics::GetStreamingLevel( World, LevelName); return StreamingLevel && StreamingLevel->IsLevelLoaded();}
// LatentInfo 콜백 (UFUNCTION 필요)UFUNCTION()void ULevelStreamingManager::OnLoadComplete(){ // 로드 완료 후 처리 UE_LOG(LogTemp, Log, TEXT("Level load complete")); OnLevelLoaded.Broadcast(NAME_None); // 실제 이름 추적 필요}4. 동적 서브레벨 생성 (런타임 인스턴스)
섹션 제목: “4. 동적 서브레벨 생성 (런타임 인스턴스)”에디터에 사전 등록 없이 런타임에 새 레벨 인스턴스를 생성합니다.
bool bSuccess = false;ULevelStreamingDynamic* StreamingLevel = ULevelStreamingDynamic::LoadLevelInstance( GetWorld(), TEXT("/Game/Maps/DungeonRoom"), // 레벨 에셋 경로 FVector(1000.f, 0.f, 0.f), // 배치 위치 FRotator::ZeroRotator, bSuccess);
if (bSuccess && StreamingLevel){ // 로드 완료 콜백 등록 StreamingLevel->OnLevelLoaded.AddDynamic( this, &AMyActor::OnDynamicLevelLoaded);}이 방식은 던전 생성, 인스턴스 방 같은 프로시저럴 레벨 배치에 유용합니다.
5. 스트리밍 상태 폴링
섹션 제목: “5. 스트리밍 상태 폴링”void AMyActor::Tick(float DeltaTime){ Super::Tick(DeltaTime);
ULevelStreaming* Level = UGameplayStatics::GetStreamingLevel( GetWorld(), TEXT("DungeonRoom_01"));
if (!Level) return;
ELevelStreamingState State = ULevelStreamingBlueprintLibrary::GetStreamingState( GetWorld(), TEXT("DungeonRoom_01"));
switch (State) { case ELevelStreamingState::Loading: // 로딩 UI 표시 break; case ELevelStreamingState::LoadedVisible: // 레벨 완전히 표시됨 → 플레이어 이동 허용 AllowPlayerEntry(); break; case ELevelStreamingState::Unloaded: // 언로드 완료 break; default: break; }}6. 로드 화면 블로킹 패턴
섹션 제목: “6. 로드 화면 블로킹 패턴”문 열기처럼 레벨 전환이 즉각적으로 느껴져야 할 때는 로딩이 완료될 때까지 트리거를 블로킹합니다.
void ADoorActor::TryOpen(){ FName TargetLevel = TEXT("NextRoom");
if (ULevelStreamingManager* Mgr = GetGameInstance()->GetSubsystem<ULevelStreamingManager>()) { if (!Mgr->IsLevelLoaded(TargetLevel)) { // 아직 로드 안 됨 → 로드 시작 + 문 잠금 bDoorLocked = true; Mgr->LoadLevel(TargetLevel); Mgr->OnLevelLoaded.AddDynamic(this, &ADoorActor::OnNextRoomReady); } else { OpenDoor(); } }}
void ADoorActor::OnNextRoomReady(FName LevelName){ bDoorLocked = false; OpenDoor();}7. Seamless Travel — 멀티플레이어 레벨 전환
섹션 제목: “7. Seamless Travel — 멀티플레이어 레벨 전환”싱글플레이어에서는 OpenLevel로 레벨 전환이 가능하지만, 멀티플레이어에서는 연결을 유지하면서 레벨을 바꾸는 Seamless Travel이 필요합니다.
// GameMode에서 Seamless Travel 설정AMyGameMode::AMyGameMode(){ // Seamless Travel 활성화 (기본값 false) bUseSeamlessTravel = true;}
// 서버에서 모든 클라이언트를 새 레벨로 이동void AMyGameMode::TravelToNextLevel(const FString& NextMapName){ // "?listen" 옵션으로 서버 소켓 유지 FString TravelURL = FString::Printf(TEXT("/Game/Maps/%s?listen"), *NextMapName); GetWorld()->ServerTravel(TravelURL, /*bAbsolute=*/true);}
// Seamless Travel 단계별 콜백 — AGameMode에서 재정의 가능virtual void GetSeamlessTravelActorList( bool bToEntry, TArray<AActor*>& ActorList) override{ Super::GetSeamlessTravelActorList(bToEntry, ActorList);
// 전환 중 유지할 액터 추가 (PlayerController는 자동 유지) // ActorList.Add(GameStateActor);}// 클라이언트에서 Seamless Travel 완료 감지void AMyPlayerController::PostSeamlessTravel(){ Super::PostSeamlessTravel(); // 새 레벨 로드 완료 후 HUD 재초기화 등 RecreateHUD();}Seamless Travel 흐름:
- 서버
ServerTravel호출 → Entry Level 로드 (전환용 임시 레벨) - 클라이언트 → Entry Level로 자동 이동 (연결 유지)
- Entry Level → 목적지 레벨 이동
PostSeamlessTravel콜백 실행
비교:
| 항목 | OpenLevel (Hard) | ServerTravel (Seamless) |
|---|---|---|
| 연결 유지 | X (재연결) | O |
| 로딩 시간 | 동기 블로킹 | 비동기 |
| 멀티플레이어 적합 | X | O |
| 전환 중 로딩 화면 | 직접 구현 | Entry Level로 처리 |
- Level Streaming은
Windows > Levels패널에서 서브레벨을 등록하고 Streaming Method를Blueprint로 설정해야 런타임 제어가 가능하다. UGameplayStatics::LoadStreamLevel/UnloadStreamLevel은 비동기 LatentAction 기반이므로 완료 콜백을 활용한다.- 프로시저럴 레벨 배치가 필요하면
ULevelStreamingDynamic::LoadLevelInstance를 사용한다. - 멀티플레이어 레벨 전환은
bUseSeamlessTravel = true+ServerTravel로 클라이언트 연결을 유지한다. - 오픈월드 신규 프로젝트라면 World Partition이 더 적합하며, Level Streaming은 구획이 명확한 선형 맵이나 레거시 프로젝트에 적합하다.