1. 개념/설계: DataAsset vs PrimaryDataAsset
가장 중요한 핵심은 "언리얼 엔진의 '에셋 매니저(Asset Manager)'가 이 데이터를 어떻게 추적하고 로드하는가?"이다.
UDataAsset(데이터 에셋):- 단순한 데이터 보관함.
- 에셋 매니저가 고유 ID로 추적하지 않는다.
- 주로 다른 블루프린트나 C++ 클래스에서 하드 레퍼런스(Hard Reference)로 직접 연결해서 사용할 때 적합하다. (예: 특정 캐릭터가 항상 고정적으로 들고 있는 기본 무기 스탯)
UPrimaryDataAsset(프라이머리 데이터 에셋):UDataAsset을 상속받은 특수한 보관함으로, 고유한 주민등록번호(PrimaryAssetId)를 가진다.- 게임이 시작될 때 언리얼의 에셋 매니저가 이 에셋들의 목록을 쫙 스캔해서 카탈로그를 만든다.
- 데이터가 필요할 때 메모리에 무겁게 미리 올려두지 않고, ID를 통해 비동기 로딩(Async Loading)을 하거나 소프트 레퍼런스(Soft Reference)로 관리할 때 압도적으로 유리하다. 수백 개의 아이템이 존재하는 RPG의 인벤토리 시스템 등에 필수적.

2. 헤더 (.h) 구현
이제 실제 RPG 게임의 '무기 아이템' 데이터를 만든다고 가정하고 코드를 작성해 보겠습니다. 무기 아이템은 많아질 수 있으므로 UPrimaryDataAsset을 사용하는 것이 모범 답안입니다.
RPGWeaponItem.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h" // UPrimaryDataAsset이 포함된 헤더
#include "RPGWeaponItem.generated.h"
// Forward Declaration (전방 선언): 컴파일 시간 최적화를 위해 헤더 include를 최소화합니다.
class UTexture2D;
class UStaticMesh;
/**
* 무기 아이템 데이터를 정의하는 Primary Data Asset 클래스입니다.
*/
UCLASS(BlueprintType)
class YOURGAME_API URPGWeaponItem : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
// 에셋 매니저가 이 에셋을 고유하게 식별하기 위해 반드시 오버라이드해야 하는 함수입니다.
virtual FPrimaryAssetId GetPrimaryAssetId() const override;
public:
// TEXT() 매크로를 사용하지 않는 순수 식별자용 문자열이므로 FName을 사용합니다.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Info")
FName ItemName;
// 공격력 같은 수치 데이터
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Stats")
int32 AttackPower;
// 메모리 최적화를 위해 하드 레퍼런스(UTexture2D*) 대신 소프트 레퍼런스 사용!
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Visual")
TSoftObjectPtr<UTexture2D> ItemIcon;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Visual")
TSoftObjectPtr<UStaticMesh> WeaponMesh;
};
💡 Rider IDE 팁 (VS 2022 키맵):
UPrimaryDataAsset이 정확히 어떤 기능을 갖는지 궁금하다면 커서를 올리고F12(정의로 이동)를 눌러 엔진 코드를 확인해 보세요.GetPrimaryAssetId()를 타이핑하다가Alt + Enter를 누르면 Rider가 선언 및 C++ 구현부까지 자동으로 생성해 주어 생산성이 크게 오릅니다.
3. 소스 (.cpp) 구현
RPGWeaponItem.cpp
#include "RPGWeaponItem.h"
// 구조화된 로그(Structured Logging)를 위해 추가합니다. Build.cs에 꼭 추가할 필요는 없지만 Core에 포함되어 있습니다.
#include "Logging/StructuredLog.h"
FPrimaryAssetId URPGWeaponItem::GetPrimaryAssetId() const
{
// "Weapon"이라는 타입 이름과, 현재 에셋의 이름(GetFName)을 조합하여 고유 ID를 생성합니다.
// 예: Weapon:Sword_Basic
FPrimaryAssetId AssetId = FPrimaryAssetId(FPrimaryAssetType("Weapon"), GetFName());
// 디버깅을 위한 최신 UE5 로그 포맷 사용 예시
// UE_LOGFMT(LogTemp, Log, "Weapon Asset ID Generated: {0}", AssetId.ToString());
return AssetId;
}
4. 에디터 설정 (필수!)
코드를 컴파일(Ctrl + Shift + B)하고 에디터로 돌아왔다면, 에셋 매니저에게 *"Weapon이라는 타입의 에셋을 찾아라!"*라고 알려주어야 합니다.
- 언리얼 에디터에서 편집(Edit) -> 프로젝트 세팅(Project Settings)을 엽니다.
- 좌측 패널에서 Game -> Asset Manager (에셋 매니저)를 클릭합니다.
- Primary Asset Types to Scan (스캔할 프라이머리 에셋 타입) 배열에 항목을 추가(
+)합니다. - Primary Asset Type에 우리가 코드에 적은
"Weapon"을 입력합니다. - Asset Base Class에 우리가 만든
RPGWeaponItem을 선택합니다. - Directories에 이 데이터 에셋들을 저장할 폴더(예:
/Game/Data/Weapons)를 지정해 줍니다.
이렇게 하면 게임 로딩 시 엔진이 해당 폴더를 가볍게 스캔하여 카탈로그를 완성합니다!
⚠️ 시니어의 추가 조언 (메모리와 참조)
만약 UPrimaryDataAsset 안에 UTexture2D* MyIcon; 처럼 포인터로(하드 레퍼런스) 이미지를 연결해 두었다면, 이 데이터 에셋이 메모리에 올라오는 순간 아이콘 텍스처와 무기 메쉬까지 전부 한 번에 메모리에 강제로 올라오게 됩니다. 수십 개의 무기가 있다면 심각한 메모리 낭비와 긴 로딩 타임(히치 현상)을 유발하죠.
따라서 위 코드처럼 반드시 TSoftObjectPtr을 사용하여 경로만 가지고 있다가, 플레이어가 장비창을 열 때 등 진짜 필요할 때만 비동기로 로드(Async Load)하는 습관을 들이셔야 합니다.
개념이 조금 명확해지셨나요?
이 데이터를 바탕으로 실제 인벤토리를 구현하실 계획이신가요? 혹시 C++에서 이 Soft Reference를 어떻게 비동기(Async)로 랙 없이 로드하는지 다음 단계가 궁금하시다면 말씀해 주세요!
'프로그래밍 > Unreal Engine 5' 카테고리의 다른 글
| [Unreal Engine 5] 동기(Synchronous) VS 비동기(Asynchronous) (0) | 2026.03.09 |
|---|---|
| [Unreal Engine 5] 언리얼 엔진 인풋(Input) 아키텍처 파헤치기: F4 vs Lyra 비교 및 CS 관점의 동작 원리 (0) | 2026.03.05 |
| [Unreal Engine 5] Unreal Engine 5 VS Schmetterling Engine 2.0 (0) | 2026.02.05 |
| [Unreal Engine 5] UFUNCTION()와 지정자(Specfier)들 (0) | 2026.01.29 |
| [Unreal Engine 5] GAS(Gameplay Ability System) (0) | 2026.01.26 |