인벤토리 창에서 아이템을 클릭하여 위치를 바꾸고 같은 아이템끼리 합치는 기능을 구현해 볼 것이다.
// Inv_HoverItem.h
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Blueprint/UserWidget.h"
#include "Inv_HoverItem.generated.h"
class UInv_InventoryItem;
/**
* The HoverItem is the item that will appear and follow the mouse
* when an inventory item on the grid has been clicked.
*/
class UImage;
class UTextBlock;
UCLASS()
class INVENTORY_API UInv_HoverItem : public UUserWidget
{
GENERATED_BODY()
public:
void SetImageBrush(const FSlateBrush& Brush) const;
void UpdateStackCount(const int32 Count);
FGameplayTag GetItemType() const;
int32 GetStackCount() const { return StackCount; }
bool IsStackable() const { return bIsStackable; }
void SetIsStackable(bool bStacks);
int32 GetPreviousGridIndex() const { return PreviousGridIndex; }
void SetPreviousGridIndex(int32 Index) { PreviousGridIndex = Index; }
FIntPoint GetGridDimensions() const { return GridDimensions; }
void SetGridDimensions(const FIntPoint& Dimensions) { GridDimensions = Dimensions; }
UInv_InventoryItem* GetInventoryItem() const;
void SetInventoryItem(UInv_InventoryItem* Item);
private:
UPROPERTY(meta = (BindWidget))
TObjectPtr<UImage> Image_Icon;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UTextBlock> Text_StackCount;
int32 PreviousGridIndex;
FIntPoint GridDimensions;
TWeakObjectPtr<UInv_InventoryItem> InventoryItem;
bool bIsStackable{ false };
int32 StackCount{ 0 };
};
// Inv_HoverItem.cpp
#include "Widgets/Inventory/HoverItem/Inv_HoverItem.h"
#include "Components/Image.h"
#include "Components/TextBlock.h"
#include "Items/Inv_InventoryItem.h"
void UInv_HoverItem::SetImageBrush(const FSlateBrush& Brush) const
{
Image_Icon->SetBrush(Brush);
}
void UInv_HoverItem::UpdateStackCount(const int32 Count)
{
StackCount = Count;
if (Count > 0)
{
Text_StackCount->SetText(FText::AsNumber(Count));
Text_StackCount->SetVisibility(ESlateVisibility::Visible);
}
else
{
Text_StackCount->SetVisibility(ESlateVisibility::Collapsed);
}
}
FGameplayTag UInv_HoverItem::GetItemType() const
{
if (InventoryItem.IsValid())
{
return InventoryItem->GetItemManifest().GetItemType();
}
return FGameplayTag();
}
void UInv_HoverItem::SetIsStackable(bool bStacks)
{
bIsStackable = bStacks;
if (!bStacks)
{
Text_StackCount->SetVisibility(ESlateVisibility::Collapsed);
}
}
UInv_InventoryItem* UInv_HoverItem::GetInventoryItem() const
{
return InventoryItem.Get();
}
void UInv_HoverItem::SetInventoryItem(UInv_InventoryItem* Item)
{
InventoryItem = Item;
}
먼저 인벤토리에서 아이템을 클릭시 움직이는 이미지를 생성하기 위해서 Inv_HoverItem을 만들고 위와 같이 코드를 구성하였다.
함수는 몇개 없어 간단하다.
// Inv_SpatialInventory.h
#pragma once
#include "CoreMinimal.h"
#include "Widgets/Inventory/InventoryBase/Inv_InventoryBase.h"
#include "Inv_SpatialInventory.generated.h"
class UInv_InventoryGrid;
class UWidgetSwitcher;
class UButton;
/**
*
*/
UCLASS()
class INVENTORY_API UInv_SpatialInventory : public UInv_InventoryBase
{
GENERATED_BODY()
public:
virtual void NativeOnInitialized() override;
virtual FInv_SlotAvailabilityResult HasRoomForItem(UInv_ItemComponent* ItemComponent) const override;
private:
UPROPERTY(meta = (BindWidget))
TObjectPtr<UWidgetSwitcher> Switcher;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UInv_InventoryGrid> Grid_Equippables;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UInv_InventoryGrid> Grid_Consumables;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UInv_InventoryGrid> Grid_Craftables;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> Button_Equippables;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> Button_Consumables;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> Button_Craftables;
UFUNCTION()
void ShowEquippables();
UFUNCTION()
void ShowConsumables();
UFUNCTION()
void ShowCraftables();
void DisableButton(UButton* Button);
void SetActiveGrid(UInv_InventoryGrid* Grid, UButton* Button);
TWeakObjectPtr<UInv_InventoryGrid> ActiveGrid;
};
// Inv_SpatialInventory.cpp
#include "Widgets/Inventory/Spatial/Inv_SpatialInventory.h"
#include "Inventory.h"
#include "Components/Button.h"
#include "Components/WidgetSwitcher.h"
#include "InventoryManagement/Utils/Inv_InventoryStatics.h"
#include "Widgets/Inventory/Spatial/Inv_InventoryGrid.h"
#include "Inventory.h"
void UInv_SpatialInventory::NativeOnInitialized()
{
Super::NativeOnInitialized();
Button_Equippables->OnClicked.AddDynamic(this, &ThisClass::ShowEquippables);
Button_Consumables->OnClicked.AddDynamic(this, &ThisClass::ShowConsumables);
Button_Craftables->OnClicked.AddDynamic(this, &ThisClass::ShowCraftables);
ShowEquippables();
}
FInv_SlotAvailabilityResult UInv_SpatialInventory::HasRoomForItem(UInv_ItemComponent* ItemComponent) const
{
switch (UInv_InventoryStatics::GetItemCategoryFromItemComp(ItemComponent))
{
case EInv_ItemCategory::Equippable:
return Grid_Equippables->HasRoomForItem(ItemComponent);
case EInv_ItemCategory::Consumable:
return Grid_Consumables->HasRoomForItem(ItemComponent);
case EInv_ItemCategory::Craftable:
return Grid_Craftables->HasRoomForItem(ItemComponent);
default:
UE_LOG(LogInventory, Error, TEXT("ItemComponent doesn't have a valid Item Category."))
return FInv_SlotAvailabilityResult();
}
}
void UInv_SpatialInventory::ShowEquippables()
{
SetActiveGrid(Grid_Equippables, Button_Equippables);
}
void UInv_SpatialInventory::ShowConsumables()
{
SetActiveGrid(Grid_Consumables, Button_Consumables);
}
void UInv_SpatialInventory::ShowCraftables()
{
SetActiveGrid(Grid_Craftables, Button_Craftables);
}
void UInv_SpatialInventory::DisableButton(UButton* Button)
{
Button_Equippables->SetIsEnabled(true);
Button_Consumables->SetIsEnabled(true);
Button_Craftables->SetIsEnabled(true);
Button->SetIsEnabled(false);
}
void UInv_SpatialInventory::SetActiveGrid(UInv_InventoryGrid* Grid, UButton* Button)
{
if (ActiveGrid.IsValid()) ActiveGrid->HideCursor();
ActiveGrid = Grid;
if (ActiveGrid.IsValid()) ActiveGrid->ShowCursor();
DisableButton(Button);
Switcher->SetActiveWidget(Grid);
}
ShowEquippables, ShowConsumables, ShowCraftables 함수를 추가하고 각 버튼을 누를 시 다른 버튼들이 나오도록 구성하였다.
// Inv_InventoryGrid.cpp
void UInv_InventoryGrid::OnGridSlotClicked(int32 GridIndex, const FPointerEvent& MouseEvent)
{
if (!IsValid(HoverItem)) return;
if (!GridSlots.IsValidIndex(ItemDropIndex)) return;
if (CurrentQueryResult.ValidItem.IsValid() && GridSlots.IsValidIndex(CurrentQueryResult.UpperLeftIndex))
{
OnSlottedItemClicked(CurrentQueryResult.UpperLeftIndex, MouseEvent);
return;
}
auto GridSlot = GridSlots[ItemDropIndex];
if (!GridSlot->GetInventoryItem().IsValid())
{
PutDownOnIndex(ItemDropIndex);
}
}
void UInv_InventoryGrid::PutDownOnIndex(const int32 Index)
{
AddItemAtIndex(HoverItem->GetInventoryItem(), Index, HoverItem->IsStackable(), HoverItem->GetStackCount());
UpdateGridSlots(HoverItem->GetInventoryItem(), Index, HoverItem->IsStackable(), HoverItem->GetStackCount());
ClearHoverItem();
}
void UInv_InventoryGrid::ClearHoverItem()
{
if (!IsValid(HoverItem)) return;
HoverItem->SetInventoryItem(nullptr);
HoverItem->SetIsStackable(false);
HoverItem->SetPreviousGridIndex(INDEX_NONE);
HoverItem->UpdateStackCount(0);
HoverItem->SetImageBrush(FSlateNoResource());
HoverItem->RemoveFromParent();
HoverItem = nullptr;
ShowCursor();
}
UUserWidget* UInv_InventoryGrid::GetVisibleCursorWidget()
{
if (!IsValid(GetOwningPlayer())) return nullptr;
if (!IsValid(VisibleCursorWidget))
{
VisibleCursorWidget = CreateWidget<UUserWidget>(GetOwningPlayer(), VisibleCursorWidgetClass);
}
return VisibleCursorWidget;
}
UUserWidget* UInv_InventoryGrid::GetHiddenCursorWidget()
{
if (!IsValid(GetOwningPlayer())) return nullptr;
if (!IsValid(HiddenCursorWidget))
{
HiddenCursorWidget = CreateWidget<UUserWidget>(GetOwningPlayer(), HiddenCursorWidgetClass);
}
return HiddenCursorWidget;
}
bool UInv_InventoryGrid::IsSameStackable(const UInv_InventoryItem* ClickedInventoryItem) const
{
const bool bIsSameItem = ClickedInventoryItem == HoverItem->GetInventoryItem();
const bool bIsStackable = ClickedInventoryItem->IsStackable();
return bIsSameItem && bIsStackable && HoverItem->GetItemType().MatchesTagExact(ClickedInventoryItem->GetItemManifest().GetItemType());
}
void UInv_InventoryGrid::SwapWithHoverItem(UInv_InventoryItem* ClickedInventoryItem, const int32 GridIndex)
{
if (!IsValid(HoverItem)) return;
UInv_InventoryItem* TempInventoryItem = HoverItem->GetInventoryItem();
const int32 TempStackCount = HoverItem->GetStackCount();
const bool bTempIsStackable = HoverItem->IsStackable();
// Keep the same previous grid index
AssignHoverItem(ClickedInventoryItem, GridIndex, HoverItem->GetPreviousGridIndex());
RemoveItemFromGrid(ClickedInventoryItem, GridIndex);
AddItemAtIndex(TempInventoryItem, ItemDropIndex, bTempIsStackable, TempStackCount);
UpdateGridSlots(TempInventoryItem, ItemDropIndex, bTempIsStackable, TempStackCount);
}
bool UInv_InventoryGrid::ShouldSwapStackCounts(const int32 RoomInClickedSlot, const int32 HoveredStackCount, const int32 MaxStackSize) const
{
return RoomInClickedSlot == 0 && HoveredStackCount < MaxStackSize;
}
void UInv_InventoryGrid::SwapStackCounts(const int32 ClickedStackCount, const int32 HoveredStackCount, const int32 Index)
{
UInv_GridSlot* GridSlot = GridSlots[Index];
GridSlot->SetStackCount(HoveredStackCount);
UInv_SlottedItem* ClickedSlottedItem = SlottedItems.FindChecked(Index);
ClickedSlottedItem->UpdateStackCount(HoveredStackCount);
HoverItem->UpdateStackCount(ClickedStackCount);
}
bool UInv_InventoryGrid::ShouldConsumeHoverItemStacks(const int32 HoveredStackCount, const int32 RoomInClickedSlot) const
{
return RoomInClickedSlot >= HoveredStackCount;
}
void UInv_InventoryGrid::ConsumeHoverItemStacks(const int32 ClickedStackCount, const int32 HoveredStackCount, const int32 Index)
{
const int32 AmountToTransfer = HoveredStackCount;
const int32 NewClickedStackCount = ClickedStackCount + AmountToTransfer;
GridSlots[Index]->SetStackCount(NewClickedStackCount);
SlottedItems.FindChecked(Index)->UpdateStackCount(NewClickedStackCount);
ClearHoverItem();
ShowCursor();
const FInv_GridFragment* GridFragment = GridSlots[Index]->GetInventoryItem()->GetItemManifest().GetFragmentOfType<FInv_GridFragment>();
const FIntPoint Dimensions = GridFragment ? GridFragment->GetGridSize() : FIntPoint(1, 1);
HighlightSlots(Index, Dimensions);
}
bool UInv_InventoryGrid::ShouldFillInStack(const int32 RoomInClickedSlot, const int32 HoveredStackCount) const
{
return RoomInClickedSlot < HoveredStackCount;
}
void UInv_InventoryGrid::FillInStack(const int32 FillAmount, const int32 Remainder, const int32 Index)
{
UInv_GridSlot* GridSlot = GridSlots[Index];
const int32 NewStackCount = GridSlot->GetStackCount() + FillAmount;
GridSlot->SetStackCount(NewStackCount);
UInv_SlottedItem* ClickedSlottedItem = SlottedItems.FindChecked(Index);
ClickedSlottedItem->UpdateStackCount(NewStackCount);
HoverItem->UpdateStackCount(Remainder);
}
void UInv_InventoryGrid::ShowCursor()
{
if (!IsValid(GetOwningPlayer())) return;
GetOwningPlayer()->SetMouseCursorWidget(EMouseCursor::Default, GetVisibleCursorWidget());
}
void UInv_InventoryGrid::HideCursor()
{
if (!IsValid(GetOwningPlayer())) return;
GetOwningPlayer()->SetMouseCursorWidget(EMouseCursor::Default, GetHiddenCursorWidget());
}
void UInv_InventoryGrid::OnGridSlotHovered(int32 GridIndex, const FPointerEvent& MouseEvent)
{
if (IsValid(HoverItem)) return;
UInv_GridSlot* GridSlot = GridSlots[GridIndex];
if (GridSlot->IsAvailable())
{
GridSlot->SetOccupiedTexture();
}
}
void UInv_InventoryGrid::OnGridSlotUnhovered(int32 GridIndex, const FPointerEvent& MouseEvent)
{
if (IsValid(HoverItem)) return;
UInv_GridSlot* GridSlot = GridSlots[GridIndex];
if (GridSlot->IsAvailable())
{
GridSlot->SetUnoccupiedTexture();
}
}
Inv_InventoryGrid.cpp에 위와같이 함수들을 추가하였다.
아이템 이동, 같은 아이템 합치기, 아이템 스왑 등등이 잘 구현된 것을 볼 수 있다.
'언리얼 - C++ 프로젝트' 카테고리의 다른 글
| Inventory System (8) 아이템 드랍 (0) | 2025.08.05 |
|---|---|
| Inventory System (7) 아이템 나누기 (0) | 2025.08.05 |
| Inventory System (5) 아이템 스택 (3) | 2025.08.03 |
| Inventory System (4) 아이템 픽업 (0) | 2025.08.02 |
| Inventory System (3) 인벤토리 그리드 및 인벤토리 컴포넌트 구성 (2) | 2025.07.30 |