개요
이번 포스팅에서는 이전 포스팅에 이어 실제 Slot을 사용한 Inventory 위젯을 제작해보도록 하겠습니다.
Inventory 디자인
이전 포스팅에서 제작한 Slot을 가로 / 세로 박스에 넣고, 소비 / 기타창을 구분하기 위한 버튼과 골드량을 추가하였으며, 슬롯의 경우 해당 칸 수에 따라 인덱스를 저장해주었습니다.
Inventory Wiget (C++)
기존에는 meta = (BindWidget) 키워드를 통해 각각의 위젯을 코드와 바인딩시켰으나, 이와 같이 반복되는 위젯의 양이 많은 경우 하나씩 추가해서 관리하는 것보다는 다음과 같이 TArray<>를 통해 묶어서 관리하는 것이 편하다고 생각하였습니다.
이제 모든 슬롯들을 런타임 도중 찾아서 해당 배열에 추가해야 했으며, WidgetTree를 통해 모든 Widget을 불러올 수 있었습니다.
WidgetTree를 사용하기 위해서는 #include "Blueprint/WidgetTree.h" 헤더를 추가해줘야 합니다.
다음으로는 인벤토리를 드래그&드랍으로 위치를 변경하는 방법입니다.
기존 Slot을 구현할 때는 여러 겹의 이미지를 사용해야 해서 DragOperation 클래스를 사용했지만, 이번에는 그렇지 않기에 버튼으로 Pressed & Released 이벤트를 받아 구현하게 되었습니다.
우선 Pressed 이벤트 발생시 클릭된 버튼의 위치와 거기서의 마우스 커서 위치(offset)을 저장하였습니다.
Released 이벤트 발생 시에는 bIsDragging 변수를 false로 처리하기만 해주고, 나머지는 Tick() 함수에서 실제 인벤토리 위젯의 위치를 변경시켰습니다.
UserWidget에서는 NativeTick() 함수가 일반적인 Tick() 함수의 역할을 하며, 이를 통해 bIsDragging 변수가 True인 경우 위젯의 위치를 현재 마우스 위치로 보정값(offset)을 적용하여 이동시켜주었습니다.
Inventory Wiget 초기화 및 업데이트
Inventory 위젯은 소비/기타 타입을 enum을 통해 구분하고 있으며, 초기화 시 해당 값에 따라 내부에 위치한 모든 Slot을 초기화하였으며, 골드 또한 인벤토리 컴포넌트로부터 값을 받아와 처리하였습니다.
인벤토리 컴포넌트의 Delegate와 Binding
제작된 인벤토리 위젯은 활성화 시 플레이어의 입력을 받아야하므로, HUD 위젯이라는 큰 틀 안에 부품으로 추가해주었습니다.
또한, PlayerController에서 HUD 위젯을 초기화함과 동시에 인벤토리에서 선언했던 델리게이트(골드, 인벤 변경)와 업데이트 함수를 바인딩 해주는 방식으로 실제로 아이템의 변동이 존재할 때만 업데이트 함수가 실행되도록 구현하였습니다.
Inventory 전체 코드
Inventory.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UI/TA_CustomWidget.h"
#include "TA_Inventory.generated.h"
UENUM(BlueprintType)
enum class EInventoryType : uint8
{
IT_Consum, // 소비
IT_Misc, // 기타
};
UCLASS()
class TIMELESSADVENTURE_API UTA_Inventory : public UTA_CustomWidget
{
GENERATED_BODY()
protected:
virtual void NativeConstruct() override;
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
public:
void InitInventory();
void UpdateInvenSlot();
void UpdateGold();
private:
UFUNCTION()
void ChangeInventoryType_C();
UFUNCTION()
void ChangeInventoryType_M();
UFUNCTION()
void PressMoveBTN();
UFUNCTION()
void ReleaseMoveBTN();
private:
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UButton> BTN_Frame;
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UButton> BTN_ChangeC;
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UButton> BTN_ChangeM;
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UTextBlock> TXT_Gold;
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UBorder> B_Main;
UPROPERTY()
TArray<TObjectPtr<class UTA_Slot>> Slots;
private:
EInventoryType InventoryType;
bool bIsDragging;
FVector2D InitialOffset;
FVector2D InitialPos;
};
Inventory.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/TA_Inventory.h"
#include "Data/TA_SlotType.h"
#include "UI/TA_Slot.h"
#include "Interface/InventoryInterface.h"
#include "Component/TA_InventoryComponent.h"
#include "Blueprint/WidgetTree.h"
#include "Components/Button.h"
#include "Components/Border.h"
#include "Components/TextBlock.h"
#include "Blueprint/WidgetLayoutLibrary.h"
#include "Components/CanvasPanelSlot.h"
void UTA_Inventory::NativeConstruct()
{
Super::NativeConstruct();
if (BTN_ChangeC) BTN_ChangeC->OnClicked.AddDynamic(this, &UTA_Inventory::ChangeInventoryType_C);
if (BTN_ChangeM) BTN_ChangeM->OnClicked.AddDynamic(this, &UTA_Inventory::ChangeInventoryType_M);
if (BTN_Frame) BTN_Frame->OnPressed.AddDynamic(this, &UTA_Inventory::PressMoveBTN);
if (BTN_Frame) BTN_Frame->OnReleased.AddDynamic(this, &UTA_Inventory::ReleaseMoveBTN);
TArray<UWidget*> TempWidgets;
WidgetTree->GetAllWidgets(TempWidgets);
for (UWidget* Widget : TempWidgets)
{
if (Widget->GetName().Contains(TEXT("Slot")))
{
UTA_Slot* InvSlot = Cast<UTA_Slot>(Widget);
if (InvSlot)
{
InvSlot->SetOwnerPlayer(OwnerActor);
Slots.Push(InvSlot);
}
}
}
InventoryType = EInventoryType::IT_Consum;
}
void UTA_Inventory::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
Super::NativeTick(MyGeometry, InDeltaTime);
if (bIsDragging)
{
FVector2D MousePos = UWidgetLayoutLibrary::GetMousePositionOnViewport(GetWorld());
float DeltaX = InitialOffset.X - MousePos.X;
float DeltaY = InitialOffset.Y - MousePos.Y;
InitialPos.X += -DeltaX;
InitialPos.Y += -DeltaY;
InitialOffset = MousePos;
UCanvasPanelSlot* slot = UWidgetLayoutLibrary::SlotAsCanvasSlot(B_Main);
if (slot)
{
slot->SetPosition(InitialPos);
}
}
}
void UTA_Inventory::InitInventory()
{
// 슬롯 초기화
UpdateInvenSlot();
// 골드 초기화
UpdateGold();
}
void UTA_Inventory::ChangeInventoryType_C()
{
UE_LOG(LogTemp, Warning, TEXT("C"));
InventoryType = EInventoryType::IT_Consum;
UpdateInvenSlot();
}
void UTA_Inventory::ChangeInventoryType_M()
{
UE_LOG(LogTemp, Warning, TEXT("M"));
InventoryType = EInventoryType::IT_Misc;
UpdateInvenSlot();
}
void UTA_Inventory::PressMoveBTN()
{
bIsDragging = true;
FVector2D WidgetPos;
UCanvasPanelSlot* slot = UWidgetLayoutLibrary::SlotAsCanvasSlot(B_Main);
if (slot)
{
WidgetPos = slot->GetPosition();
}
InitialOffset = UWidgetLayoutLibrary::GetMousePositionOnViewport(this);
InitialPos = WidgetPos;
}
void UTA_Inventory::ReleaseMoveBTN()
{
bIsDragging = false;
}
void UTA_Inventory::UpdateInvenSlot()
{
// 슬롯 초기화
for (UTA_Slot* InvSlot : Slots)
{
switch (InventoryType)
{
case EInventoryType::IT_Consum:
InvSlot->Init(ESlotType::ST_Inventory_C);
break;
case EInventoryType::IT_Misc:
InvSlot->Init(ESlotType::ST_Inventory_M);
break;
default:
break;
}
}
}
void UTA_Inventory::UpdateGold()
{
IInventoryInterface* InvInterface = Cast<IInventoryInterface>(OwnerActor);
if (InvInterface)
{
TXT_Gold->SetText(FText::FromString(FString::FromInt(InvInterface->GetInventory()->GetGold())));
}
}
결과 영상
'프로젝트 > TimelessAdventure' 카테고리의 다른 글
[UE Team Project/T.A.] 11. Boss AI (0) | 2024.10.22 |
---|---|
[UE Team Project/T.A.] 10. 무기 교체 (CombatComponent, UI) (0) | 2024.10.11 |
[UE Team Project/T.A.] 8. 아이템 및 인벤토리 - 2 (UI - Slot) (0) | 2024.10.10 |
[UE Team Project/T.A.] 7. 아이템 및 인벤토리 - 1 (Component) (0) | 2024.10.10 |
[UE Team Project/T.A.] 6. 원거리 공격(활) 구현 / 에임 오프셋 (0) | 2024.09.30 |