[ Unreal Engine 5 / #13 Shop & Item & Inventory3 ]

SeungWoo·2024년 9월 25일
0

[ Ureal Engine 5 / 수업 ]

목록 보기
14/31
post-thumbnail

  • 현재 이런 상태이다 보니 Shop 부분에 아이템을 넣어서 보기 편하게 나열 시켜보자

  • Bind가 아닌 값을 직접 할당하는 방안을 선택하는건, 위와 같이 문자 조합따위로 String로 변환해야할 떄
    ex) 국가별 언어관리

Purchasing 기능

상점기능 구매 기능을 추가 해보겠습니다

Player가 거래를 하므로 해당 거래 로직을 Player로직을 해보겠습니다

TPSPlayer.h

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Status")
	int64 CurrentMoney = 9999;
	
	UFUNCTION(BlueprintCallable)
	void UpdateMoney(int64 inputVal);

TPSPlayer.cpp

void ACTPSPlayer::UpdateMoney(int64 inputVal)
{
	int64 _result;
	_result = CurrentMoney + inputVal;

	if (_result < 0)
	{
		GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT(" Not Enough Money "));
	}
	else
	{
		CurrentMoney = _result;
	}
}
  • 임의 재화 9999를 가지게 하고 이 재화로 상점에 물건 Icon 버튼을 누를시 차감되고, 재화가 0 밑으로 떨어질 경우 경고 메세지 를 보낸다

  • Player의 재화를 나타내는 UI를 제작

  • 해당 버튼을 누를시 재화가 떨어지는 블루프린트이다

INVENTORY Component Class

  • 플레이어로직에 Inventory 로직을 제어할 경우

    • 플레이어 클래스에 직접 변수 선언 : 플레이어 클래스에 직접 인벤토리를 정의하고 관리하게 되면, 인벤토리 로직과 플레이어 로직이 강하게 결합. 즉, 플레이어 클래스가 무거워지고, 인벤토리 관련 로직이 복잡할수록 플레이어 클래스 자체가 복잡해짐 —> 그렇기에 ,~
    • Stack 개념의 Item 이 있으면, 복잡도가 증가 한다
      Array 를 이용하였기에,
      Item 검색이 매우 비효율적이다
  • 따로 만들고 그 Component를 붙여줘서 재 사용성을 늘리는 로직을 짜보자

  • Actor Component 형태로 붙여주면서 해당 Inventory를 사용할 수 있게 C++ 클래스 하나를 만든다
  • 이름은 InventoryActorComponent로 한다
  • ActorComponent는 액터에 추가되는 모듈화된 기능이나 행동을 정의하는 클래스입니다
    • 모듈성 : 액터에 여러 컴포넌트를 추가하여 액터의 기능을 나누어 구현할 수 있습니다. 각 컴포넌트는 독립적이므로 다양한 액터에 재사용이 가능합니다.
    • 계층화 : 컴포넌트가 서로 간에 독립적이지만 부모 액터의 라이프사이클과 밀접하게 연관되어 있습니다.
    • 네트워킹: ActorComponent는 네트워크 게임에서 상태 동기화 및 복제 기능을 처리할 수 있습니다.
    • 타임라인에 의한 관리 : ActorComponent는 BeginPlay, Tick 등 액터의 기본 라이프사이클 메서드에 접근할 수 있습니다.

InventoryActorComponent.h

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "ItemData.h"
#include "InventoryActorComponent.generated.h"


USTRUCT(BlueprintType)
struct FInventoryItem
{
	GENERATED_BODY()

	// 아이템의 데이터
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
	FItemData ItemData;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
	int32 Quantity;

	FInventoryItem()
		: ItemData(), Quantity(1) // 기본 수량은 1로 한다
	{}
};

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class MAGICIAN_PROJECT_API UInventoryActorComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	UInventoryActorComponent();
	
    // 아이템의 고유 ID를 키로 하는 인벤토리 맵 
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
	TMap<int32, FInventoryItem> Inventory;
	
    // DataTable로 부터 아이템을 로드
	UFUNCTION(BlueprintCallable, Category = "Inventory")
	void LoadItemFromDataTable(UDataTable* ItemDataTable);

	// 인벤토리에 아이템 추가
	UFUNCTION(BlueprintCallable, Category = "Inventory")
	void AddItemToInventory(int32 ItemID, int32 Quantity);

	// 인벤토리에 아이템 제거
	UFUNCTION(BlueprintCallable, Category = "Inventory")
	void RemoveItemFromInventory(int32 ItemID, int32 Quantity);

	// 인벤토리가 가득 찼는지 확인 
	UFUNCTION(BlueprintCallable, Category = "Inventory")
	bool IsInventoryFull() const;
	
    // 인벤토리의 최대 크기
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
	int32 MaxInventorySize;


protected:
	// Called when the game starts
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;		
};


Map (맵) :

  • Map은 키-값(key-value) 쌍을 저장하는 데이터 구조로, 각 키는 해당하는 값과 연관됩니다. Unreal Engine의 TMap 클래스는 해시맵을 나타냅니다. 이는 특정 키를 기반으로 값을 검색하고, 매우 빠른 검색 시간을 제공
  • 특징
    • 각 요소는 특정 키를 기반으로 저장됩니다. 각 키는 유일해야 합니다.
    • 데이터의 삽입, 검색 및 삭제가 빠릅니다.
    • 순서가 보장되지 않으며, 순차적으로 반복하는 것은 기대되지 않습니다.
    • 중복된 키를 가질 수 없지만(Unique), 값은 중복될 수 있습니다.

InventoryActorComponent.cpp

void UInventoryActorComponent::LoadItemFromDataTable(UDataTable* ItemDataTable)
{
	if (ItemDataTable)
	{
		TArray<FName> RowNames = ItemDataTable->GetRowNames();

		for (const FName& RowName : RowNames)
		{
			FItemData* ItemData = ItemDataTable->FindRow<FItemData>(RowName, "");
			if (ItemData)
			{
				// ItemID를 키로 하여 인벤토리에 추가 ( 기본 수량 1 )
				FInventoryItem NewItem;
				NewItem.ItemData = *ItemData;
				NewItem.Quantity = 1; // 처음 로드 시 기본 수량을 1로 설정

				Inventory.Add(ItemData->ItemID, NewItem);
			}
		}
	}

}
void UInventoryActorComponent::AddItemToInventory(int32 ItemID, int32 Quantity)
{
	// 인벤토리가 가득 찼는지 확인 
	if (IsInventoryFull())
	{
		UE_LOG(LogTemp, Warning, TEXT("Inventory is full"));
		return;
	}

	// 이미 인벤토리에 해당 아이템이 있는지 확인
	if (Inventory.Contains(ItemID))
	{
		FInventoryItem& ExitingItem = Inventory[ItemID];

		// 아이템이 스택 가능할 경우 수량을 추가
		if (ExitingItem.ItemData.bIsStackable)
		{
			ExitingItem.Quantity += Quantity;
			UE_LOG(LogTemp, Warning, TEXT("Increased quantity of item with ID %d to %d "), ItemID, Quantity);
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("Item with ID %d is not stackable!"), ItemID);
		}
	}
	else
	{
		// DataTable 에서 해당 ID의 아이템을 로드하여 추가
		FInventoryItem NewItem;
		NewItem.ItemData.ItemID = ItemID; // 기본 데이터 설정
		NewItem.Quantity = Quantity;
		Inventory.Add(ItemID, NewItem);

		UE_LOG(LogTemp, Warning, TEXT("Added new item with ID %d, Quantity : %d "), ItemID, Quantity);
	}

}
void UInventoryActorComponent::RemoveItemFromInventory(int32 ItemID, int32 Quantity)
{
	if (Inventory.Contains(ItemID))
	{
		FInventoryItem& ItemData = Inventory[ItemID];

		// 아이템의 수량을 감소시키고 0 이하일 경우 제거
		ItemData.Quantity -= Quantity;
		if (ItemData.Quantity <= 0)
		{
			Inventory.Remove(ItemID);
			UE_LOG(LogTemp, Warning, TEXT("Removed item with ID %d from inventory."), ItemID)
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("Decrease quantity of item with ID %d to %d "), ItemID, Quantity);
		}
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("Item with ID %d not found in inventory. "), ItemID)
	}

}
bool UInventoryActorComponent::IsInventoryFull() const
{
	return Inventory.Num() >= MaxInventorySize;
}

profile
This is my study archive

0개의 댓글