PART 5 · 강의 1/7

아이템 시스템 설계

Data Assets 기반 모듈형 아이템 시스템 아키텍처

01

아이템 시스템 개요

언리얼 엔진의 Data Asset을 활용한 확장 가능한 설계

RPG 게임에서 아이템 시스템은 게임플레이의 핵심입니다. 언리얼 엔진 5에서는 Primary Data Asset을 활용한 데이터 주도 설계가 권장됩니다. 이 방식은 디자이너가 블루프린트에서 쉽게 새 아이템을 생성할 수 있으면서도, 런타임 성능을 최적화할 수 있습니다.

핵심 설계 원칙

Definition(정의)Instance(인스턴스)를 분리합니다. Definition은 아이템의 불변 데이터(이름, 아이콘, 기본 스탯)를, Instance는 가변 데이터(강화 레벨, 내구도, 스택 수)를 담당합니다.

아이템 유형

⚔️

Equipment

장비 아이템

🧪

Consumable

소비 아이템

💎

Material

재료 아이템

📜

Quest

퀘스트 아이템

💰

Currency

화폐

02

시스템 아키텍처

Fragment 기반 모듈형 설계 패턴

┌─────────────────────────────────────────────────────────────────┐
│                      UItemDefinition                             │
│                    (UPrimaryDataAsset)                           │
├─────────────────────────────────────────────────────────────────┤
│  DisplayName, Description, Icon, ItemType, Rarity, BasePrice    │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │              TArray<UItemFragment*> Fragments            │    │
│  │  ┌────────────┐ ┌────────────┐ ┌────────────┐           │    │
│  │  │ Equippable │ │ Consumable │ │ Stackable  │  ...      │    │
│  │  └────────────┘ └────────────┘ └────────────┘           │    │
│  └─────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘
                              │
                              │ References
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                       UItemInstance                              │
│                         (UObject)                                │
├─────────────────────────────────────────────────────────────────┤
│  ItemDefinition*, StackCount, ItemGuid                          │
│  EnhancementLevel, Durability, ...                              │
└─────────────────────────────────────────────────────────────────┘

Fragment 패턴의 장점

UItemFragment_Equippable 장비 슬롯, 스탯 수정자, 부여 어빌리티
UItemFragment_Consumable 사용 시 적용할 GameplayEffect
UItemFragment_Stackable 최대 스택 크기 정의

이 패턴은 Epic Games의 Lyra 프로젝트에서 사용하는 방식으로, 새로운 아이템 기능을 추가할 때 기존 코드를 수정하지 않고 Fragment만 추가하면 됩니다.

03

아이템 정의 구현

UPrimaryDataAsset 기반 ItemDefinition

열거형 정의

ItemDefinition.h // 아이템 유형 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 };

스탯 수정자 구조체

ItemDefinition.h // 아이템이 제공하는 스탯 수정자 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 베이스 클래스

ItemDefinition.h // 아이템 프래그먼트 베이스 (모듈형 설계) 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; };

소비 아이템 및 스택 프래그먼트

ItemDefinition.h // 소비 아이템 프래그먼트 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; };
04

Primary Data Asset 구현

에셋 관리 및 비동기 로딩 지원

ItemDefinition.h // 아이템 정의 (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()); } };
Asset Manager 설정

DefaultGame.ini에서 Primary Asset Type을 등록해야 합니다:

[/Script/Engine.AssetManagerSettings] +PrimaryAssetTypesToScan=(PrimaryAssetType="Item",AssetBaseClass=/Script/MyGame.ItemDefinition,bHasBlueprintClasses=True,bIsEditorOnly=False,Directories=((Path="/Game/Items")))
05

아이템 인스턴스

런타임 아이템 데이터 관리

ItemInstance.h #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); };
ItemInstance.cpp #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; }
SUMMARY

핵심 요약

  • Data Asset 기반 설계 — UPrimaryDataAsset을 상속받아 아이템 정의를 생성하면 Asset Manager와 연동되어 비동기 로딩이 가능합니다.
  • Definition vs Instance — 불변 데이터(정의)와 가변 데이터(인스턴스)를 분리하여 메모리를 효율적으로 관리합니다.
  • Fragment 패턴 — 아이템 기능을 모듈형으로 분리하여 조합할 수 있습니다. 새 기능 추가 시 기존 코드를 수정할 필요가 없습니다.
  • GAS 연동 — 장비 아이템은 GameplayEffect와 GameplayAbility를 부여하여 스탯 변경 및 스킬 제공이 가능합니다.
  • TSoftObjectPtr 사용 — 아이콘, 메시 등의 에셋은 Soft Reference로 참조하여 필요 시에만 로드합니다.
주의사항

아이템 인스턴스를 직렬화할 때는 Definition의 AssetId만 저장하고, 로드 시 Asset Manager를 통해 Definition을 다시 로드해야 합니다. Definition 자체를 저장하면 에셋 이동 시 세이브 파일이 깨집니다.

PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: UPrimaryDataAsset 기반 아이템 정의

UPrimaryDataAsset를 상속하여 URPGItemDefinition을 만들고, ItemName(FText), ItemType(EItemType), Icon(TSoftObjectPtr), StackLimit(int32)을 정의하세요. Data Asset으로 무기, 방어구, 소비 아이템을 각각 생성하세요.

실습 2: 아이템 인스턴스 시스템

URPGItemInstance 클래스를 만들어 아이템 정의를 참조하면서 개별 상태(내구도, 강화 레벨, 소켓 젬)를 관리하세요. 동일 아이템이더라도 인스턴스마다 다른 상태를 가질 수 있도록 설계하세요.

심화 과제: AssetManager 기반 아이템 로딩

UAssetManager를 상속하여 아이템 에셋을 PrimaryAssetType으로 등록하고, 비동기 로딩(AsyncLoadPrimaryAsset)을 구현하세요. 아이템 카탈로그가 수천 개일 때도 메모리 효율적으로 관리되는 구조를 설계하세요.