Data Layers 시스템
동적 월드 변화를 위한 런타임 콘텐츠 활성화 시스템
Data Layers 개요
액터 그룹의 논리적 관리와 동적 로딩
Data Layers는 World Partition 내에서 액터를 논리적으로 그룹화하고, 에디터와 런타임에서 동적으로 로드/언로드할 수 있게 하는 시스템입니다.
| 레이어 타입 | 용도 | 런타임 제어 |
|---|---|---|
| Editor | 에디터 내 조직화, 가시성 토글 | 불가능 |
| Runtime | 게임플레이 기반 동적 로딩 | 가능 |
활용 사례
퀘스트 기반 월드 변화
퀘스트 진행에 따라 마을이 파괴되거나 재건되는 등의 월드 상태 변화 구현
시간대별 콘텐츠
낮/밤에 따라 다른 NPC, 몬스터, 이벤트 활성화
멀티플레이어 인스턴스
플레이어별 개인 인스턴스 영역 관리
시즌 이벤트
특정 기간에만 활성화되는 축제, 이벤트 콘텐츠
Data Layer Asset 생성
에디터에서의 Data Layer 설정
1. Data Layer Asset 생성
// Content Browser에서 생성
Content Browser > Add > Miscellaneous > Data Layer Asset
// 또는 World Partition 에디터에서
Window > World Partition > Data Layers
> 우클릭 > Create New Data Layer
2. Data Layer 속성 설정
// Data Layer Asset 설정
Layer Type: Runtime // Editor 또는 Runtime
Initial Runtime State: Unloaded // 초기 상태
Is Runtime Only: false // 에디터에서도 표시 여부
Debug Color: (원하는 색상) // 에디터 시각화 색상
// Initial Runtime State 옵션:
// - Unloaded: 시작 시 로드하지 않음
// - Loaded: 로드하되 비가시
// - Activated: 로드하고 가시 상태
3. 액터에 Data Layer 할당
// 액터 선택 후 Details 패널에서
Details > World Partition > Data Layers
> + 버튼 클릭
> Data Layer Asset 선택
// 또는 여러 액터 일괄 할당
여러 액터 선택 > 우클릭 > Assign to Data Layer
C++ 런타임 제어
코드를 통한 Data Layer 상태 변경
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "WorldPartition/DataLayer/DataLayerAsset.h"
#include "DataLayerManager.generated.h"
/**
* Data Layer 상태 관리 서브시스템
*/
UCLASS()
class MYGAME_API UDataLayerManager : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
// Data Layer 활성화
UFUNCTION(BlueprintCallable, Category = "Data Layer")
void ActivateDataLayer(UDataLayerAsset* DataLayer, UWorld* World);
// Data Layer 비활성화
UFUNCTION(BlueprintCallable, Category = "Data Layer")
void DeactivateDataLayer(UDataLayerAsset* DataLayer, UWorld* World);
// Data Layer 상태 설정
UFUNCTION(BlueprintCallable, Category = "Data Layer")
void SetDataLayerState(UDataLayerAsset* DataLayer,
EDataLayerRuntimeState NewState, UWorld* World);
// Data Layer 현재 상태 확인
UFUNCTION(BlueprintCallable, Category = "Data Layer")
EDataLayerRuntimeState GetDataLayerState(UDataLayerAsset* DataLayer,
UWorld* World) const;
private:
// 퀘스트 ID와 Data Layer 매핑
UPROPERTY()
TMap<FName, UDataLayerAsset*> QuestDataLayers;
};
#include "DataLayerManager.h"
#include "WorldPartition/DataLayer/DataLayerSubsystem.h"
void UDataLayerManager::ActivateDataLayer(
UDataLayerAsset* DataLayer, UWorld* World)
{
SetDataLayerState(DataLayer, EDataLayerRuntimeState::Activated, World);
}
void UDataLayerManager::DeactivateDataLayer(
UDataLayerAsset* DataLayer, UWorld* World)
{
SetDataLayerState(DataLayer, EDataLayerRuntimeState::Unloaded, World);
}
void UDataLayerManager::SetDataLayerState(
UDataLayerAsset* DataLayer,
EDataLayerRuntimeState NewState,
UWorld* World)
{
if (!DataLayer || !World)
{
UE_LOG(LogTemp, Warning, TEXT("Invalid DataLayer or World"));
return;
}
if (UDataLayerSubsystem* DataLayerSubsystem =
UWorld::GetSubsystem<UDataLayerSubsystem>(World))
{
DataLayerSubsystem->SetDataLayerInstanceRuntimeState(
DataLayer,
NewState
);
UE_LOG(LogTemp, Log, TEXT("DataLayer '%s' state changed to %d"),
*DataLayer->GetName(),
(int32)NewState);
}
}
EDataLayerRuntimeState UDataLayerManager::GetDataLayerState(
UDataLayerAsset* DataLayer,
UWorld* World) const
{
if (!DataLayer || !World)
{
return EDataLayerRuntimeState::Unloaded;
}
if (UDataLayerSubsystem* DataLayerSubsystem =
UWorld::GetSubsystem<UDataLayerSubsystem>(World))
{
return DataLayerSubsystem->GetDataLayerInstanceRuntimeState(DataLayer);
}
return EDataLayerRuntimeState::Unloaded;
}
실전 예제: 퀘스트 기반 월드 변화
마을 파괴/재건 시스템 구현
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "WorldPartition/DataLayer/DataLayerAsset.h"
#include "QuestWorldManager.generated.h"
// 월드 상태 열거형
UENUM(BlueprintType)
enum class EVillageState : uint8
{
Normal, // 평화로운 상태
UnderAttack, // 공격 중
Destroyed, // 파괴됨
Rebuilt // 재건됨
};
UCLASS()
class MYGAME_API AQuestWorldManager : public AActor
{
GENERATED_BODY()
public:
AQuestWorldManager();
// 마을 상태 변경
UFUNCTION(BlueprintCallable, Category = "Quest")
void SetVillageState(EVillageState NewState);
UFUNCTION(BlueprintPure, Category = "Quest")
EVillageState GetCurrentVillageState() const { return CurrentState; }
protected:
virtual void BeginPlay() override;
// 각 상태별 Data Layer
UPROPERTY(EditAnywhere, Category = "Data Layers")
UDataLayerAsset* NormalVillageLayer;
UPROPERTY(EditAnywhere, Category = "Data Layers")
UDataLayerAsset* AttackEffectsLayer;
UPROPERTY(EditAnywhere, Category = "Data Layers")
UDataLayerAsset* DestroyedVillageLayer;
UPROPERTY(EditAnywhere, Category = "Data Layers")
UDataLayerAsset* RebuiltVillageLayer;
private:
EVillageState CurrentState;
void UpdateDataLayers();
void SetLayerState(UDataLayerAsset* Layer, bool bActivate);
};
#include "QuestWorldManager.h"
#include "WorldPartition/DataLayer/DataLayerSubsystem.h"
AQuestWorldManager::AQuestWorldManager()
{
PrimaryActorTick.bCanEverTick = false;
CurrentState = EVillageState::Normal;
}
void AQuestWorldManager::BeginPlay()
{
Super::BeginPlay();
UpdateDataLayers();
}
void AQuestWorldManager::SetVillageState(EVillageState NewState)
{
if (CurrentState == NewState) return;
CurrentState = NewState;
UpdateDataLayers();
UE_LOG(LogTemp, Log, TEXT("Village state changed to: %d"), (int32)NewState);
}
void AQuestWorldManager::UpdateDataLayers()
{
// 모든 레이어 비활성화
SetLayerState(NormalVillageLayer, false);
SetLayerState(AttackEffectsLayer, false);
SetLayerState(DestroyedVillageLayer, false);
SetLayerState(RebuiltVillageLayer, false);
// 현재 상태에 맞는 레이어 활성화
switch (CurrentState)
{
case EVillageState::Normal:
SetLayerState(NormalVillageLayer, true);
break;
case EVillageState::UnderAttack:
SetLayerState(NormalVillageLayer, true);
SetLayerState(AttackEffectsLayer, true);
break;
case EVillageState::Destroyed:
SetLayerState(DestroyedVillageLayer, true);
break;
case EVillageState::Rebuilt:
SetLayerState(RebuiltVillageLayer, true);
break;
}
}
void AQuestWorldManager::SetLayerState(UDataLayerAsset* Layer, bool bActivate)
{
if (!Layer) return;
if (UDataLayerSubsystem* Subsystem =
UWorld::GetSubsystem<UDataLayerSubsystem>(GetWorld()))
{
EDataLayerRuntimeState State = bActivate
? EDataLayerRuntimeState::Activated
: EDataLayerRuntimeState::Unloaded;
Subsystem->SetDataLayerInstanceRuntimeState(Layer, State);
}
}
성능 고려사항
Data Layer 사용 시 최적화 팁
너무 많은 에셋을 동시에 로드하면 성능 저하가 발생합니다. Runtime Data Layer의 동시 활성화 수를 모니터링하세요.
- 레이어 세분화 - 너무 큰 레이어보다 적절히 분할된 작은 레이어가 효율적
- Loaded 상태 활용 - 프리로딩이 필요할 때 Loaded(비가시) 상태 사용
- 동시 로드 제한 - 한 번에 전환되는 레이어 수 제한
- 공유 에셋 주의 - 여러 레이어에서 공유하는 에셋의 레퍼런스 관리
핵심 요약
- Data Layers는 액터 그룹의 런타임 로드/언로드를 위한 시스템
- Runtime 타입 레이어만 게임플레이 중 동적 제어 가능
- UDataLayerSubsystem의
SetDataLayerInstanceRuntimeState()로 상태 변경 - 세 가지 상태: Unloaded(언로드), Loaded(로드/비가시), Activated(로드/가시)
- 퀘스트, 시간대, 이벤트 등 동적 월드 변화 구현에 활용
도전 과제
배운 내용을 직접 실습해보세요
에디터에서 Data Layer를 생성(예: DL_QuestArea, DL_SeasonalContent)하고, 관련 액터들을 각 레이어에 할당하세요. 런타임에 Data Layer를 활성화/비활성화하며 콘텐츠 전환을 확인하세요.
UDataLayerManager::SetDataLayerRuntimeState()를 사용하여 퀘스트 진행에 따라 특정 Data Layer를 Activated/Deactivated로 전환하는 코드를 작성하세요.
RPG의 시즌 이벤트(여름 축제, 겨울 던전)를 Data Layer로 구성하세요. 서버 시간에 따라 자동으로 Data Layer가 전환되고, 스트리밍과 연동되어 메모리를 효율적으로 관리하는 시스템을 설계하세요.