Skip to content

UE5 C++ DataAsset & DataTable

개요 — 데이터를 코드에서 분리해야 하는 이유

Section titled “개요 — 데이터를 코드에서 분리해야 하는 이유”

게임 밸런스 수치(캐릭터 스탯, 아이템 가격, 레벨별 경험치)를 C++ 소스코드에 하드코딩하면 기획자가 값을 변경할 때마다 엔지니어가 개입해야 합니다. UDataAssetUDataTable을 사용하면 데이터를 에셋으로 분리해 기획자가 에디터에서 직접 수정할 수 있습니다.

비교 항목UDataAssetUDataTable
구조단일 객체 (트리형)행(Row) 기반 테이블
적합한 데이터아이템 정의, 캐릭터 설정레벨 경험치표, 가격표
CSV 임포트불가가능
Blueprint 편집가능가능
계층 상속C++ 상속으로 확장구조체 정의로 확장

WeaponDataAsset.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "GameplayTagContainer.h"
#include "WeaponDataAsset.generated.h"
UENUM(BlueprintType)
enum class EWeaponType : uint8
{
Melee,
Ranged,
Magic
};
UCLASS(BlueprintType)
class MYGAME_API UWeaponDataAsset : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon|Info")
FName WeaponID;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon|Info")
FText DisplayName;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon|Info")
EWeaponType WeaponType = EWeaponType::Melee;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon|Stats")
float BaseDamage = 10.f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon|Stats")
float AttackSpeed = 1.0f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon|Tags")
FGameplayTagContainer AbilityTags;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon|Visuals")
TSoftObjectPtr<USkeletalMesh> WeaponMesh;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon|Visuals")
TSoftObjectPtr<UParticleSystem> HitEffect;
};

1.2 Primary Data Asset — 에셋 관리자 연동

Section titled “1.2 Primary Data Asset — 에셋 관리자 연동”

UPrimaryDataAsset을 사용하면 Asset Manager와 연동해 비동기 로드가 가능합니다.

CharacterDataAsset.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "CharacterDataAsset.generated.h"
UCLASS(BlueprintType)
class MYGAME_API UCharacterDataAsset : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
// PrimaryDataAsset 식별자 (Asset Manager에서 사용)
virtual FPrimaryAssetId GetPrimaryAssetId() const override
{
return FPrimaryAssetId(TEXT("CharacterData"), GetFName());
}
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Character")
FText CharacterName;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Character|Stats")
float MaxHealth = 100.f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Character|Stats")
float MoveSpeed = 400.f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Character|Mesh")
TSoftObjectPtr<USkeletalMesh> CharacterMesh;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Character|Abilities")
TArray<TSoftClassPtr<class UGameplayAbility>> DefaultAbilities;
};

1.3 DefaultEngine.ini에 Primary Asset Type 등록

Section titled “1.3 DefaultEngine.ini에 Primary Asset Type 등록”
[/Script/Engine.AssetManagerSettings]
+PrimaryAssetTypesToScan=(PrimaryAssetType="CharacterData",AssetBaseClass=/Script/MyGame.CharacterDataAsset,bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Data/Characters")),Rules=(Priority=0,bApplyRecursively=True))
WeaponComponent.cpp
void UWeaponComponent::EquipWeapon(TSoftObjectPtr<UWeaponDataAsset> WeaponDataRef)
{
// 동기 로드 (소용량 에셋, 게임 시작 시)
UWeaponDataAsset* WeaponData = WeaponDataRef.LoadSynchronous();
if (!WeaponData) return;
BaseDamage = WeaponData->BaseDamage;
AttackSpeed = WeaponData->AttackSpeed;
// 비동기 스켈레탈 메시 로드
StreamableManager.RequestAsyncLoad(WeaponData->WeaponMesh.ToSoftObjectPath(),
FStreamableDelegate::CreateUObject(this, &UWeaponComponent::OnMeshLoaded, WeaponData));
}
void UWeaponComponent::OnMeshLoaded(UWeaponDataAsset* WeaponData)
{
if (USkeletalMesh* Mesh = WeaponData->WeaponMesh.Get())
{
WeaponMeshComponent->SetSkeletalMesh(Mesh);
}
}

FTableRowBase를 상속한 구조체를 행(Row) 타입으로 사용합니다.

ItemTableRow.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataTable.h"
#include "ItemTableRow.generated.h"
USTRUCT(BlueprintType)
struct FItemTableRow : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
FText DisplayName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
FText Description;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
int32 BuyPrice = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
int32 SellPrice = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
float Weight = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
TSoftObjectPtr<UTexture2D> Icon;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
bool bIsStackable = false;
};
ItemDatabase.h
UCLASS()
class MYGAME_API AItemDatabase : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, Category = "Data")
TObjectPtr<UDataTable> ItemDataTable;
// FName 키(Row Name)로 아이템 데이터 조회
UFUNCTION(BlueprintCallable, Category = "Data")
const FItemTableRow* GetItemData(FName ItemID) const;
UFUNCTION(BlueprintCallable, Category = "Data")
TArray<FName> GetAllItemIDs() const;
};
ItemDatabase.cpp
const FItemTableRow* AItemDatabase::GetItemData(FName ItemID) const
{
if (!ItemDataTable) return nullptr;
// FindRow<T>: 템플릿으로 타입 안전 조회, 없으면 nullptr
return ItemDataTable->FindRow<FItemTableRow>(ItemID, TEXT("GetItemData"));
}
TArray<FName> AItemDatabase::GetAllItemIDs() const
{
if (!ItemDataTable) return {};
return ItemDataTable->GetRowNames();
}
void AShopManager::PopulateShopInventory()
{
if (!ItemDataTable) return;
// GetAllRows: 모든 행을 TArray로 획득
TArray<FItemTableRow*> AllRows;
ItemDataTable->GetAllRows<FItemTableRow>(TEXT("PopulateShop"), AllRows);
for (FItemTableRow* Row : AllRows)
{
if (Row && Row->BuyPrice > 0)
{
ShopInventory.Add(Row);
}
}
}

DataTable은 CSV 파일로 데이터를 임포트할 수 있습니다.

---,DisplayName,Description,BuyPrice,SellPrice,Weight,bIsStackable
Sword_Iron,철제 검,기본 철제 검,100,50,3.5,false
Potion_HP_Small,소형 HP 포션,HP 50 회복,30,15,0.5,true
Shield_Wood,나무 방패,간단한 나무 방패,80,40,4.0,false

에디터에서 DataTable 에셋을 생성할 때 Row Structure로 FItemTableRow를 지정한 뒤 CSV를 임포트합니다.


4. 비동기 로드 — Asset Manager 활용

Section titled “4. 비동기 로드 — Asset Manager 활용”
MyGameInstance.cpp
void UMyGameInstance::PreloadCharacterData(FName CharacterID)
{
UAssetManager& Manager = UAssetManager::Get();
FPrimaryAssetId AssetId(TEXT("CharacterData"), CharacterID);
TArray<FName> BundlesToLoad = { TEXT("UI") }; // Bundle 단위 로드
Manager.LoadPrimaryAsset(AssetId, BundlesToLoad,
FStreamableDelegate::CreateUObject(this, &UMyGameInstance::OnCharacterDataLoaded, AssetId));
}
void UMyGameInstance::OnCharacterDataLoaded(FPrimaryAssetId AssetId)
{
UAssetManager& Manager = UAssetManager::Get();
UCharacterDataAsset* Data = Cast<UCharacterDataAsset>(
Manager.GetPrimaryAssetObject(AssetId));
if (Data)
{
UE_LOG(LogTemp, Log, TEXT("Loaded: %s (HP: %.0f)"),
*Data->CharacterName.ToString(), Data->MaxHealth);
}
}

상황권장
단일 캐릭터·아이템 설정UDataAsset (또는 UPrimaryDataAsset)
테이블 형태의 게임 데이터UDataTable + CSV 임포트
비동기 로드 + 메모리 관리UPrimaryDataAsset + UAssetManager
런타임 행 추가/삭제TMap<FName, FMyStruct> + DataAsset

핵심 규칙:

  • DataAsset의 메시·텍스처 참조는 TSoftObjectPtr로 선언해 필요할 때 로드 (Soft Reference)
  • DataTable의 FindRow<T> 두 번째 파라미터는 디버그 컨텍스트 문자열 — 비워두면 안 됨
  • Primary Asset 등록은 DefaultEngine.iniPrimaryAssetTypesToScan 섹션에서 경로를 지정