
우리가 일반적으로 데이터를 관리할 때는 정형적인 구조를 가진 데이터 형식을 사용한다.
예를 들어 엑셀이나 데이터베이스에서 흔히 볼 수 있는 다음과 같은 형태이다.

이처럼 행(Row)과 열(Column)로 구성된 구조화된 데이터 형태를 데이터 테이블(Data Table)이라고 한다.
Unreal Engine에서도 이러한 개념을 기반으로 Data Table이라는 데이터 관리 시스템을 제공한다.
Data Table은 구조화된 데이터를 테이블 형태로 저장하고 관리할 수 있는 Asset이다.
여기서 각 행(Row)은 하나의 데이터 항목을 의미하고,
각 열(Column)은 해당 데이터가 가지는 속성값을 의미한다.
이처럼 Data Table은 여러 개의 데이터를 동일한 구조로 정리하여 관리할 수 있는 데이터 컨테이너라고 볼 수 있다.
게임 개발에서는 캐릭터 능력치나 아이템 스탯과 같은 데이터가 매우 많이 등장한다.
일반적인 방식에서는 이러한 값들이 코드 내부에 직접 작성되는 경우가 많다.
float AttackDamage = 10.0f;
float Speed = 100.0f;
하지만 이런 방식은 데이터가 코드에 강하게 결합되는 문제를 만든다.
예를 들어 다음과 같은 상황이 발생할 수 있다.
Data Table을 사용하면 이러한 데이터를 코드에서 분리하여 별도의 데이터 Asset으로 관리할 수 있다.
Game Logic (C++)
│
│ 데이터 조회
▼
Data Table
즉 코드에서는 데이터를 직접 보관하지 않고, 필요할 때 Data Table에서 데이터를 읽어와 사용하는 방식으로 동작하게 된다.
이러한 구조는 다음과 같은 장점을 만든다.
따라서 Data Table은 Data-Driven Design을 적용할 때 가장 먼저 쉽게 사용되는 데이터 관리 방식이라고 볼 수 있다.
Unreal Engine의 Data Table은 UStruct를 기반으로 데이터를 저장하는 구조를 가진다.
즉, Data Table에 저장되는 모든 데이터는 동일한 구조를 가진 Struct 형태로 정의되어야 한다.
일반적인 테이블에서 Column이 데이터의 속성을 의미한다면, Unreal Engine에서는 이 Column 구조를 Struct로 정의한다.
예를 들어 캐릭터의 기본 능력치를 관리한다고 가정해 보자.
캐릭터는 다음과 같은 스탯을 가진다,
이러한 데이터를 Data Table로 관리하기 위해서는 일반적으로 다음과 같은 과정을 거친다.
Struct 생성
캐릭터의 능력치 구조를 정의하는 Struct를 만든다.
Data Table 생성
위에서 만든 Struct를 기반으로 Data Table을 생성한다.
Data Load
Blueprint 또는 C++에서 Data Table의 Row 데이터를 읽어와 캐릭터의 스탯으로 사용한다.
이제 Blueprint/C++에서 각각 Struct를 생성하고 Data Table을 만들어 실제로 사용하는 방법을 살펴보자.
먼저 콘텐츠 브라우저에서 캐릭터 스탯을 저장하기 위한 Struct를 생성한다.

이후 Struct 내부에 다음과 같은 변수를 추가한다.

이 Struct는 Data Table의 Column 구조가 된다.
즉 Data Table에 들어가는 모든 데이터는 이 Struct와 동일한 형태를 가지게 된다.
Struct를 만들었다면 이제 이를 기반으로 Data Table Asset을 생성할 수 있다.

Data Table 생성 창이 나타나면 Row Struct를 선택해야 한다.
여기에 만들어뒀던 Character Stat Struct를 선택한다.

이렇게 하면 CharacterStat Struct를 기반으로 하는 Data Table이 생성된다.
Data Table을 열면 다음과 같은 형태의 테이블이 나타난다.

여기서 Add 버튼을 눌러 Data Table에 새로운 데이터를 추가할 수 있다.
Data Table에서
를 의미한다.
즉 각 Row는 하나의 캐릭터 데이터를 나타내며, Column은 해당 캐릭터의 능력치 정보를 담는다.
이렇게 Data Table에 데이터를 작성하면 여러 캐릭터의 스탯을 하나의 테이블에서 관리할 수 있다.
또한 데이터를 직접 입력하는 것 외에도, CSV 파일을 가져와 Data Table로 Import하는 방식을 사용할 수도 있다.
이 방법을 사용하면 엑셀과 같은 외부 툴에서 데이터를 작성한 뒤 Unreal Engine으로 쉽게 가져올 수 있다.


먼저 캐릭터의 스탯을 관리하기 위한 Actor Component를 생성한다.

그리고 StatComponent의 BeginPlay에서 Data Table을 읽어 캐릭터의 초기 스탯을 설정할 수 있다.



이제 캐릭터 Blueprint에 StatComponent를 추가한다.

그리고 Character Blueprint의 Details 패널에서

Data Table과 원하는 CharacterType을 설정해주자.
이렇게 하면 게임 시작 시 StatComponent가 Data Table에서 Warrior Row 데이터를 읽어와 캐릭터의 스탯을 초기화한다.
Blueprint와 동일한 구조를 C++에서도 구성할 수 있다.
C++에서는 Struct를 정의하고 Data Table에서 Row 데이터를 읽어 캐릭터의 스탯을 초기화하는 방식으로 구현한다.
전체 흐름은 Blueprint와 동일하다.
먼저 Data Table에서 사용할 Struct를 정의한다.
Data Table에서 사용되는 Struct는 반드시 FTableRowBase를 상속해야 한다.

#pragma once
#include "Engine/DataTable.h"
#include "CoreMinimal.h"
#include "FCharacterStat.generated.h"
USTRUCT(BlueprintType)
struct FCharacterStat : public FTableRowBase
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly)
int32 HP;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
int32 MP;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
int32 Attack;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
int32 Defense;
};
Struct를 만들었다면 Blueprint와 동일하게 Data Table Asset을 생성할 수 있다.

데이터는 블루프린트에서와 동일한 방식으로 추가해주면 된다.

이제 캐릭터 스탯을 관리하기 위한 StatComponent를 C++로 만든다.

#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "StatComponent.generated.h"
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class DATADRIVENDESIGN_API UStatComponent : public UActorComponent
{
GENERATED_BODY()
public:
UStatComponent();
protected:
virtual void BeginPlay() override;
public:
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
UDataTable* CharacterStatTable;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FName CharacterType;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
int32 HP;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
int32 MP;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
int32 Attack;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
int32 Defense;
};
#include "FCharacterStat.h"
void UStatComponent::BeginPlay()
{
Super::BeginPlay();
if (CharacterStatTable == nullptr)
{
return;
}
const FCharacterStat* Stat = CharacterStatTable->FindRow<FCharacterStat>(CharacterType, TEXT(""));
if (Stat)
{
HP = Stat->HP;
MP = Stat->MP;
Attack = Stat->Attack;
Defense = Stat->Defense;
}
}
FindRow 함수를 사용하면 Row Name을 기준으로 Data Table의 데이터를 가져올 수 있다.
즉, CharacterType이 Warrior라면 Warrior Row 데이터를 가져오게 된다.
이제 캐릭터 클래스에 StatComponent를 추가한다.
public:
...
UPROPERTY(EditAnywhere, BlueprintReadOnly)
class UStatComponent* StatComponent;
#include "StatComponent.h"
ADataDrivenDesignCharacter::ADataDrivenDesignCharacter()
{
...
StatComponent = CreateDefaultSubobject<UStatComponent>(TEXT("StatComponent"));
}
이제 에디터에서 마찬가지로 Data Table과 Character Type을 설정해주면 블루프린트와 동일하게 동작한다.

여기서 Character Type을 Enum형태로 만들어서 관리하면 문자열 오타 문제 등을 줄이고 더 쉽게 설정이 가능하다.
Data Table은 여러 데이터를 한 곳에서 관리할 수 있는 매우 편리한 시스템이지만, 사용할 때 몇 가지 주의할 점이 있다.
Data Table의 각 데이터는 Row Name을 기준으로 구분된다.
따라서 Blueprint나 C++ 코드에서 Row Name을 직접 참조하고 있는 경우, Row Name을 변경하면 해당 참조가 정상적으로 동작하지 않을 수 있다.
Data Table은 Struct를 기반으로 생성되기 때문에 Struct 구조가 변경되면 기존 Data Table 데이터에 영향을 줄 수 있다.
예를 들어 Struct에 새로운 변수를 추가하면 기존 Row에는 해당 변수 값이 기본값으로 설정될 수 있으며, 변수 타입이나 이름을 변경하면 기존 데이터가 정상적으로 유지되지 않을 수 있다.
따라서 Struct 설계는 가능한 한 초기에 안정적으로 구성하는 것이 중요하다.
Data Table에서 Asset을 직접 참조할 때는 로딩 방식에도 주의해야 한다.
예를 들어 Data Table의 Struct 안에 다음과 같은 Asset을 직접 참조하도록 구성할 수 있다.
이처럼 Asset을 Data Table row가 Hard Reference를 포함하면, 해당 Asset들이 로딩 의존성에 포함될 수 있다.
즉 다음과 같은 상황이 발생할 수 있다.
Data Table 로드
→ Data Table이 참조하는 Asset 로드
→ 여러 Asset이 동시에 메모리에 로딩
이 경우 프로젝트 규모가 커질수록 불필요한 Asset이 한 번에 로드되어 메모리 사용량이 증가하거나 로딩 시간이 길어질 수 있다.
그래서 Asset을 직접 참조하기보다는 Soft Reference 방식을 사용하는 경우가 많다.
Soft Reference는 Asset의 경로만 저장하고 실제 Asset은 필요할 때 로드하기 때문에, 대규모 프로젝트에서 메모리 관리와 로딩 최적화에 도움이 된다.
Data Table은 정적인 데이터를 관리하는 데 매우 적합한 시스템이다.
예를 들어 다음과 같은 데이터들이 대표적인 사용 사례이다.
이러한 데이터들은 게임 실행 중에 크게 변경되지 않고, 여러 시스템에서 공통으로 참조하기 때문에 Data Table로 관리하면 매우 편리하다.
또한 CSV 파일을 사용하면 엑셀에서 데이터를 관리한 뒤 Unreal Engine으로 쉽게 임포트할 수 있는 장점도 있다.
Data Table은 편리하지만 몇 가지 한계도 존재한다.
예를 들어 다음과 같은 데이터는 관리와 표현이 어렵다.
Data Table은 하나의 Struct 기반으로 구성되기 때문에 서로 다른 형태의 데이터를 유연하게 관리하기 어렵다.
예를 들어
처럼 서로 다른 속성을 가진 데이터를 하나의 Data Table에서 관리하려고 하면 Struct가 지나치게 커질 수 있다.
Data Table은 기본적으로 정적 데이터를 관리하기 위한 시스템이기 때문에 게임 실행 중에 데이터를 수정하거나 저장하는 용도로는 적합하지 않다.
따라서 런타임 중에 데이터의 변경이나 추가가 필요한 경우 Data Table이 아닌 다른 방법을 이용하는 것이 좋다.
Data Table은 협업 환경에서 동시에 수정하기 어렵다는 한계가 있다.
Unreal Engine 프로젝트에서 대부분의 에셋은 Binary Asset 형태로 저장된다.
이러한 파일들은 일반적인 텍스트 파일처럼 Git에서 자동으로 병합(merge)하기 어렵기 때문에, 실제 프로젝트에서는 작업 시에 Checkout를 걸어두는 경우가 많다.
그래서 한 사람이 Data Table을 수정하고 있는 동안 다른 사람은 같은 파일을 수정할 수 없다.
따라서 팀 규모가 커질수록 콘텐츠 작업의 병목(Bottleneck)을 만들 수 있다.
설명 GOAT..