icon

안동민 개발노트

4장 : 월드와 씬 구성

레벨 스트리밍과 World Partition


이전 절에서 액터의 생성과 관리에 대해 알아보며, 게임 월드 내의 객체들을 어떻게 효율적으로 다룰 수 있는지 이해했습니다. 하지만 게임 월드가 매우 커지고 복잡해질 경우, 모든 액터와 모든 지형 데이터를 동시에 메모리에 로드하는 것은 불가능에 가깝습니다. 이 문제를 해결하기 위해 언리얼 엔진은 레벨 스트리밍(Level Streaming)월드 파티션(World Partition)을 제공합니다. 특히 UE5 프로젝트에서는 월드 파티션이 대규모 월드의 기본 경로입니다.


레벨 스트리밍 (Level Streaming)

레벨 스트리밍은 메인 레벨(Persistent Level)에 필요한 서브 레벨(Sub-levels)들을 동적으로 로드하거나 언로드하여 메모리 사용량을 최적화하고 로딩 시간을 단축하는 기술입니다. 플레이어가 월드를 탐색함에 따라, 현재 위치 주변의 레벨만 메모리에 로드하고, 멀리 떨어진 레벨은 언로드하여 리소스를 절약합니다.

레벨 스트리밍의 작동 방식

퍼시스턴트 레벨 (Persistent Level): 항상 메모리에 로드되어 있는 메인 레벨입니다. 게임의 시작 지점, 전역 게임 로직, 공통 리소스 등이 포함될 수 있습니다.

서브 레벨 (Sub-levels): 퍼시스턴트 레벨에 동적으로 스트리밍될 작은 레벨들입니다. 각각의 서브 레벨은 특정 지역, 건물 내부, 던전 등을 포함할 수 있습니다.

스트리밍 볼륨 (Streaming Volume): 플레이어가 특정 볼륨(영역)에 진입하거나 이탈할 때 서브 레벨의 로드/언로드를 트리거하는 투명한 볼륨입니다.

레벨 스트리밍의 장점

  • 메모리 최적화: 모든 레벨 데이터를 동시에 로드할 필요가 없어 메모리 사용량을 크게 줄일 수 있습니다.
  • 로딩 시간 단축: 게임 시작 시 모든 것을 로드하는 대신, 필요한 부분만 점진적으로 로드하므로 초기 로딩 시간이 짧아집니다.
  • 끊김 없는 월드: 로딩 화면 없이 넓은 월드를 연속적으로 탐색하는 경험을 제공할 수 있습니다.
  • 협업 용이: 여러 팀원이 각자의 서브 레벨을 독립적으로 작업하고 나중에 병합할 수 있습니다.

레벨 스트리밍 구현 (C++)

레벨 스트리밍을 C++에서 제어하려면 UGameplayStatics 클래스의 함수들을 사용합니다.

AMyLevelStreamer.h
#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();
};
AMyLevelStreamer.cpp
#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(월드, 레벨이름, 로드 시 보이는지 여부, 로드 후 활성화 여부)
    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());
}
사용법

메인 레벨(Persistent Level)에서 AMyLevelStreamer 액터 블루프린트(BP_MyLevelStreamer)를 배치합니다.

BP_MyLevelStreamer의 디테일 패널에서 Level To Stream Name에 스트리밍할 서브 레벨의 이름을 지정합니다 (예: Level_Forest, Level_City).

레벨 블루프린트나 다른 액터의 로직에서 BP_MyLevelStreamerLoad My Level 또는 Unload My Level 함수를 호출하여 스트리밍을 제어합니다. 일반적으로는 플레이어가 특정 트리거 볼륨(Trigger Volume)에 진입했을 때 이 함수들을 호출하도록 설정합니다.


월드 파티션 (World Partition, UE5 기준)

월드 파티션은 UE5에서 대규모 오픈 월드를 관리하는 기본 시스템입니다. 월드를 스트리밍 셀 단위로 자동 분할하고, 플레이어 위치/데이터 레이어 상태에 맞춰 필요한 셀만 로드합니다.

월드 파티션의 주요 특징

  • 자동 셀 분할: 하나의 월드를 스트리밍 셀로 나누어 메모리 사용량을 안정화합니다.
  • 거리 기반 자동 로드: 플레이어 주변 셀 중심으로 로드/언로드가 이루어집니다.
  • Data Layer: 퀘스트 단계, 시간대, 월드 상태별로 액터 집합을 분리해 제어할 수 있습니다.
  • One File Per Actor: 액터 단위 파일 분리로 충돌을 줄여 협업 효율이 높습니다.

월드 파티션 사용 절차 (UE5)

새로운 레벨을 생성하거나 기존 레벨을 엽니다.

Window > World Settings를 엽니다.

World Partition이 활성화된 템플릿인지 확인합니다. (새 UE5 오픈 월드 템플릿은 기본 활성화)

Window > World Partition 도구를 열어 셀 로드 범위, Data Layer 상태, HLOD 구성을 점검합니다.

UE4 참고: 구버전 프로젝트의 World Composition은 레거시 개념으로만 이해하면 충분합니다. 신규 UE5 프로젝트에서는 월드 파티션 기준으로 설계하는 편이 유지보수에 유리합니다.

C++와 월드 파티션

월드 파티션은 엔진이 스트리밍을 자동 관리하므로, C++에서는 상태 확인과 예외 제어에 집중하는 방식이 일반적입니다.

  • 현재 로드된 레벨 확인: UGameplayStatics::GetCurrentLevelName() 또는 UWorld::GetStreamingLevels()를 사용하여 현재 스트리밍 중인 레벨 목록을 확인할 수 있습니다.
  • 액터 존재 여부 확인: 스트리밍된 레벨 내의 액터에 접근하기 전에 해당 레벨이 제대로 로드되었는지 확인하는 로직을 추가하는 것이 중요합니다.

언제 무엇을 사용할까요?

  • 레벨 스트리밍 (수동)
  • 특정 이벤트에 따라 명확하게 로드/언로드해야 하는 이벤트 기반의 레벨 전환 (예: 건물 내부 진입, 던전 입구).

  • 게임 내의 작은 구역들을 분할하여 관리할 때.

  • 월드 파티션 자동 로직 외에 특정 연출 타이밍 제어가 필요할 때.

  • 월드 파티션 (UE5) (자동)
  • 거대한 오픈 월드를 제작할 때.

  • 플레이어가 넓은 지역을 자유롭게 탐색하는 게임.

  • 수많은 타일 레벨로 구성된 월드를 자동으로 관리하고 싶을 때.

  • 대규모 팀 협업이 중요한 오픈 월드 프로젝트.

대부분의 현대 오픈 월드 게임은 UE5의 월드 파티션을 중심으로 구축됩니다. 월드 컴포지션은 구버전 레거시 프로젝트를 읽을 때 필요한 배경 지식으로 이해하면 충분합니다.


이제 여러분은 언리얼 엔진에서 거대한 게임 월드를 효율적으로 구성하고 관리하기 위한 핵심 기술인 레벨 스트리밍과 월드 파티션의 개념을 이해하셨습니다. 이 기능들을 적절히 활용하면 메모리 문제를 해결하고, 로딩 시간을 줄이며, 끊김 없는 플레이 경험을 제공하는 대규모 게임을 만들 수 있습니다.

목차