[UE5 C++] 아이템 & 인벤토리 시스템 - 4

LeeTaes·2024년 5월 18일
0

[UE_Project] MysticMaze

목록 보기
15/17
post-thumbnail

언리얼 엔진을 사용한 RPG 프로젝트 만들기

  • 인벤토리 아이템 Drag & Drop 구현

DragSlot

  • 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;
};

Slot에 적용하기

  • 기존에 만든 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 함수는 언리얼 엔진에서 사용자 정의 위젯 클래스에서 마우스 버튼이 눌렸을 때 호출되는 이벤트 처리 함수입니다.

  • InGeometry는 위젯의 기하학적 정보(위치, 크기)를 가지고 있습니다.
  • InMouseEvent는 마우스 이벤트에 대한 정보를 가지며 어떤 버튼이 어떻게 눌렸는니 등의 정보를 제공해줍니다.

위 함수를 통해 좌클릭 이벤트를 추가해주도록 하겠습니다.

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;
}

결과 및 요약

전체 과정을 간단히 요약해보자면 다음과 같습니다.

  1. Drag를 시작할 때 해당 슬롯의 인덱스와 타입을 DragSlot에 저장하고 마우스를 따라다닐 위젯을 설정합니다.
  2. Drag를 종료하면 종료한 위치의 슬롯 인덱스와 타입을 인벤토리 컴포넌트에 넘겨줌으로써 아이템을 교체합니다.

즉, 드래그 시작위치의 인덱스와 슬롯 타입을 저장하여 드래그 종료 위치의 인덱스와 교체하는 로직입니다.

profile
클라이언트 프로그래머 지망생

0개의 댓글