이전 글에서 이어집니다
2025.11.23 - [Unreal 프로젝트 다이어리/두번째 프로젝트] - Unreal - 인벤토리 ( 아이템 추가하기 )
Unreal - 인벤토리 ( 아이템 추가하기 )
이전글에서 이어집니다2025.11.22 - [Unreal 프로젝트 다이어리/두번째 프로젝트] - Unreal - 인벤토리 (크기변경하기, 창옮기기) Unreal - 인벤토리 ( 크기변경하기, 창옮기기 )인벤토리를 구현해보겠습니
lucodev.tistory.com
아이템 드래그앤드롭 기능을 추가하였습니다
구현한 내용
- 인벤토리 드래그 앤 드롭
구현방식
1. NativeOnMouseButtonDown에서 원본 슬롯을 반투명 처리하고 드래그 시작 감지.
2. NativeOnDragDetected에서 DragDropOperation 생성, 마우스를 따라다니는 드래그 비주얼 설정,
Drop/Cancel 시 원본 슬롯 복구.
사용한 클래스
| SlotWidget | 인벤토리 슬롯 UI, 아이템표시 + 드래그 / 드롭 처리 |
| InvenSlotReplicateWidget | 드래그 중 마우스를 따라다니는 아이템 비주얼 위젯 |
| InventoryDragDropOper | 드래그 중 아이템 데이터와 DragVisual을 담는 컨테이너 |
구현
언리얼 엔진에서 드래그 앤 드롭 시스템을 구현할때 드래그 중인 아이템의
정보 + 비주얼 을 담을수있는 상태를 담는 컨테이너 객체를 제공합니다
이름은 DragDropOperation이며 해당 클래스로 만들어주시면됩니다

목적은 이와 같습니다
드래그 중인 아이템의 데이터를 담는 컨테이너클래스 이며
드래그가 진행되는동안 드롭할 때 어떤 아이템인지 알려주기 위해 사용됩니다
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/DragDropOperation.h"
#include "ItemSlot.h"
#include "InventoryDragDropOper.generated.h"
UCLASS()
class PORTFOLIOMS_API UInventoryDragDropOper : public UDragDropOperation
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere)
int32 startSlotIdx = -1;
UPROPERTY(EditAnywhere)
int32 itemID = -1;
UPROPERTY(EditAnywhere)
int32 count = 0;
UPROPERTY(EditAnywhere)
FItemSlot dragSlot;
};
먼저 InventoryComponent에서 인벤토리는 TMap으로
키와 밸류 값으로 이루어져있습니다 TMap<int32, FItemSlot>
TMap의 FItemSlot 을 바꿔주는 즉 인벤토리를 서로 바꿔주는 Swap 함수를 만들어주어야합니다
해당 함수는 인벤토리 기능을 담당하는 InventoryComponent에서 만들어주었습니다
출발 슬롯과 목적지 슬롯의 FItemSlot값을 서로 임시변수로 교환하여
두 슬롯의 내용을 바꿔줍니다
void UInventoryComponent::SwapItem(int32 startIdx, int32 destinationIdx)
{
FItemSlot destinationSlot = items[destinationIdx]; // 목적지 슬롯 기존 아이템
items[destinationIdx] = items[startIdx]; // 목적지 {itemID = 88, inCount = 88}
items[startIdx] = destinationSlot; // 출발지
onInventoryUpdated.Broadcast(startIdx, items[startIdx]);
onInventoryUpdated.Broadcast(destinationIdx, items[destinationIdx]);
}
블루프린트로는 구현하기 쉬웠는데 c++로 구현할려니 막막하더라구요
구글링과 공식문서를 뒤적거리다가 이와같은 함수가 있다 라는것을 알았습니다
위젯에서는 드래그할때 이와같은 함수를 오버라이드 하여 사용할수있습니다
| NativeOnMouseButtonDown | 사용자가 위젯에서 마우스 버튼을 눌렀을 때 호출 |
| NativeOnDragDetected | 마우스를 눌러 드래그를 시작한 것을 감지했을 때 호출되는 이벤트 |
| NativeOnDrop | 위젯에 드래그된 오퍼레이션이 놓였을 때 호출되는 이벤트, 드롭 처리(데이터 이동, 교환 등)를 수행. |
| NativeOnDragCancelled | 드래그 도중 취소 |
이와같이 오버라이드 하여 사용합니다
virtual FReply NativeOnMouseButtonDown(const FGeometry& inGeometry, const FPointerEvent& inMouseEvent) override;
virtual void NativeOnDragDetected(const FGeometry& inGeometry, const FPointerEvent& pointerEvent, UDragDropOperation*& outOperation) override;
virtual bool NativeOnDrop(const FGeometry& inGeometry, const FDragDropEvent& inDragDropEvent, UDragDropOperation* inOperation) override;
virtual void NativeOnDragCancelled(const FDragDropEvent& inDragDropEvent, UDragDropOperation* inOperation) override;
SlotWidget에 이와같이 추가해줍니다
public:
class UInventoryComponent* inventoryComp;
FItemSlot currentSlotData;
UPROPERTY(EditAnywhere)
TSubclassOf<UInvenSlotReplicateWidget> replicateWidget;
void SetDraggingVisual(bool bDragging);
public:
int32 slotIdx = -1;
슬롯 내에서 NativeConstruct내에서 사용하고있는 pawn에서 원하는 컴포넌트를 먼저 캐스팅해줍니다
inventoryComp = myPawn->FindComponentByClass<UInventoryComponent>();
NativeOnMouseButtonDown 함수입니다
좌클릭의 신호를 받을때 신호를 처리합니다
FReply USlotWidget::NativeOnMouseButtonDown(const FGeometry& inGeometry, const FPointerEvent& inMouseEvent)
{
if (inMouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
{
if (!currentSlotData.isEmpty()) //있을때만 처리
return UWidgetBlueprintLibrary::DetectDragIfPressed(inMouseEvent, this, EKeys::LeftMouseButton).NativeReply;
}
return FReply::Unhandled();
}
드래그도중 호출되는 NativeOnDragDetected 함수입니다
아까만든 DragDropOperation에서 변수값을 현재 슬롯의 데이터와 동기화하고
드래그 비주얼 위젯을 생성하여 아이콘을 반영합니다
(SetDraggingVisual 함수 커스텀 함수이며 바인딩 된 이미지, 텍스트의 Alpha값을 조절합니다)
여기서 정말 많이 삽질했는데
처음에 삽질한 코드를 보자
//삽질코드
dragOper->DefaultDragVisual = this;
this->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
outOperation = dragOper;
이러면 가끔 슬롯이 감지 안되는 레이아웃 클릭 충돌 문제가 있었다
위젯 리플릭터로 디버깅해보니 감지가 잘 안되던문제가 있었다
즉 복제위젯이 따로 별도로 만들어야만했다
그리고 InvenSlotReplicateWidget이란 드래그할때 보여지는 위젯을 별도로 만들었다
void USlotWidget::NativeOnDragDetected(const FGeometry& inGeometry, const FPointerEvent& pointerEvent, UDragDropOperation*& outOperation)
{
if (currentSlotData.isEmpty() || !inventoryComp)
return;
SetDraggingVisual(true);
UInventoryDragDropOper* dragOper = NewObject<UInventoryDragDropOper>();
dragOper->startSlotIdx = slotIdx;
dragOper->itemID = currentSlotData.itemID;
dragOper->count = currentSlotData.inCount;
dragOper->dragSlot = currentSlotData;
if (replicateWidget)
{
UInvenSlotReplicateWidget* dragVisual = CreateWidget<UInvenSlotReplicateWidget>(GetWorld(), replicateWidget);
if (dragVisual)
{
const FItemData* itemData = inventoryComp->itemDataMap.Find(currentSlotData.itemID);
if (itemData)
dragVisual->SetItemImage(itemData->itemIcon); // 아이콘 반영
else
dragVisual->SetItemImage(nullptr);
dragVisual->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
dragOper->DefaultDragVisual = dragVisual;
}
}
outOperation = dragOper;
}
NativeOnDrop함수입니다
드래그된 아이템이 슬롯 위에 놓였을때 호출되며
opration에서 드래그한 아이템의 데이터를 가져와 현재 슬롯과 교환 처리하며
드래그 비주얼과 원본 슬롯 상태를 초기화합니다
bool USlotWidget::NativeOnDrop(const FGeometry& inGeometry, const FDragDropEvent& inDragDropEvent, UDragDropOperation* inOperation)
{
UInventoryDragDropOper* dragOper = Cast<UInventoryDragDropOper>(inOperation);
if (!dragOper || !inventoryComp)
return false;
if (dragOper->startSlotIdx == slotIdx)
return false;
inventoryComp->SwapItem(dragOper->startSlotIdx, slotIdx);
dragOper->dragSlot.Clear();
dragOper->count = 0;
dragOper->itemID = 0;
SetDraggingVisual(false);
return true;
}
그리고 전에 만든 슬롯을 업데이트 해주는 함수에 슬롯의 색을 초기화해주는 코드를 추가해주었습니다
void USlotWidget::UpdateSlot(const FItemSlot& inslot, const FItemData& inData)
{
//원본
//추가내용
//슬롯 초기화시켜주기
Image_ItemSlot->SetBrushFromTexture(inslot.isEmpty() ? emptyTexture : inData.itemIcon);
float originA = 1.0f;
FLinearColor iconColor = Image_ItemSlot->ColorAndOpacity;
iconColor.A = originA;
Image_ItemSlot->SetColorAndOpacity(iconColor);
FSlateColor textColor = TextBlock_ItemCount->ColorAndOpacity;
FLinearColor originL = textColor.GetSpecifiedColor();
originL.A = originA;
TextBlock_ItemCount->SetColorAndOpacity(originL);
}
NativeOnDragCancelled 함수입니다
드래그 도중 슬롯밖으로 마우스를 놓거나 drop이 실패할때 원본 슬롯이 투명으로 남는 문제를 방지하기위해
안전장치 역할을 해줍니다
void USlotWidget::NativeOnDragCancelled(const FDragDropEvent& inDragDropEvent, UDragDropOperation* inOperation)
{
Super::NativeOnDragCancelled(inDragDropEvent, inOperation);
SetDraggingVisual(false);
}
결과


'Unreal 프로젝트 다이어리 > 두번째 프로젝트' 카테고리의 다른 글
| Unreal - 인벤토리(5) ( 아이템 정보 ) (0) | 2025.11.28 |
|---|---|
| Unreal - 인벤토리(4) ( 정렬, 삭제 ) (0) | 2025.11.26 |
| Unreal - 인벤토리(2) ( 아이템 추가하기 ) (0) | 2025.11.23 |
| Unreal - 인벤토리(1) ( 크기변경하기, 창옮기기 ) (2) | 2025.11.22 |
| Unreal - UI (HpWidget, PostureWidget) (0) | 2025.11.21 |