
들어가며
언리얼에서 데이터 중심 설계를 하다 보면 반드시 마주치는 두 클래스가 있습니다. UDataAsset과 UPrimaryDataAsset.
"둘 다 데이터 담는 거 아냐?" 저도 처음엔 그렇게 생각했는데, Asset Manager와의 연동 여부에서 결정적인 차이가 납니다.
이 글은 언리얼 엔진의 기본 구조를 알고 있는 분들을 대상으로 합니다.
UDataAsset
기본 개념
UDataAsset은 UObject를 상속받는 단순한 데이터 컨테이너입니다.
UCLASS()
class UWeaponData : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere)
FString WeaponName;
UPROPERTY(EditAnywhere)
float Damage;
UPROPERTY(EditAnywhere)
UTexture2D* Icon;
};
Content Browser에서 우클릭 → Miscellaneous → Data Asset으로 생성할 수 있습니다.
특징
- 단순한 데이터 홀더: 값을 담고 에디터에서 편집하는 용도
- 하드 레퍼런스: 직접 UPROPERTY로 참조
- Asset Manager와 무관: 로딩/언로딩을 직접 관리
- 소규모 데이터에 적합
사용법
// 하드 레퍼런스로 직접 참조
UPROPERTY(EditAnywhere)
UWeaponData* WeaponData;
// 사용
void AMyCharacter::Attack()
{
float damage = WeaponData->Damage;
UE_LOG(LogTemp, Log, TEXT("Weapon: %s, Damage: %f"), *WeaponData->WeaponName, damage);
}
간단하죠. 에디터에서 슬롯에 끌어다 놓으면 끝입니다. 하지만 이 방식은 에셋이 많아지면 문제가 생깁니다. 하드 레퍼런스이기 때문에, 참조하는 쪽이 로드될 때 데이터도 함께 메모리에 올라옵니다.
UPrimaryDataAsset
기본 개념
UPrimaryDataAsset은 UDataAsset을 상속받으면서 Asset Manager 시스템과 통합됩니다.
UCLASS()
class UItemData : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FString ItemName;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
int32 MaxStackSize;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TSoftObjectPtr<UTexture2D> Icon; // 소프트 레퍼런스!
};
핵심: GetPrimaryAssetId()
UPrimaryDataAsset은 GetPrimaryAssetId()를 자동으로 구현합니다. 이게 Asset Manager가 에셋을 식별하고 관리하는 핵심 키입니다.
// PrimaryDataAsset이 자동으로 해주는 것
FPrimaryAssetId UPrimaryDataAsset::GetPrimaryAssetId() const
{
// 클래스 이름 + 에셋 이름으로 고유 ID 생성
return FPrimaryAssetId(GetClass()->GetFName(), GetFName());
}
// 결과 예시: "ItemData:Sword_01"
일반 UDataAsset에는 이 함수가 없습니다. Asset Manager가 에셋을 찾으려면 이 ID가 필요한데, UDataAsset은 ID 자체가 없으니 관리 대상이 될 수 없는 겁니다.
Asset Manager로 비동기 로딩
// Asset Manager를 통한 비동기 로딩
void AMyGameMode::LoadItem(FPrimaryAssetId ItemId)
{
UAssetManager& Manager = UAssetManager::Get();
Manager.LoadPrimaryAsset(ItemId, {},
FStreamableDelegate::CreateUObject(this, &AMyGameMode::OnItemLoaded, ItemId));
}
void AMyGameMode::OnItemLoaded(FPrimaryAssetId ItemId)
{
UAssetManager& Manager = UAssetManager::Get();
UItemData* Item = Cast<UItemData>(Manager.GetPrimaryAssetObject(ItemId));
if (Item)
{
UE_LOG(LogTemp, Log, TEXT("Loaded: %s"), *Item->ItemName);
}
}
하드 레퍼런스 없이, 필요할 때만 로딩하고 쓸 수 있습니다.
비교 정리
| UDataAsset | UPrimaryDataAsset | |
|---|---|---|
| 상속 | UObject | UDataAsset |
| Asset Manager 연동 | ❌ | ✅ |
| GetPrimaryAssetId() | 없음 | 자동 구현 |
| 비동기 로딩 | 직접 구현해야 함 | Asset Manager 지원 |
| 에셋 탐색/스캔 | 수동 | Asset Manager가 자동 스캔 |
| 번들/쿠킹 규칙 | 수동 관리 | 규칙 기반 자동 |
| 레퍼런스 방식 | 주로 하드 레퍼런스 | 소프트 레퍼런스 권장 |
| 적합한 상황 | 간단한 설정 데이터 | 게임 콘텐츠 (아이템, 스킬 등) |
언제 뭘 써야 하나?
UDataAsset이 적합한 경우
- 게임 설정값 (밸런스 테이블, 난이도 설정)
- 항상 메모리에 올라가 있어도 되는 작은 데이터
- 특정 클래스에서만 직접 참조하는 데이터
- 에셋 수가 적고 (10개 미만) Asset Manager까지 쓸 필요 없는 경우
UPrimaryDataAsset이 적합한 경우
- 아이템, 스킬, 캐릭터 등 게임 콘텐츠 데이터
- 수십~수백 개의 에셋을 체계적으로 관리해야 할 때
- 비동기 로딩이 필요할 때 (오픈 월드, 대규모 인벤토리)
- 에셋 번들링/쿠킹 규칙이 필요할 때
판단 기준
에셋이 10개 이상? → PrimaryDataAsset
비동기 로딩 필요? → PrimaryDataAsset
그 외 간단한 설정? → DataAsset
PrimaryDataAsset 사용 시 필수 설정
Asset Manager가 에셋을 스캔하려면 DefaultGame.ini에 경로를 알려줘야 합니다.
[/Script/Engine.AssetManagerSettings]
+PrimaryAssetTypesToScan=(PrimaryAssetType="ItemData",AssetBaseClass=/Script/MyGame.UItemData,bHasBlueprintClasses=True,Directories=((Path="/Game/Data/Items")))
이 설정이 없으면 Asset Manager가 에셋을 찾지 못합니다. PrimaryDataAsset을 만들어 놓고 "왜 안 되지?" 하는 경우 대부분 이 설정을 빠뜨린 겁니다.
프로젝트 설정 > Game > Asset Manager에서 UI로도 설정할 수 있습니다.
흔한 실수
1. PrimaryDataAsset인데 DefaultGame.ini 설정을 안 함
위에서 말한 그대로입니다. LoadPrimaryAsset()이 null을 반환한다면 가장 먼저 확인하세요.
2. 소규모 프로젝트에서 PrimaryDataAsset 남용
아이템 5개짜리 프로토타입인데 Asset Manager까지 세팅하는 건 오버엔지니어링입니다. DataAsset으로 시작해서, 에셋이 늘어나면 그때 마이그레이션해도 늦지 않습니다.
3. PrimaryDataAsset에 하드 레퍼런스 사용
PrimaryDataAsset의 장점은 필요할 때만 로딩하는 건데, 내부에서 UTexture2D*처럼 하드 레퍼런스를 쓰면 결국 다 같이 로드됩니다. TSoftObjectPtr<>을 쓰세요.
// ❌ 하드 레퍼런스 - PrimaryDataAsset의 의미가 없어짐
UPROPERTY(EditAnywhere)
UTexture2D* Icon;
// ✅ 소프트 레퍼런스 - 필요할 때만 로드
UPROPERTY(EditAnywhere)
TSoftObjectPtr<UTexture2D> Icon;
마무리
정리하면:
- UDataAsset = 단순 데이터 컨테이너. 설정값, 소규모 데이터에 적합
- UPrimaryDataAsset = Asset Manager 연동. 게임 콘텐츠 대량 관리에 적합
- 판단 기준은 "Asset Manager가 필요한가?" 한 가지입니다
더 공부하고 싶다면?
Reference