이전글에서 이어집니다
2025.11.22 - [Unreal 프로젝트 다이어리/두번째 프로젝트] - Unreal - 인벤토리 (크기변경하기, 창옮기기)
Unreal - 인벤토리 ( 크기변경하기, 창옮기기 )
인벤토리를 구현해보겠습니다1인 개발이다보니 기획도 스스로 해야했습니다 (어쩔수있나요)인벤토리 초기 구상도입니다인벤토리 구상도 인벤토리 기능인벤토리의 현재 기능은 이와 같습니다
lucodev.tistory.com
아이템슬롯에 아이템을 추가해보겠습니다
구현한 내용
- 아이템 획득 시 해당 아이템의 최대 스택 수 만큼 한 슬롯에 저장됩니다
- 아이템번호로 아이템을 구분합니다
- 아이템을 인벤토리에 추가 및 삭제 가 가능합니다
구현방식
- 아이템을 먹었을 때 ItemID, Count를 전달받습니다
- SlotItem(TArray<FItemSlot>)에서 동일한 아이템을 찾습니다
- ItemData에서 최대 스택을 확인합니다
- 이미 있는 슬롯이면 MaxStack 까지 합칩니다
- Count가 아직 남아있으면 빈슬롯을 찾아 넣습니다
- 남은 Count가 있으면 인벤토리 공간 부족 처리로 넘어갑니다
- UI를 업데이트합니다
사용한 클래스
| InventoryComponent | 인벤토리 기능의 두뇌역할, 실제 로직 실행장소 |
| InventoryWidget | 모든 SlotWidget을 보유하고 관리하는 인벤토리 전체 UI |
| ItemData | 아이템의 고정된 속성을 제공하는 데이터베이스 역할 |
| SlotWidget | 화면에 보이는 인벤토리 '한 칸'을 그리는 위젯 |
| ItemSlot | 인벤토리 '한 칸' 을 표현하는 구조체 |
구현
먼저 게임에서 사용할 모든 아이템 데이터를 제작해주었습니다
아이템은 각각의 ID를 보유하며 itemName, Type, maxStack등 여러가지 속성을 가집니다
UENUM(BlueprintType)
enum class EItemType : uint8
{
Consumable,
Upgrade,
Quest,
Money
};
USTRUCT(BlueprintType)
struct FItemData : public FTableRowBase
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 itemID;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString itemName;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
EItemType itemType;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 maxStack;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString itemDescription;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
UTexture2D* itemIcon;
};

인벤토리를 배열(TArray)로 관리하기위해
FItemSlot 즉 인벤토리의 한 칸 에 들어있는 아이템 정보를 저장하는 구조체를 만들어주었습니다
어떤아이템이 들어있는가? -> itemID로 판단
몇개들어있는가? -> inCount로 판단
비어있는칸인가? -> isEmpty()로 판단
아이템을 버리거나 이동하면? -> Clear()로 비움
USTRUCT(BlueprintType)
struct FItemSlot
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
int32 itemID = -1;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
int32 inCount = 0;
bool isEmpty() const
{
return itemID < 0 || inCount <= 0;
}
void Clear()
{
itemID = -1;
inCount = 0;
}
};
인벤토리 컴포넌트에서 데이터테이블을 할당하고
아이템 데이터 테이블을 읽어서 itemDataMap(아이디 -> 데이터) 로 저장해두었습니다
TMap<int32, FItemData> itemDataMap;
void UInventoryComponent::BeginPlay()
{
Super::BeginPlay();
InitializeInventory(baseInvenSlotCount);
}
void UInventoryComponent::InitializeInventory(int32 numSlots)
{
baseInvenSlotCount = numSlots;
items.SetNum(baseInvenSlotCount);
if (itemDataTable)
{
static const FString contextString(TEXT("ItemDataInialize"));
TArray<FItemData*> allItems;
itemDataTable->GetAllRows<FItemData>(contextString, allItems);
for (FItemData* row : allItems)
{
if (row)
itemDataMap.Add(row->itemID, *row);
}
}
}
인벤토리의 특정 슬롯이 업데이트 되었다 라는 사실을
다른 UI에 알려주는 이벤트 시스템으로 델리게이트를 사용하여 인벤토리위젯을 바인딩한뒤 갱신합니다
인벤토리컴포넌트에 델리게이트를 달아줍니다
인벤토리컴포넌트.h의 클래스 상단 델리게이트를 추가해줍니다
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnInventoryUpdated, int32, SlotIndex, FItemSlot, Item);
컴포넌트 내 델리게이트 변수를 선언해줍니다
UPROPERTY(BlueprintAssignable, Category = "Inventory")
FOnInventoryUpdated onInventoryUpdated;
아이템을 추가하거나 삭제할때 Broadcast하여 몇번째 슬롯이 바뀌었는지 구조체의 현재 상태를 알려주면됩니다
onInventoryUpdated.Broadcast(배열에서 몇 번째 슬롯이 바뀌었는지, FItemSlot 구조체의 현재 상태);
이제 아이템을 추가하는 핵심 AddItem함수를 만들어보았습니다
아이템을 인벤토리에 넣는데 스택 규칙을 지켜서 넣는 함수입니다
규칙은 이와 같습니다
1. 이미 같은 아이템이 있는 슬롯에 먼저 눌러담습니다
2. 그 다음 빈 슬롯을 찾아서 넣습니다
3. 각 슬롯이 변경되면델리게이트로 UI에게 알려서 UI를 업데이트합니다
bool UInventoryComponent::AddItem(int32 itemID, int32 count)
{
if (!itemDataMap.Contains(itemID))
return false;
const FItemData& mapData = itemDataMap[itemID];
int32 maxStack = mapData.maxStack;
int32 remain = count;
//스택계산
for (int32 i = 0; i < items.Num(); i++)
{
FItemSlot& slot = items[i];
if (slot.itemID == itemID && slot.inCount < maxStack)
{
int32 slotSpace = maxStack - slot.inCount;
int32 addAmount = FMath::Min(slotSpace, remain);
slot.inCount += addAmount;
remain -= addAmount;
onInventoryUpdated.Broadcast(i, slot);
if (remain <= 0)
return true;
}
}
//빈슬롯에 새로 넣기
for (int32 i = 0; i < items.Num(); i++)
{
FItemSlot& slot = items[i];
if (slot.isEmpty())
{
int32 addAmount = FMath::Min(maxStack, remain);
slot.itemID = itemID;
slot.inCount = addAmount;
remain -= addAmount;
onInventoryUpdated.Broadcast(i, slot);
if (remain <= 0)
return true;
}
}
return false;
}
그런뒤 인벤토리 위젯에서 onInventoryUpdated 델리게이트를 받을수있게 바인드 하여 구독을 해줍니다
if (inventoryComp)
inventoryComp->onInventoryUpdated.AddDynamic(this, &UInventoryWidget::OnInventoryUpdated);
void UInventoryWidget::OnInventoryUpdated(int32 slotIndex, FItemSlot item)
{
if (!WrapBox_Inven)
return;
USlotWidget* slotWidget = Cast<USlotWidget>(WrapBox_Inven->GetChildAt(slotIndex));
if (!slotWidget)
return;
const FItemData* itemData = inventoryComp->itemDataMap.Find(item.itemID);
if (itemData)
slotWidget->UpdateSlot(item, *itemData);
else
// 빈 슬롯 처리
slotWidget->UpdateSlot(item, FItemData());
}
마지막으로 SlotWidget 슬롯을 업데이트 하여 이미지와 갯수Text를 업데이트 시켜주는 함수를 만들어주면 마무리가됩니다
void USlotWidget::UpdateSlot(const FItemSlot& inslot, const FItemData& inData)
{
if (!Image_ItemSlot || !TextBlock_ItemCount)
return;
if (inslot.isEmpty())
{
Image_ItemSlot->SetBrushFromTexture(emptyTexture);
TextBlock_ItemCount->SetText(FText::FromString(TEXT("")));
return;
}
// 아이템이 있으면 아이템 이미지와 개수 표시
if (inData.itemIcon)
Image_ItemSlot->SetBrushFromTexture(inData.itemIcon);
// 아이템 개수가 1 이상이면 표시, 1이면 빈 텍스트
if (inslot.inCount > 1)
TextBlock_ItemCount->SetText(FText::AsNumber(inslot.inCount));
else
TextBlock_ItemCount->SetText(FText::FromString(TEXT("")));
}
결과
현재 아이템을 먹는곳을 따로 구현하지않아 키보드 입력으로 아이템을 받아보겠습니다
1번 아이템, 그리고 7번 아이템을 받아보겠습니다
그리고 해당 아이템의 최대 스택은 1번은 12개 7번은 5개입니다

이번에는 ItemID5번과 ItemID14번을 1개, 100개씩 받아보겠습니다
해당 최대스택은 5, 255입니다

'Unreal 프로젝트 다이어리 > 두번째 프로젝트' 카테고리의 다른 글
| Unreal - 인벤토리(4) ( 정렬, 삭제 ) (0) | 2025.11.26 |
|---|---|
| Unreal - 인벤토리(3) (드래그 앤 드롭) (0) | 2025.11.25 |
| Unreal - 인벤토리(1) ( 크기변경하기, 창옮기기 ) (2) | 2025.11.22 |
| Unreal - UI (HpWidget, PostureWidget) (0) | 2025.11.21 |
| Unreal - StatComponent (0) | 2025.11.21 |