[UE5] UI (3)

GamzaTori·2024년 11월 5일

UE5 C++

목록 보기
24/27

이전에 만든 MyUserWidget을 상속받은 클래스 생성

// h
public:
	UMyInventoryEntryWidget(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());


public:
	void Init(UMyInventorySlotsWidget* InSlotsWidget, UMyItemInstance* InItemInstance, int32 InItemCount);

protected:
	virtual void NativeConstruct() override;

private:
	FIntPoint CachedFromSlotPos = FIntPoint::ZeroValue;		// 아이템의 위치 캐싱
	FVector2D CachedDeltaWidgetPos = FVector2D::ZeroVector;
	int32 ItemCount = 0;

protected:
	UPROPERTY()
	TObjectPtr<UMyInventorySlotsWidget> SlotsWidget;	// 해당 위젯 위에 아이템을 생성하기 위해 캐싱

	UPROPERTY()
	TObjectPtr<UMyItemInstance> ItemInstance;			// 아이템의 정보

protected:
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<USizeBox> SizeBox_Root;		// 크기를 지정하기 위함

	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UTextBlock> Text_Count;		// 아이템의 개수

	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UImage> Image_Icon;			// 사용할 이미지의 아이콘

	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UImage> Image_Hover;			// 아이템에 마우스를 갖다대면 아이템 위에 표시
  • SizeBox를 통해 아이템의 사이즈를 정하고
  • Image_Icon 위에 마우스를 갖다대면 Image_Hover의 불투명도를 조절하여 호버링 표시
  • 아이템에 대한 정보와 아이템의 아이콘으로 InventorySlots에서 관리
// cpp
void UMyInventoryEntryWidget::Init(UMyInventorySlotsWidget* InSlotsWidget, UMyItemInstance* InItemInstance, int32 InItemCount)
{
	SlotsWidget = InSlotsWidget;
	ItemInstance = InItemInstance;
	ItemCount = InItemCount;
}

void UMyInventoryEntryWidget::NativeConstruct()
{
	Super::NativeConstruct();

	Text_Count->SetText(FText::GetEmpty());
}

이후 이름에 맞게 위젯 블루프린트에서 생성

마우스 및 드래그 이벤트 감지 추가

// h
protected:
    // 마우스 및 드래그 드롭 감지
	virtual void NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
	virtual void NativeOnMouseLeave(const FPointerEvent& InMouseEvent) override;
	virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
	virtual void NativeOnDragDetected(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent, UDragDropOperation*& OutOperation) override;
	virtual void NativeOnDragCancelled(const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation) override;
// cpp
void UMyInventoryEntryWidget::NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
	Super::NativeOnMouseEnter(InGeometry, InMouseEvent);

	// 마우스가 아이콘 위에 올라오면 불투명도를 조절해 호버링
	Image_Hover->SetRenderOpacity(1.f);
}

void UMyInventoryEntryWidget::NativeOnMouseLeave(const FPointerEvent& InMouseEvent)
{
	Super::NativeOnMouseLeave(InMouseEvent);

	// 마우스가 아이콘에서 떠나면 불투명도를 조절해 호버링 취소
	Image_Hover->SetRenderOpacity(0.f);
}

FReply UMyInventoryEntryWidget::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
	FReply Reply = Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);

	if (InMouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
	{
    	// 왼쪽 마우스 버튼이 눌리면 드래그 감지 추가
		Reply.DetectDrag(TakeWidget(), EKeys::LeftMouseButton);
	}

	return Reply;
}
  • 이미지의 SetRenderOpacity를 이용해서 호버링되는 이미지가 마우스를 갖다 댔을때 보였다가 떼면 안보이도록 조정

  • 이미지의 외곽에 브러쉬를 이용해 외곽선 표시

InventorySlotsWidget에 InventoryEntryWidget을 추가

// h
protected:
	UPROPERTY()
	TSubclassOf<UR1InventoryEntryWidget> EntryWidgetClass;

	UPROPERTY()
	TArray<TObjectPtr<UR1InventoryEntryWidget>> EntryWidgets;
  • 아이템이 여러개일 수 있으므로 TArray로 가지고 있는다
// cpp
UMyInventorySlotsWidget::UMyInventorySlotsWidget(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	
	ConstructorHelpers::FClassFinder<UMyInventorySlotWidget> FindSlotWidgetClass(TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/UI/Item/Inventory/WBP_InventorySlot.WBP_InventorySlot_C'"));
	if (FindSlotWidgetClass.Succeeded())
	{
		SlotWidgetClass = FindSlotWidgetClass.Class;
	}
	ConstructorHelpers::FClassFinder<UMyInventoryEntryWidget> FindEntryWidgetClass(TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/UI/Item/Inventory/WBP_InventoryEntry.WBP_InventoryEntry_C'"));
	if (FindEntryWidgetClass.Succeeded())
	{
		EntryWidgetClass = FindEntryWidgetClass.Class;
	}
}
  • Slot과 마찬가지로 Entry를 FClassFinder로 가져온다
void UMyInventorySlotsWidget::NativeConstruct()
{
	Super::NativeConstruct();
	
	SlotWidgets.SetNum(X_COUNT * Y_COUNT);

	for (int32 y = 0; y < Y_COUNT; y++)
	{
		for (int32 x = 0; x < X_COUNT; x++)
		{
			int32 index = y * X_COUNT + x;

			URMynventorySlotWidget* SlotWidget = CreateWidget<UMyInventorySlotWidget>(GetOwningPlayer(), SlotWidgetClass);
			SlotWidgets[index] = SlotWidget;
			GridPanel_Slots->AddChildToUniformGrid(SlotWidget, y, x);
		}
	}

	EntryWidgets.SetNum(X_COUNT * Y_COUNT);

	UMyInventorySubsystem* Inventory = Cast<UMyInventorySubsystem>(USubsystemBlueprintLibrary::GetWorldSubsystem(this, UMyInventorySubsystem::StaticClass()));

	const TArray<TObjectPtr<UMyItemInstance>>& Items = Inventory->GetItems();

	for (int32 i = 0; i < Items.Num(); i++)
	{
		const TObjectPtr<UMyItemInstance>& Item = Items[i];
		FIntPoint ItemSlotPos = FIntPoint(i % X_COUNT, i / X_COUNT);
		OnInventoryEntryChanged(ItemSlotPos, Item);
	}
}
  • Slot과 마찬가지로 Entry도 5x10 크기 만큼 할당하고
  • Subsystem에서 만든 Inventory의 아이템들을 가져와서 ItemSlotPos 위치에 아이템을 배치

인벤토리에 아이템을 해당 위치에 배치하기

// h
protected:
	void OnInventoryEntryChanged(const FIntPoint& ItemSlotPos, TObjectPtr<UMyItemInstance> Item);
    
// cpp
void UMyInventorySlotsWidget::OnInventoryEntryChanged(const FIntPoint& InItemSlotPos, TObjectPtr<UMyItemInstance> Item)
{
	// 2차원 좌표를 1차원 인덱스로 변경
	int32 SlotIndex = InItemSlotPos.Y * X_COUNT + InItemSlotPos.X;

	if (UMyInventoryEntryWidget* EntryWidget = EntryWidgets[SlotIndex])
	{
    	// 해당 인덱스에 EntryWidget이 이미 있었으면
		if (Item == nullptr)
		{
        	// 옮기는 아이템이 비어있으면 해당 아이템 제거
			CanvasPanel_Entries->RemoveChild(EntryWidget);
			EntryWidgets[SlotIndex] = nullptr;
		}
	}
	else
	{
    	// 해당 인덱스에 아무것도 없으면 아이템 생성 후 추가
		EntryWidget = CreateWidget<UMyInventoryEntryWidget>(GetOwningPlayer(), EntryWidgetClass);
		EntryWidgets[SlotIndex] = EntryWidget;

		UCanvasPanelSlot* CanvasPanelSlot = CanvasPanel_Entries->AddChildToCanvas(EntryWidget);
		CanvasPanelSlot->SetAutoSize(true);
		CanvasPanelSlot->SetPosition(FVector2D(InItemSlotPos.X * 50, InItemSlotPos.Y * 50));

		// 임시로 아이템 1개 생성
        // TODO 아이템 개수의 정보를 저장해서 해당 개수만큼 생성하도록
        // ex) 물약 100개
		EntryWidget->Init(this, Item, 1);
	}
}

Drag & Drop 구현

DragDrop Operation을 상속받은 클래스 생성

// h
public:
	UMyDragDropOperation(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());

public:
	FIntPoint FromItemSlotPos = FIntPoint::ZeroValue;		// 아이템의 원래 위치

	UPROPERTY()
	TObjectPtr<UR1ItemInstance> ItemInstance;

	FVector2D DeltaWidgetPos = FVector2D::ZeroVector;		// 마우스와 아이템의 거리
  • 아이템의 정보와 위치를 가지고 있는다

Drag에 사용될 Widget 생성

  • InventoryEntryWidget: 인벤토리 위에 아이템이 그려지는 위젯
  • ItemDragWidget: 드래그해서 들고있는 아이템을 그려지는 위젯
// h
public:
	UMyItemDragWidget(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());

public:
	void Init(const FVector2D& InWidgetSize, UTexture2D* InItemIcon, int32 InItemCount);

protected:
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<USizeBox> SizeBox_Root;

	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UImage> Image_Icon;

	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UTextBlock> Text_Count;
    
// cpp
void UMyItemDragWidget::Init(const FVector2D& InWidgetSize, UTexture2D* InItemIcon, int32 InItemCount)
{
	SizeBox_Root->SetWidthOverride(InWidgetSize.X);
	SizeBox_Root->SetHeightOverride(InWidgetSize.Y);

	Image_Icon->SetBrushFromTexture(InItemIcon);
	Text_Count->SetText((InItemCount >= 2) ? FText::AsNumber(InItemCount) : FText::GetEmpty());
}

EntryWidget에서 마우스로 누른 아이템의 위치 가져오기

// cpp
FReply UMyInventoryEntryWidget::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
	FReply Reply = Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);

	if (InMouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
	{
		Reply.DetectDrag(TakeWidget(), EKeys::LeftMouseButton);
	}

	const FIntPoint UnitInventorySlotSize = FIntPoint(50, 50);

	FVector2D MouseWidgetPos = SlotsWidget->GetCachedGeometry().AbsoluteToLocal(InMouseEvent.GetScreenSpacePosition());
	FVector2D ItemWidgetPos = SlotsWidget->GetCachedGeometry().AbsoluteToLocal(InGeometry.LocalToAbsolute(UnitInventorySlotSize / 2.f));
	FIntPoint ItemSlotPos = FIntPoint(ItemWidgetPos.X / UnitInventorySlotSize.X, ItemWidgetPos.Y / UnitInventorySlotSize.Y);

	CachedFromSlotPos = ItemSlotPos;
	CachedDeltaWidgetPos = MouseWidgetPos - ItemWidgetPos;

	return Reply;
}

EntryWidget에서 드래그 시 드래그 위젯생성

// h
protected:
	UPROPERTY()
	TSubclassOf<UMyItemDragWidget> DragWidgetClass;		// 생성자에서 FClassFinder로 찾아서 초기화

// cpp
void UMyInventoryEntryWidget::NativeOnDragDetected(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent, OUT UDragDropOperation*& OutOperation)
{
	Super::NativeOnDragDetected(InGeometry, InMouseEvent, OutOperation);
	 
     // 드래그 드롭에 사용될 위젯 생성
	UMyItemDragWidget* DragWidget = CreateWidget<UMyItemDragWidget>(GetOwningPlayer(), DragWidgetClass);
	FVector2D EntityWidgetSize = FVector2D(1 * 50, 1 * 50);
	DragWidget->Init(EntityWidgetSize, nullptr, ItemCount);

	UMyDragDropOperation* DragDrop = NewObject<UMyDragDropOperation>();
	DragDrop->DefaultDragVisual = DragWidget;		// 드래그 위젯 등록
	DragDrop->Pivot = EDragPivot::MouseDown;
	DragDrop->FromItemSlotPos = CachedFromSlotPos;
	DragDrop->ItemInstance = ItemInstance;
	DragDrop->DeltaWidgetPos = CachedDeltaWidgetPos;

	OutOperation = DragDrop;
}

아이템 개수와 투명도를 조절하는 기능 추가

// h
protected:
	void RefreshWidgetOpacity(bool bClearlyVisible);
	void RefreshItemCount(int32 NewItemCount);

// cpp
void UMyInventoryEntryWidget::RefreshWidgetOpacity(bool bClearlyVisible)
{
	SetRenderOpacity(bClearlyVisible ? 1.f : 0.5f);
}

void UMyInventoryEntryWidget::RefreshItemCount(int32 NewItemCount)
{
	ItemCount = NewItemCount;
	Text_Count->SetText((ItemCount >= 2) ? FText::AsNumber(ItemCount) : FText::GetEmpty());
}

InventorySlots에 드래그 관련 함수 추가

// h
protected:
	virtual bool NativeOnDragOver(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation) override;
	virtual void NativeOnDragLeave(const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation) override;
	virtual bool NativeOnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation) override;

private:
	void FinishDrag();
    
private:
	FIntPoint PrevDragOverSlotPos = FIntPoint(-1, -1);		// 이전 드래그 슬롯의 위치 캐싱
// cpp
bool UMyInventorySlotsWidget::NativeOnDragOver(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation)
{
	Super::NativeOnDragOver(InGeometry, InDragDropEvent, InOperation);

	UMyDragDropOperation* DragDrop = Cast<UMyDragDropOperation>(InOperation);
	check(DragDrop);

	FVector2D MouseWidgetPos = InGeometry.AbsoluteToLocal(InDragDropEvent.GetScreenSpacePosition());
	FVector2D ToWidgetPos = MouseWidgetPos - DragDrop->DeltaWidgetPos;
	FIntPoint ToSlotPos = FIntPoint(ToWidgetPos.X / 50.f, ToWidgetPos.Y / 50.f);

	if (PrevDragOverSlotPos == ToSlotPos)
    {
    	// 이전 슬롯과 위치가 같다면 패스 
		return true;
    }

	PrevDragOverSlotPos = ToSlotPos;

	// TODO

	return false;
}

void UMyInventorySlotsWidget::NativeOnDragLeave(const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation)
{
	Super::NativeOnDragLeave(InDragDropEvent, InOperation);
	FinishDrag();
}

bool UMyInventorySlotsWidget::NativeOnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation)
{
	Super::NativeOnDrop(InGeometry, InDragDropEvent, InOperation);
	FinishDrag();

	UMyDragDropOperation* DragDrop = Cast<UMyDragDropOperation>(InOperation);
	check(DragDrop);

	FVector2D MouseWidgetPos = InGeometry.AbsoluteToLocal(InDragDropEvent.GetScreenSpacePosition());
	FVector2D ToWidgetPos = MouseWidgetPos - DragDrop->DeltaWidgetPos;
	FIntPoint ToItemSlotPos = FIntPoint(ToWidgetPos.X / Item::UnitInventorySlotSize.X, ToWidgetPos.Y / Item::UnitInventorySlotSize.Y);

	// TODO
	if (DragDrop->FromItemSlotPos != ToItemSlotPos)
	{
    	// 이전 위치와 다르다면
        // 기존 위치에 아이템을 없애고
        // 새로운 위치로 아이템을 이동
		OnInventoryEntryChanged(DragDrop->FromItemSlotPos, nullptr);
		OnInventoryEntryChanged(ToItemSlotPos, DragDrop->ItemInstance);
	}

	return false;
}

void UMyInventorySlotsWidget::FinishDrag()
{
	// 드래그가 끝났으면 이전 슬롯의 위치 초기화
	PrevDragOverSlotPos = FIntPoint(-1, -1);
}
profile
게임 개발 공부중입니다.

0개의 댓글