[UE Team Project/T.A.] 9. 아이템 및 인벤토리 - 3 (UI - Inventory)

2024. 10. 11. 09:23·프로젝트/TimelessAdventure
728x90
반응형

개요

이번 포스팅에서는 이전 포스팅에 이어 실제 Slot을 사용한 Inventory 위젯을 제작해보도록 하겠습니다.


Inventory 디자인

인벤토리 디자인

 

이전 포스팅에서 제작한 Slot을 가로 / 세로 박스에 넣고, 소비 / 기타창을 구분하기 위한 버튼과 골드량을 추가하였으며, 슬롯의 경우 해당 칸 수에 따라 인덱스를 저장해주었습니다.


Inventory Wiget (C++)

기존에는 meta = (BindWidget) 키워드를 통해 각각의 위젯을 코드와 바인딩시켰으나, 이와 같이 반복되는 위젯의 양이 많은 경우 하나씩 추가해서 관리하는 것보다는 다음과 같이 TArray<>를 통해 묶어서 관리하는 것이 편하다고 생각하였습니다.

 

이제 모든 슬롯들을 런타임 도중 찾아서 해당 배열에 추가해야 했으며, WidgetTree를 통해 모든 Widget을 불러올 수 있었습니다.

 

WidgetTree를  사용하기 위해서는 #include "Blueprint/WidgetTree.h" 헤더를 추가해줘야 합니다.

실제 위젯에 들어있는 "Slot" 이름을 가진모든 슬롯을 저장하는 로직

 

다음으로는 인벤토리를 드래그&드랍으로 위치를 변경하는 방법입니다.

 

기존 Slot을 구현할 때는 여러 겹의 이미지를 사용해야 해서 DragOperation 클래스를 사용했지만, 이번에는 그렇지 않기에 버튼으로 Pressed & Released 이벤트를 받아 구현하게 되었습니다.

Button
함수 바인딩

우선 Pressed 이벤트 발생시 클릭된 버튼의 위치와 거기서의 마우스 커서 위치(offset)을 저장하였습니다.

 

Released 이벤트 발생 시에는 bIsDragging 변수를 false로 처리하기만 해주고, 나머지는 Tick() 함수에서 실제 인벤토리 위젯의 위치를 변경시켰습니다.

 

UserWidget에서는 NativeTick() 함수가 일반적인 Tick() 함수의 역할을 하며, 이를 통해 bIsDragging 변수가 True인 경우 위젯의 위치를 현재 마우스 위치로 보정값(offset)을 적용하여 이동시켜주었습니다.


Inventory Wiget 초기화 및 업데이트

Inventory 위젯은 소비/기타 타입을 enum을 통해 구분하고 있으며, 초기화 시 해당 값에 따라 내부에 위치한 모든 Slot을 초기화하였으며, 골드 또한 인벤토리 컴포넌트로부터 값을 받아와 처리하였습니다. 

 

개별 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())));
	}
}

결과 영상

인벤토리 / 퀵슬롯

 

728x90
반응형
저작자표시 비영리 변경금지 (새창열림)

'프로젝트 > 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
'프로젝트/TimelessAdventure' 카테고리의 다른 글
  • [UE Team Project/T.A.] 11. Boss AI
  • [UE Team Project/T.A.] 10. 무기 교체 (CombatComponent, UI)
  • [UE Team Project/T.A.] 8. 아이템 및 인벤토리 - 2 (UI - Slot)
  • [UE Team Project/T.A.] 7. 아이템 및 인벤토리 - 1 (Component)
리태s
리태s
게임 클라이언트 프로그래머 직무를 준비하며 공부한 내용을 정리한 블로그입니다.
    반응형
    250x250
  • 리태s
    LeeTaes 공부노트
    리태s
  • 전체
    오늘
    어제
    • Home (165)
      • 프로젝트 (20)
        • Isaac 3D (5)
        • TimelessAdventure (13)
        • FruitsPuzzle (2)
      • Game Programming (25)
        • C# (8)
        • Unity Engine (6)
        • Unreal Engine (8)
        • UE_Multiplayer (3)
      • 코딩테스트 (111)
        • 프로그래머스 (Lv. 0) (27)
        • 프로그래머스 (Lv. 1) (31)
        • 프로그래머스 (Lv. 2) (21)
        • 백준 (Study) (29)
        • 알고리즘 (3)
      • CS지식 (7)
        • 운영체제 (7)
      • 일상 (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    issac3d
    Algorithm
    프로세스
    2019 kakao
    sesac
    구현
    프로그래머스
    백준
    fsoftobjectpath
    dataasset
    청년취업사관학교
    C++
    timelessadventure
    후기
    project t.a develop
    CS지식
    2022 kakao
    코딩테스트
    Summer/Winter Coding
    pcce 기출문제
    2018 kakao
    unity
    ai controller
    delegate
    unrealengine
    tsoftobjectptr
    프로젝트
    Unreal Engine
    c#
    fruitspuzzle
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
리태s
[UE Team Project/T.A.] 9. 아이템 및 인벤토리 - 3 (UI - Inventory)
상단으로

티스토리툴바