레벨 스트리밍과 World Partition
Level Streaming과 World Partition의 로드 단위를 비교하고 대규모 월드의 영역을 필요할 때 불러옵니다.
액터 생성과 관리에서는 월드 안의 액터를 하나씩 다뤘지만, 월드 자체가 아주 커지면 다른 문제가 생깁니다. 넓은 숲과 도시, 던전이 이어진 오픈 월드의 모든 액터와 지형 데이터를 한꺼번에 메모리에 올리는 것은 사실상 불가능하고, 가능하더라도 게임을 시작할 때 아주 오래 기다려야 합니다. 그래서 플레이어 근처처럼 지금 필요한 부분만 불러오고, 필요 없어진 부분은 내리는 방법이 필요합니다.
언리얼 엔진은 이를 위해 레벨 스트리밍(Level Streaming)과 월드 파티션(World Partition)을 제공합니다. 넓게 이어진 월드에는 월드 파티션을, 제작자가 직접 나눈 서브 레벨 단위로 제어하고 싶을 때는 기존의 레벨 스트리밍을 검토합니다. 이 글에서는 두 방식의 로드 단위와 조건을 비교하고, 로드를 요청한 것과 실제로 플레이할 준비가 된 것을 구분하는 방법을 살펴봅니다.
레벨 스트리밍: 서브 레벨 단위로 불러오기
레벨 스트리밍은 메인 레벨에 필요한 서브 레벨을 실행 중에 불러오거나 내려서 메모리 사용량을 줄이고 로딩 시간을 짧게 하는 기술입니다. 플레이어가 월드를 돌아다니는 동안 현재 위치 주변의 레벨만 메모리에 두고, 멀리 떨어진 레벨은 내려 자원을 아낍니다.
레벨 스트리밍은 세 가지로 이루어집니다. 퍼시스턴트 레벨(Persistent Level)은 그 월드가 유지되는 동안 기준이 되는 메인 레벨로, 게임의 시작 지점, 전역 게임 로직, 공통 자원이 들어갈 수 있습니다. 서브 레벨(sub-level)은 퍼시스턴트 레벨 안으로 불러올 작은 레벨들로, 특정 지역, 건물 내부, 던전 같은 내용을 담습니다. 레벨 스트리밍 볼륨(Level Streaming Volume)은 플레이어의 시점(viewpoint)이 그 볼륨에 들어오거나 나갈 때 연결된 서브 레벨의 로드와 언로드를 요청하는 영역입니다. 판정 기준이 액터의 위치가 아니라 카메라 시점이라는 점을 기억해 둡니다.
이렇게 나누면 모든 레벨 데이터를 한꺼번에 불러올 필요가 없어 메모리를 크게 줄일 수 있고, 처음에 필요한 부분만 불러온 뒤 나머지를 차례로 불러오므로 처음 로딩 시간도 짧아집니다. 로딩 화면 없이 넓은 월드를 이어서 돌아다니게 할 수도 있습니다. 또 여러 팀원이 각자의 서브 레벨을 따로 작업할 수 있어 협업에도 유리합니다.
C++에서 서브 레벨 불러오기
아래 예제는 월드 파티션의 셀이 아니라 기존 서브 레벨의 스트리밍을 제어합니다. 레벨(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();
};헤더에는 불러올 서브 레벨의 이름을 에디터에서 정할 LevelToStreamName과, 블루프린트에서 부를 수 있는 LoadMyLevel, UnloadMyLevel 함수를 선언했습니다. 레벨 이름을 FName으로 둔 것은 아래에서 부를 LoadStreamLevel과 UnloadStreamLevel이 레벨 이름을 FName으로 받기 때문입니다. cpp에서는 두 함수가 이름이 비어 있는지(IsNone()) 먼저 확인한 뒤 요청을 보내는 순서와, 로드와 언로드에 넘기는 마지막 인자들의 차이를 보면 됩니다. 이 액터는 매 프레임 할 일이 없으므로 생성자에서 틱을 끕니다.
#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());
}UGameplayStatics::LoadStreamLevel의 인자는 차례로 월드 컨텍스트, 레벨 이름, 불러온 뒤 보이게 할지, 불러오는 동안 대기할지, 그리고 잠재 작업(latent action) 정보입니다. 이 코드는 네 번째 인자 bShouldBlockOnLoad에 true를 넘기므로, 불러오는 동안 프레임이 멈출 수 있습니다. 반면 UnloadStreamLevel은 마지막 인자에 false를 넘겨 기다리지 않고 요청만 합니다.
두 함수 뒤의 Attempting 로그는 요청을 보냈다는 사실을 알려 줄 뿐, 대상 레벨이 준비되었는지 확인한 결과가 아닙니다. 기본값으로 만든 FLatentActionInfo에는 완료 콜백이 연결되어 있지 않으므로, 기능을 넓힐 때는 완료를 확인하는 방법과 같은 요청이 겹칠 때 구분하는 방법을 따로 설계합니다.
이 액터를 쓰려면 퍼시스턴트 레벨에 AMyLevelStreamer를 부모로 만든 BP_MyLevelStreamer를 배치하고, 디테일 패널의 Level To Stream Name에 스트리밍할 서브 레벨의 이름(예: Level_Forest, Level_City)을 넣습니다. 그다음 레벨 블루프린트나 다른 액터의 로직에서 Load My Level이나 Unload My Level을 부르는데, 보통은 플레이어가 특정 트리거 볼륨에 들어갔을 때 부르도록 연결합니다.
월드 파티션: 월드를 셀로 나눠 불러오기
월드 파티션은 언리얼 엔진 5에서 큰 오픈 월드를 관리하는 기본 시스템입니다. 스트리밍을 켠 월드는 런타임 그리드, 스트리밍 소스(Streaming Source)의 범위, 액터의 Is Spatially Loaded 설정, 런타임 데이터 레이어(Runtime Data Layer)의 상태에 따라 무엇을 불러올지 정합니다. 플레이어만 스트리밍 소스가 될 수 있는 것은 아닙니다.
월드 파티션은 하나의 월드를 셀(cell)이라는 격자 칸으로 자동으로 나눠, 필요한 공간만 불러옵니다. 셀의 크기, 액터 사이의 참조, 로드 범위가 메모리 사용량에 영향을 줍니다. 불러오는 기준은 플레이어 같은 스트리밍 소스의 주변이며, 공간 로드를 쓰지 않도록 설정한 액터는 거리 조건과 관계없이 따로 처리됩니다.
데이터 레이어(Data Layer)는 액터 묶음을 편집용으로 나누거나 실행 중의 상태별로 나누는 기능입니다. 실행 중에 로드를 제어할 때는 런타임 데이터 레이어의 상태를 바꿉니다. 액터당 파일 하나(One File Per Actor, OFPA)는 액터마다 파일을 따로 저장해, 여러 사람이 같은 레벨을 고칠 때 파일 충돌을 줄이는 협업 기능입니다.
비슷해 보이지만 다른 기능도 있습니다. 계층적 LOD(Hierarchical Level of Detail, HLOD)는 멀리 있는 셀을 단순한 대리 모델로 보여 주는 시각 기능이고, 액터당 파일 하나는 편집 파일을 나누는 기능입니다. 두 기능 모두 실행 중에 무엇이 불러와져 있는지와는 다른 이야기입니다.
월드 파티션 켜고 살펴보기
새 레벨을 만들거나 기존 레벨을 연 뒤, Window 메뉴의 World Settings를 열어 월드 파티션이 켜져 있는지 확인합니다. 언리얼 엔진 5의 오픈 월드(Open World) 템플릿으로 만든 레벨은 처음부터 켜져 있습니다. 그다음 Window 메뉴의 World Partition 도구를 열어 셀의 로드 범위, 데이터 레이어 상태, HLOD 구성을 점검합니다.
기존의 일반 레벨을 월드 파티션으로 바꾸는 변환 절차는 Epic의 공식 문서에 따로 나와 있으며, Tools 메뉴의 Convert Level이나 변환용 커맨드렛(commandlet)을 씁니다. World Settings를 열었다고 일반 레벨이 자동으로 변환되지는 않으며, 변환할 때는 원본을 보존하고 결과를 따로 검토할 준비를 합니다. 이 글은 변환 명령을 실제로 실행한 결과를 보여 주지 않습니다. 또 언리얼 엔진 4의 월드 컴포지션(World Composition)은 이제 레거시 기능이므로 개념만 알아 두면 충분합니다.
어떤 구조를 쓸지는 월드의 크기뿐 아니라, 연출을 얼마나 명시적으로 제어해야 하는지와 팀의 제작 방식에 맞춰 고릅니다.
C++에서 월드 파티션 다루기
월드 파티션은 엔진이 스트리밍을 자동으로 관리하므로, C++에서는 상태를 확인하고 예외적인 경우를 제어하는 데 집중하는 것이 일반적입니다. GetCurrentLevelName은 현재 레벨 이름 하나를 돌려주고, GetStreamingLevels는 스트리밍 객체의 목록을 돌려주므로 각 항목의 로드와 표시 상태를 따로 확인해야 합니다. 월드 파티션에서는 해당 스트리밍 소스의 완료 상태 같은 파티션 API로 필요한 영역이 준비되었는지 판단합니다. 스트리밍된 레벨의 액터에 접근하기 전에는 그 레벨이 제대로 불러와졌는지 확인하는 로직을 꼭 넣습니다.
요청과 준비를 구분하기
로드 요청·로드 상태·플레이 준비를 구분합니다
등록한 Level_Forest 요청→로드/표시/필요한 게임 준비 검사→진행 또는 대기/실패의 요청 상태이다. Attempting 로그를 완료로 바꾸지 않고 WP 단위는 별도 보존한다.
UNREAL C++ · CH4-4
로드 요청·로드 상태·플레이 준비를 구분합니다
Level Streaming의 서브 레벨과 World Partition의 셀·액터 집합은 로드 단위와 조건이 다릅니다. 필요한 영역의 준비 상태를 별도로 확인합니다.
이 흐름은 본문의 서브 레벨 요청 예입니다. 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는 편집 협업 기능입니다. 본문의 Attempting 로그와 기본 FLatentActionInfo는 준비 완료 콜백이 아니며, 로드 예제는 bShouldBlockOnLoad=true입니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 기존 LevelStreaming 예제는 현재 월드에 서브 레벨을 등록하고 Blueprint 방식으로 제어하는 설정을 전제로 한다. 로드 요청 뒤 로드·표시 및 필요한 게임 준비 상태를 확인한다. 조건을 만족하면 다음 플레이 단계로 진행하고 아니면 대기·실패 원인을 확인한다. 요청에서 준비 성공으로 바로 가는 화살표는 없다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 레벨 요청 등록된 Level_Forest의 로드를 요청한다. |
|
|
| 준비 검사 로드·표시와 필요한 게임 준비를 확인한다. |
|
|
| 진행 허용 선택한 조건이 맞을 때 후속 동작을 진행한다. |
|
|
로드 요청·로드 상태·플레이 준비를 구분합니다
등록한 Level_Forest 요청→로드/표시/필요한 게임 준비 검사→진행 또는 대기/실패의 요청 상태이다. Attempting 로그를 완료로 바꾸지 않고 WP 단위는 별도 보존한다.
UNREAL C++ · CH4-4
로드 요청·로드 상태·플레이 준비를 구분합니다
Level Streaming의 서브 레벨과 World Partition의 셀·액터 집합은 로드 단위와 조건이 다릅니다. 필요한 영역의 준비 상태를 별도로 확인합니다.
이 흐름은 본문의 서브 레벨 요청 예입니다. 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는 편집 협업 기능입니다. 본문의 Attempting 로그와 기본 FLatentActionInfo는 준비 완료 콜백이 아니며, 로드 예제는 bShouldBlockOnLoad=true입니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 기존 LevelStreaming 예제는 현재 월드에 서브 레벨을 등록하고 Blueprint 방식으로 제어하는 설정을 전제로 한다. 로드 요청 뒤 로드·표시 및 필요한 게임 준비 상태를 확인한다. 조건을 만족하면 다음 플레이 단계로 진행하고 아니면 대기·실패 원인을 확인한다. 요청에서 준비 성공으로 바로 가는 화살표는 없다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 레벨 요청 등록된 Level_Forest의 로드를 요청한다. |
|
|
| 준비 검사 로드·표시와 필요한 게임 준비를 확인한다. |
|
|
| 진행 허용 선택한 조건이 맞을 때 후속 동작을 진행한다. |
|
|
위 그림의 위쪽은 서브 레벨을 요청한 뒤 그 대상이 준비되었는지 확인하는 흐름입니다. 현재 월드에 서브 레벨을 등록하고 Blueprint 방식으로 제어하도록 설정한 상태에서 로드를 요청하면 Attempting 로그가 남습니다. 그다음 로드와 표시, 그리고 필요한 게임 준비 조건을 확인해, 조건을 만족하면 필요한 대상의 유효성을 유지하며 다음 플레이 단계로 진행하고, 만족하지 않으면 대기하거나 대상 이름과 상태를 다시 확인합니다. 그림에는 요청에서 곧바로 준비 성공으로 가는 화살표가 없습니다.
그림의 표는 두 방식의 로드 단위와 준비 판정을 나란히 비교합니다. 레벨 스트리밍의 로드 단위는 현재 월드에 등록한 서브 레벨이고, 월드 파티션은 런타임 그리드의 셀과 액터 묶음입니다. 레벨 스트리밍은 코드, 블루프린트, 볼륨과 스트리밍 방식으로 로드를 정하고, 월드 파티션은 스트리밍 소스와 공간 로드 설정, 런타임 데이터 레이어로 정합니다. 준비를 확인할 때 레벨 스트리밍은 로드 상태와 표시 상태를 구분하고, 월드 파티션은 필요한 소스 범위의 스트리밍 완료와 활성 상태를 확인합니다.
표의 아래 두 줄은 참조와 상태 보관에 관한 것입니다. 레벨 스트리밍에서는 언로드될 액터를 쓸 때 유효성을 확인하고, 월드 파티션에서는 액터 사이의 참조 때문에 함께 불러와지는 묶음도 확인합니다. 진행 상태처럼 남겨야 할 값은 두 방식 모두 언로드되는 셀이나 액터의 수명과 분리해 따로 보관합니다. 그림 아래의 설명처럼 불러온 상태(Loaded)와 보이는 상태는 다르고, 표시되었다고 충돌, 내비게이션, 게임 데이터까지 모두 준비되었다는 뜻은 아니며, 두 로드 단위를 한 API로 제어하지도 않습니다. 단계 그림도 같은 순서로, Level_Forest를 요청하고, 로드와 표시와 게임 준비를 검사한 뒤, 조건이 맞을 때만 다음 동작을 진행합니다.
구체적으로 따라가 봅시다. 퍼시스턴트 레벨에 Level_Forest를 등록하고 Blueprint 스트리밍 방식으로 둔 뒤, 예제 액터의 Level To Stream Name에도 같은 이름을 넣습니다. LoadMyLevel()을 부르면 Attempting 로그는 함수가 로드 요청을 보냈다는 사실만 알려 줍니다. 숲이 실제로 불러와지고 보이는지, 필요한 액터가 유효한지, 플레이에 필요한 충돌과 내비게이션이 준비되었는지는 따로 확인해야 합니다. 또 항상 불러오기(Always Loaded)로 설정한 서브 레벨은 이 예의 동적 요청이나 볼륨 제어를 같은 방식으로 따르지 않습니다.
월드 파티션을 쓰는 다른 월드라면, 목적지에 스트리밍 소스를 준비하고 필요한 목표 상태를 정합니다. 그 소스 범위가 완료되었다는 확인은 월드의 모든 셀이 완료되었다는 판정이 아닙니다. 고속 이동이나 순간 이동(텔레포트)이 있다면 목적지에 미리 스트리밍 소스를 두어 준비하는 방식을 검토합니다. 대상 기기에서 입출력 지연, 프레임 정지, 메모리, 충돌과 내비게이션 준비, HLOD 전환을 함께 확인해야 하며, 스트리밍을 쓴다는 것만으로 끊김 없는 플레이가 보장되지는 않습니다.
정리
- 레벨 스트리밍은 퍼시스턴트 레벨에 등록한 서브 레벨을 코드, 블루프린트, 볼륨으로 불러오고 내리며, 레벨 스트리밍 볼륨은 카메라 시점을 기준으로 판정합니다.
LoadStreamLevel에bShouldBlockOnLoad=true를 넘기면 불러오는 동안 프레임이 멈출 수 있고, 요청 뒤의 로그는 준비 완료가 아닙니다.- 월드 파티션은 월드를 셀로 나눠 스트리밍 소스 주변을 불러오며, 공간 로드 설정과 런타임 데이터 레이어로 로드를 제어합니다.
- HLOD는 먼 셀의 시각적 대리 표현, 액터당 파일 하나는 편집 협업 기능으로, 둘 다 실행 중의 로드 상태와는 다릅니다.
- 로드 요청, 로드 상태, 표시 상태, 플레이 준비는 서로 다른 단계이므로 필요한 조건을 따로 확인합니다.
- 언로드될 액터에 남길 진행 상태는 그 액터와 분리해 보관하고, 언로드 뒤에 쓰는 참조는 유효성을 다시 확인합니다.
연습 문제
1. 보물상자의 진행도
A 지역의 보물상자를 연 뒤 다른 지역으로 이동했더니 A의 액터들이 언로드되었습니다. 퀘스트 진행도는 계속 남아 있어야 합니다. 상자 액터의 멤버 변수에만 진행도를 저장해도 충분할까요?
해설 보기
충분하지 않습니다. 상자 액터가 언로드되면 그 멤버 변수도 함께 사라지므로, 다시 불러온 상자는 처음 상태로 돌아갑니다. 언로드될 액터와 따로 유지할 상태의 보관 위치와 복원 시점을 정해야 합니다.
예를 들어 같은 실행 동안 상태를 유지하는 객체가 상자의 식별자별로 진행도를 보관하고, 상자가 다시 불러와질 때 그 값을 적용하는 흐름을 만들 수 있습니다. 상자를 가리키던 원래 참조도 언로드 뒤에는 쓰기 전에 유효성을 다시 확인합니다.
2. 편집기에서 셀을 불러왔으니
월드 파티션 창에서 목적지의 셀을 불러와 편집했고, 액터당 파일 하나 방식으로 저장도 했습니다. 이것으로 실행 중에 플레이어가 목적지에 도착했을 때의 준비도 끝났다고 할 수 있을까요?
해설 보기
그렇지 않습니다. 편집기에서 셀을 불러오는 것은 편집을 위한 작업이고, 액터당 파일 하나는 파일을 나눠 저장하는 협업 기능입니다. 둘 다 실행 중에 스트리밍 소스 범위가 불러와지고 활성화되었는지, 게임에 필요한 준비가 되었는지를 확인해 주지 않습니다.
실행 중의 준비는 목적지의 스트리밍 소스 범위가 완료되었는지와, 필요한 액터, 충돌, 내비게이션이 준비되었는지를 따로 확인해야 합니다.
3. 로그가 찍혔으니 바로 쓴다
LoadMyLevel()을 부르고 Attempting to load level 로그가 찍히자마자, 같은 함수 안에서 그 레벨에 있는 문 액터를 찾아 열려고 했습니다. 어떤 문제가 생길 수 있을까요?
해설 보기
로그는 로드를 요청했다는 사실만 알려 주므로, 그 시점에 레벨과 문 액터가 준비되었다고 가정할 수 없습니다. 이 예제는 bShouldBlockOnLoad=true를 넘기지만, LoadStreamLevel은 로드를 예약하는 플래그만 세우고 바로 돌아옵니다. 기다리는 로드는 엔진이 다음에 레벨 스트리밍 상태를 갱신할 때 일어나므로, 같은 함수 안에서는 레벨이 아직 불러와지지 않았을 수 있습니다. 로드가 끝난 뒤에도 표시 상태와 액터의 유효성, 플레이에 필요한 준비는 따로 확인해야 합니다.
문을 찾기 전에 대상 레벨의 로드와 표시 상태를 확인하고, 찾은 문 액터가 유효한지 검사한 뒤에 엽니다. 기다리지 않는 방식으로 바꾼다면, 완료를 알려 주는 콜백이나 상태 확인을 두고 그 뒤에 문을 다룹니다.
레벨 스트리밍의 동작은 Epic의 Level Streaming 문서에서, 월드 파티션과 기존 레벨의 변환 절차는 World Partition 문서에서 확인할 수 있습니다.
이것으로 4장을 마칩니다. 4장에서는 빛, 카메라, 메시로 장면을 구성하는 방법, 레벨 블루프린트와 C++ 클래스 가운데 로직을 둘 위치를 고르는 기준, 액터를 생성하고 찾고 파괴할 때의 수명 규칙, 그리고 레벨 스트리밍과 월드 파티션으로 큰 월드를 나눠 불러오는 방법을 살펴봤습니다. 다음 장의 충돌 채널과 프로파일 설정부터는 충돌 채널과 프로파일, 트리거와 오버랩 이벤트, 물리 시뮬레이션, 라인 트레이스로 월드 안의 상호작용을 만듭니다.