아이템 시스템 설계
Data Assets 기반 모듈형 아이템 시스템 아키텍처
아이템 시스템 개요
언리얼 엔진의 Data Asset을 활용한 확장 가능한 설계
RPG 게임에서 아이템 시스템은 게임플레이의 핵심입니다. 언리얼 엔진 5에서는 Primary Data Asset을 활용한 데이터 주도 설계가 권장됩니다. 이 방식은 디자이너가 블루프린트에서 쉽게 새 아이템을 생성할 수 있으면서도, 런타임 성능을 최적화할 수 있습니다.
Definition(정의)과 Instance(인스턴스)를 분리합니다. Definition은 아이템의 불변 데이터(이름, 아이콘, 기본 스탯)를, Instance는 가변 데이터(강화 레벨, 내구도, 스택 수)를 담당합니다.
아이템 유형
Equipment
장비 아이템
Consumable
소비 아이템
Material
재료 아이템
Quest
퀘스트 아이템
Currency
화폐
시스템 아키텍처
Fragment 기반 모듈형 설계 패턴
┌─────────────────────────────────────────────────────────────────┐
│ UItemDefinition │
│ (UPrimaryDataAsset) │
├─────────────────────────────────────────────────────────────────┤
│ DisplayName, Description, Icon, ItemType, Rarity, BasePrice │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ TArray<UItemFragment*> Fragments │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Equippable │ │ Consumable │ │ Stackable │ ... │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
│ References
▼
┌─────────────────────────────────────────────────────────────────┐
│ UItemInstance │
│ (UObject) │
├─────────────────────────────────────────────────────────────────┤
│ ItemDefinition*, StackCount, ItemGuid │
│ EnhancementLevel, Durability, ... │
└─────────────────────────────────────────────────────────────────┘
Fragment 패턴의 장점
이 패턴은 Epic Games의 Lyra 프로젝트에서 사용하는 방식으로, 새로운 아이템 기능을 추가할 때 기존 코드를 수정하지 않고 Fragment만 추가하면 됩니다.
아이템 정의 구현
UPrimaryDataAsset 기반 ItemDefinition
열거형 정의
// 아이템 유형
UENUM(BlueprintType)
enum class EItemType : uint8
{
Consumable,
Equipment,
Material,
Quest,
Currency
};
// 장비 슬롯
UENUM(BlueprintType)
enum class EEquipmentSlot : uint8
{
None,
Head,
Chest,
Hands,
Legs,
Feet,
MainHand,
OffHand,
Accessory1,
Accessory2
};
// 아이템 희귀도
UENUM(BlueprintType)
enum class EItemRarity : uint8
{
Common,
Uncommon,
Rare,
Epic,
Legendary
};
스탯 수정자 구조체
// 아이템이 제공하는 스탯 수정자
USTRUCT(BlueprintType)
struct FItemStatModifier
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FGameplayAttribute Attribute;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float Value = 0.f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
bool bIsPercentage = false;
};
Fragment 베이스 클래스
// 아이템 프래그먼트 베이스 (모듈형 설계)
UCLASS(Abstract, Blueprintable, EditInlineNew, DefaultToInstanced)
class MYGAME_API UItemFragment : public UObject
{
GENERATED_BODY()
};
// 장비 프래그먼트
UCLASS()
class MYGAME_API UItemFragment_Equippable : public UItemFragment
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
EEquipmentSlot Slot = EEquipmentSlot::None;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TArray<FItemStatModifier> StatModifiers;
// 장착 시 적용할 GameplayEffect
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TSubclassOf<UGameplayEffect> EquipEffect;
// 장착 시 부여할 어빌리티
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TArray<TSubclassOf<UGameplayAbility>> GrantedAbilities;
// 메시 (소켓에 부착할)
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TSoftObjectPtr<USkeletalMesh> EquipmentMesh;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FName AttachSocketName;
};
소비 아이템 및 스택 프래그먼트
// 소비 아이템 프래그먼트
UCLASS()
class MYGAME_API UItemFragment_Consumable : public UItemFragment
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TSubclassOf<UGameplayEffect> UseEffect;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
bool bConsumeOnUse = true;
};
// 스택 가능 프래그먼트
UCLASS()
class MYGAME_API UItemFragment_Stackable : public UItemFragment
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
int32 MaxStackSize = 99;
};
Primary Data Asset 구현
에셋 관리 및 비동기 로딩 지원
// 아이템 정의 (Primary Data Asset)
UCLASS(BlueprintType)
class MYGAME_API UItemDefinition : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
// 기본 정보
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item")
FText DisplayName;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item",
meta = (MultiLine = true))
FText Description;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item")
TSoftObjectPtr<UTexture2D> Icon;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item")
EItemType ItemType = EItemType::Material;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item")
EItemRarity Rarity = EItemRarity::Common;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item")
int32 BasePrice = 0;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item")
FGameplayTagContainer ItemTags;
// 모듈형 프래그먼트
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Instanced, Category = "Fragments")
TArray<TObjectPtr<UItemFragment>> Fragments;
// 프래그먼트 찾기 템플릿
template<class T>
T* FindFragment() const
{
for (UItemFragment* Fragment : Fragments)
{
if (T* TypedFragment = Cast<T>(Fragment))
{
return TypedFragment;
}
}
return nullptr;
}
// Primary Asset ID (Asset Manager 연동)
virtual FPrimaryAssetId GetPrimaryAssetId() const override
{
return FPrimaryAssetId(TEXT("Item"), GetFName());
}
};
DefaultGame.ini에서 Primary Asset Type을 등록해야 합니다:
[/Script/Engine.AssetManagerSettings]
+PrimaryAssetTypesToScan=(PrimaryAssetType="Item",AssetBaseClass=/Script/MyGame.ItemDefinition,bHasBlueprintClasses=True,bIsEditorOnly=False,Directories=((Path="/Game/Items")))
아이템 인스턴스
런타임 아이템 데이터 관리
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "ItemInstance.generated.h"
class UItemDefinition;
UCLASS(BlueprintType)
class MYGAME_API UItemInstance : public UObject
{
GENERATED_BODY()
public:
UItemInstance();
// 아이템 정의 참조
UPROPERTY(BlueprintReadOnly)
TObjectPtr<const UItemDefinition> ItemDefinition;
// 스택 수량
UPROPERTY(BlueprintReadWrite)
int32 StackCount = 1;
// 아이템 고유 ID (세이브/로드용)
UPROPERTY(BlueprintReadOnly)
FGuid ItemGuid;
// 강화 레벨
UPROPERTY(BlueprintReadWrite)
int32 EnhancementLevel = 0;
// 내구도
UPROPERTY(BlueprintReadWrite)
float Durability = 100.f;
// 초기화 함수
void Initialize(const UItemDefinition* Definition, int32 Count = 1);
};
#include "ItemInstance.h"
#include "ItemDefinition.h"
UItemInstance::UItemInstance()
{
ItemGuid = FGuid::NewGuid();
}
void UItemInstance::Initialize(const UItemDefinition* Definition, int32 Count)
{
ItemDefinition = Definition;
StackCount = Count;
ItemGuid = FGuid::NewGuid();
EnhancementLevel = 0;
Durability = 100.f;
}
핵심 요약
- Data Asset 기반 설계 — UPrimaryDataAsset을 상속받아 아이템 정의를 생성하면 Asset Manager와 연동되어 비동기 로딩이 가능합니다.
- Definition vs Instance — 불변 데이터(정의)와 가변 데이터(인스턴스)를 분리하여 메모리를 효율적으로 관리합니다.
- Fragment 패턴 — 아이템 기능을 모듈형으로 분리하여 조합할 수 있습니다. 새 기능 추가 시 기존 코드를 수정할 필요가 없습니다.
- GAS 연동 — 장비 아이템은 GameplayEffect와 GameplayAbility를 부여하여 스탯 변경 및 스킬 제공이 가능합니다.
- TSoftObjectPtr 사용 — 아이콘, 메시 등의 에셋은 Soft Reference로 참조하여 필요 시에만 로드합니다.
아이템 인스턴스를 직렬화할 때는 Definition의 AssetId만 저장하고, 로드 시 Asset Manager를 통해 Definition을 다시 로드해야 합니다. Definition 자체를 저장하면 에셋 이동 시 세이브 파일이 깨집니다.
도전 과제
배운 내용을 직접 실습해보세요
UPrimaryDataAsset를 상속하여 URPGItemDefinition을 만들고, ItemName(FText), ItemType(EItemType), Icon(TSoftObjectPtr
URPGItemInstance 클래스를 만들어 아이템 정의를 참조하면서 개별 상태(내구도, 강화 레벨, 소켓 젬)를 관리하세요. 동일 아이템이더라도 인스턴스마다 다른 상태를 가질 수 있도록 설계하세요.
UAssetManager를 상속하여 아이템 에셋을 PrimaryAssetType으로 등록하고, 비동기 로딩(AsyncLoadPrimaryAsset)을 구현하세요. 아이템 카탈로그가 수천 개일 때도 메모리 효율적으로 관리되는 구조를 설계하세요.