icon
4장 : 월드와 씬 구성

레벨 스트리밍과 World Composition


이전 절에서 액터의 생성과 관리에 대해 알아보며, 게임 월드 내의 객체들을 어떻게 효율적으로 다룰 수 있는지 이해했습니다. 하지만 게임 월드가 매우 커지고 복잡해질 경우, 모든 액터와 모든 지형 데이터를 동시에 메모리에 로드하는 것은 불가능에 가깝습니다. 이 문제를 해결하기 위해 언리얼 엔진은 레벨 스트리밍(Level Streaming)월드 컴포지션(World Composition) 이라는 강력한 기능을 제공합니다. 이 기능들은 거대한 오픈 월드를 구현하거나 메모리 제약이 있는 환경에서 게임을 개발할 때 필수적입니다.


레벨 스트리밍 (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 Composition)

월드 컴포지션은 언리얼 엔진 4에서 대규모 오픈 월드를 효율적으로 관리하기 위해 도입된 기능입니다. 이는 기본적으로 레벨 스트리밍을 한 단계 더 추상화하고 자동화한 시스템이라고 볼 수 있습니다. 월드 컴포지션을 사용하면 거대한 월드를 수많은 작은 타일 레벨(Tile Levels)로 나누고, 이 타일 레벨들을 월드 맵 형태로 관리하며, 플레이어의 위치에 따라 자동으로 로드/언로드하도록 설정할 수 있습니다.

언리얼 엔진 5의 월드 파티션 (World Partition): 언리얼 엔진 5에서는 월드 컴포지션의 개념이 더욱 발전된 월드 파티션 시스템으로 대체되었습니다. 월드 파티션은 하나의 거대한 월드 액터를 스트리밍 셀(Streaming Cells)이라는 작은 조각들로 자동 분할하고, 플레이어와의 거리에 따라 필요한 셀만 메모리에 로드하는 방식입니다. UE5의 월드 파티션은 월드 컴포지션보다 설정이 더 간편하고, 대규모 월드 협업에 훨씬 강력한 기능을 제공합니다.

하지만 월드 컴포지션의 기본 원리와 장점은 월드 파티션과 일맥상통하므로, 여기서는 월드 컴포지션의 핵심 개념을 통해 대규모 월드 관리의 필요성을 이해하는 데 집중하겠습니다.

월드 컴포지션의 주요 특징

  • 타일 기반 월드: 거대한 월드를 일정한 크기의 작은 레벨 타일들로 분할합니다.
  • 자동 스트리밍: 플레이어의 위치를 기반으로 주변 타일 레벨들을 자동으로 로드/언로드합니다.
  • 월드 맵 뷰: 레벨들을 2D 월드 맵 형태로 시각화하여 배치하고 관리할 수 있습니다.
  • 협업 용이: 각 타일 레벨을 독립적인 팀원이 작업하고 Git/Perforce 등 버전 관리 시스템으로 쉽게 병합할 수 있습니다.
  • LOD (Level of Detail) 레벨: 멀리 있는 타일 레벨은 저해상도 버전으로 로드하거나, 아예 로드하지 않아 성능을 최적화할 수 있습니다.

월드 컴포지션 활성화 (UE4)

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

Window > World Settings를 엽니다.

World 섹션에서 Enable World Composition 을 체크합니다.

이제 Window > Levels 패널을 열면, World Composition 탭이 보이고 여기서 타일 레벨들을 임포트하거나 추가하여 월드 맵을 구성할 수 있습니다.

C++와 월드 컴포지션/월드 파티션

월드 컴포지션(혹은 월드 파티션)은 주로 엔진 레벨에서 자동으로 레벨 스트리밍을 관리하므로, 개발자가 C++ 코드에서 직접적으로 이를 제어할 필요는 줄어듭니다. 하지만 여전히 어떤 레벨이 로드되었는지 확인하거나, 특정 레벨 내의 액터에 접근하는 로직은 필요할 수 있습니다.

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

언제 무엇을 사용할까요?

  • 레벨 스트리밍 (수동)

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

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

  • 월드 컴포지션/월드 파티션 없이 간단한 동적 로딩이 필요할 때.

  • 월드 컴포지션 (UE4) / 월드 파티션 (UE5) (자동)

  • 거대한 오픈 월드를 제작할 때.

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

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

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

대부분의 현대 오픈 월드 게임은 언리얼 엔진 5의 월드 파티션 시스템을 기반으로 구축됩니다. 이는 언리얼 엔진이 제공하는 가장 효율적인 대규모 월드 관리 솔루션이기 때문입니다. 월드 컴포지션은 UE4에서 유사한 역할을 했으며, 그 개념을 이해하는 것은 월드 파티션의 배경을 이해하는 데 도움이 됩니다.


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