- 인벤토리 아이템 Drag & Drop 구현
- Unreal에서 제공해주는 DragDropOperation을 사용해 드래그 & 드랍을 구현해보도록 하겠습니다.
- 예제(블루프린트)
DragDropOperation 클래스는 드래그 앤 드롭 기능을 구현할 때 사용됩니다.
DragDropOperation을 상속받는 DragSlot클래스를 만들어 생성 시 정보(처음 클릭한 슬롯의 인덱스, 타입)를 저장해 사용해보도록 하겠습니다.
// MMDragSlot Header
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/DragDropOperation.h"
#include "GameData/MMEnums.h"
#include "MMDragSlot.generated.h"
/**
*
*/
UCLASS()
class MYSTICMAZE_API UMMDragSlot : public UDragDropOperation
{
GENERATED_BODY()
// Drag 정보 저장용 변수
public:
UPROPERTY(VisibleAnywhere)
int PrevSlotIndex;
UPROPERTY(VisibleAnywhere)
ESlotType SlotType;
};
- 기존에 만든 MMSlot Class에 Drog&Drop 기능을 추가해주도록 하겠습니다.
우선 마우스 입력을 받아주기 위한 함수를 오버라이딩 해주도록 합니다.
추가적으로 드래그&드랍에 필요한 함수도 오버라이딩 해주도록 하겠습니다.
// MMSlot Header
virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
virtual void NativeOnDragDetected(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent, UDragDropOperation*& OutOperation)override;
virtual bool NativeOnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation)override;
...
// Drag에 사용될 WidgetClass
// (Slot으로 설정해 드래그 시 Slot이 그대로 마우스를 따라다니게 할 것)
UPROPERTY(EditAnywhere, Category = "Slot")
TSubclassOf<UMMSlot> DragWidgetClass;
NativeOnMouseButtonDown 함수는 언리얼 엔진에서 사용자 정의 위젯 클래스에서 마우스 버튼이 눌렸을 때 호출되는 이벤트 처리 함수입니다.
위 함수를 통해 좌클릭 이벤트를 추가해주도록 하겠습니다.
FReply UMMSlot::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
FEventReply Reply;
Reply.NativeReply = Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);
// 좌클릭 입력이 들어온 경우
if (InMouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
{
// 정보 체크용 변수
bool Success = false;
// 해당 슬롯에 아이템 정보가 존재하는지 체크합니다.
IMMInventoryInterface* InvPlayer = Cast<IMMInventoryInterface>(OwningActor);
if (InvPlayer)
{
switch (SlotType)
{
case ESlotType::ST_InventoryEquipment:
if (IsValid(InvPlayer->GetInventoryComponent()->GetEquipmentItems()[SlotIndex]))
{
Success = true;
}
break;
case ESlotType::ST_InventoryConsumable:
if (IsValid(InvPlayer->GetInventoryComponent()->GetConsumableItems()[SlotIndex]))
{
Success = true;
}
break;
case ESlotType::ST_InventoryOther:
if (IsValid(InvPlayer->GetInventoryComponent()->GetOtherItems()[SlotIndex]))
{
Success = true;
}
break;
}
if (Success)
{
// 유효한 아이템이 존재하면 드래그 이벤트를 감지하도록 UWidgetBlueprintLibrary::DetectDragIfPressed 함수를 호출합니다.
Reply = UWidgetBlueprintLibrary::DetectDragIfPressed(InMouseEvent, this, EKeys::LeftMouseButton);
}
}
}
return Reply.NativeReply;
}
좌클릭 시 아이템이 유효하다면 DetectDragIfPressed() 함수를 호출하게 되며 드래그가 감지되었을 때 NativeOnDragDetected() 함수가 자동으로 호출됩니다.
NativeOnDragDetected() 함수는 드래그가 시작될 때 호출되며 NativeOnDrop() 함수는 드래그가 종료될 때 호출됩니다.
세부 로직을 구현해주도록 하겠습니다.
void UMMSlot::NativeOnDragDetected(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent, UDragDropOperation*& OutOperation)
{
Super::NativeOnDragDetected(InGeometry, InMouseEvent, OutOperation);
if (OutOperation == nullptr)
{
// 드래그 슬롯을 생성합니다.
UMMDragSlot* Operation = NewObject<UMMDragSlot>();
OutOperation = Operation;
// 슬롯과 슬롯 타입을 지정합니다.
Operation->PrevSlotIndex = SlotIndex;
Operation->SlotType = SlotType;
// Drag 위젯을 생성합니다.
if (DragWidgetClass)
{
UMMSlot* DragWidget = CreateWidget<UMMSlot>(GetWorld(), DragWidgetClass);
if (DragWidget)
{
// 생성된 위젯을 초기화해줍니다.
DragWidget->SlotType = SlotType;
DragWidget->SetOwningActor(OwningActor);
DragWidget->SlotIndex = SlotIndex;
DragWidget->Init();
// 드래그 슬롯의 드래그 위젯을 설정합니다.
Operation->DefaultDragVisual = DragWidget;
}
}
}
}
bool UMMSlot::NativeOnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation)
{
Super::NativeOnDrop(InGeometry, InDragDropEvent, InOperation);
UMMDragSlot* Operation = Cast<UMMDragSlot>(InOperation);
if (Operation)
{
// 같은 타입의 슬롯인 경우
if (Operation->SlotType == SlotType)
{
// Operation에 저장된 PrevSlotIndex 위치의 아이템을 현재 SlotIndex의 아이템과 교체합니다.
IMMInventoryInterface* InvPlayer = Cast<IMMInventoryInterface>(OwningActor);
if (InvPlayer)
{
InvPlayer->GetInventoryComponent()->SwapItem(Operation->PrevSlotIndex, SlotIndex, Operation->SlotType, SlotType);
}
return true;
}
return false;
}
전체 과정을 간단히 요약해보자면 다음과 같습니다.
즉, 드래그 시작위치의 인덱스와 슬롯 타입을 저장하여 드래그 종료 위치의 인덱스와 교체하는 로직입니다.