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

LeeTaes·2024년 5월 15일
0

[UE_Project] MysticMaze

목록 보기
12/17

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

  • 아이템 데이터 애셋 제작하기
  • 상호작용 위젯 제작하기
  • 아이템 박스 제작 및 테스트

아이템 데이터 애셋 제작하기

  • 아이템의 정보를 저장하기 위한 데이터 애셋을 추가해주도록 하겠습니다.
  • 아이템의 종류는 3종류로 무기, 포션, 마나스톤이며 각각 필요한 정보를 저장할 수 있는 데이터 애셋 클래스를 생성해주도록 합니다.

추가적으로 AssetManager를 통해 데이터를 로드할 생각이므로 GetPrimaryAssetId() 함수를 오버라이드 해주도록 하겠습니다.
Unreal AssetManager Document

MMItemData Class

  • 아이템 데이터 애셋에서 공용으로 사용할 정보들을 추가해줍니다.
// MMItemData Header

#pragma once

#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "MMItemData.generated.h"

UENUM(BlueprintType)
enum class EItemType : uint8 
{ 
	IT_Weapon,		// 무기
	IT_Potion,		// 포션
	IT_ManaStone,	// 마석
};


/**
 * 
 */
UCLASS()
class MYSTICMAZE_API UMMItemData : public UPrimaryDataAsset
{
	GENERATED_BODY()
	
public:
	virtual FPrimaryAssetId GetPrimaryAssetId() const override
	{
		return FPrimaryAssetId("MMItemData", GetFName());
	}
	
public:
	// 아이템 타입
	UPROPERTY(EditAnywhere, Category = Base)
	EItemType ItemType;

	// 아이템 이미지
	UPROPERTY(EditAnywhere, Category = Base)
	TObjectPtr<UTexture2D> ItemTexture;

	// 아이템 이름
	UPROPERTY(EditAnywhere, Category = Base)
	FString ItemName;

	// 아이템 구매 가격
	UPROPERTY(EditAnywhere, Category = Base)
	int32 ItemPurchasePrice;

	// 아이템 판매 가격
	UPROPERTY(EditAnywhere, Category = Base)
	int32 ItemSalePrice;
};

MMWeaponItemData Class

  • 무기 아이템 데이터의 경우 4가지 정보를 추가하였습니다.
    - 실제 무기 액터를 스폰하기 위해 필요한 클래스
    - 구매에 필요한 재료 아이템의 수
    - 무기 스탯
    - 장착 가능한 직업 정보
// MMWeaponItemData Header
#pragma once

#include "CoreMinimal.h"
#include "Item/MMItemData.h"
#include "GameData/MMCharacterStat.h"
#include "GameData/MMEnums.h"
#include "MMWeaponItemData.generated.h"

/**
 * 
 */
UCLASS()
class MYSTICMAZE_API UMMWeaponItemData : public UMMItemData
{
	GENERATED_BODY()
	
public:
	UMMWeaponItemData();

	virtual FPrimaryAssetId GetPrimaryAssetId() const override
	{
		return FPrimaryAssetId("MMItemData", GetFName());
	}

public:
	// 생성할 무기 클래스
	UPROPERTY(EditAnywhere, Category = Weapon)
	TSubclassOf<class AMMWeapon> WeaponClass;

	// 구매에 필요한 재료 수(마나스톤)
	UPROPERTY(EditAnywhere, Category = Weapon)
	int32 ItemMaterialQuantity;

	// 무기의 스탯
	UPROPERTY(EditAnywhere, Category = Weapon)
	FMMCharacterStat WeaponStat;

	// 착용 가능한 클래스 정보
	UPROPERTY(EditAnywhere, Category = Weapon)
	EClassType PurchaseableClass;
};

MMPotionItemData Class

  • 포션의 타입과 회복량에 대한 정보를 추가하였습니다.
// MMPotionItemData Header

#pragma once

#include "CoreMinimal.h"
#include "Item/MMItemData.h"
#include "MMPotionItemData.generated.h"

UENUM(BlueprintType)
enum class EPotionType : uint8
{
	PT_Hp,		// 체력 포션
	PT_Mp,		// 마나 포션
};

/**
 * 
 */
UCLASS()
class MYSTICMAZE_API UMMPotionItemData : public UMMItemData
{
	GENERATED_BODY()
	
public:
	UMMPotionItemData();

	virtual FPrimaryAssetId GetPrimaryAssetId() const override
	{
		return FPrimaryAssetId("MMItemData", GetFName());
	}
	
public:
	UPROPERTY(EditAnywhere, Category = Potion)
	EPotionType PotionType;

	UPROPERTY(EditAnywhere, Category = Potion)
	float Percent;
};

MMManaStoneItemData Class

  • 한 종류의 마나스톤만 드랍될 예정이므로 추가만 했습니다.
// MMManaStoneItemData Header

#pragma once

#include "CoreMinimal.h"
#include "Item/MMItemData.h"
#include "MMManaStoneItemData.generated.h"

/**
 * 
 */
UCLASS()
class MYSTICMAZE_API UMMManaStoneItemData : public UMMItemData
{
	GENERATED_BODY()
	
public:
	UMMManaStoneItemData();

	virtual FPrimaryAssetId GetPrimaryAssetId() const override
	{
		return FPrimaryAssetId("MMItemData", GetFName());
	}
};

아이템 데이터 추가하기

  • 위에서 제작한 데이터 애셋 클래스를 상속받아 아이템 데이터 애셋을 생성합니다.
  • 무기 : 직업별 4종류
  • 포션 : Hp, Mp (대, 중, 소)
  • 마나스톤 : 1종

ItemData 폴더 내부에 모든 아이템 데이터 애셋을 추가해주도록 합니다.


아이템 박스 제작하기

  • 아이템 박스는 몬스터가 사망시 드랍하는 "드랍 아이템"입니다.
  • 아이템 박스에는 골드와 마나스톤만 들어있습니다.
  • 아이템 박스에 충돌체를 추가해 플레이어와 충돌 시 위젯을 보여주는 로직을 추가해보도록 하겠습니다.

아이템 박스에 사용할 콜리전 프리셋을 추가합니다.

  • MMCollision.h에도 다음 내용을 추가합니다.
    #define MMTRIGGER TEXT("MMTrigger")

MMInteractionWidget Class
아이템 박스에서 사용할 Interaction Widget을 간단히 제작해주도록 합니다.

// MMInteractionWidget Header

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "MMInteractionWidget.generated.h"

/**
 * 
 */
UCLASS()
class MYSTICMAZE_API UMMInteractionWidget : public UUserWidget
{
	GENERATED_BODY()
	
public:
	UPROPERTY(meta = (BindWidget = "true"))
	TObjectPtr<class UTextBlock> TXT_HelpText;

public:
	void SetHelpText(FString HelpText);
};

// MMInteractionWidget Cpp
#include "UI/MMInteractionWidget.h"
#include "Components/TextBlock.h"

void UMMInteractionWidget::SetHelpText(FString HelpText)
{
	// 텍스트 설정
	TXT_HelpText->SetText(FText::FromString(HelpText));
}

MMItemBox Class

  • 위에서 제작한 위젯을 포함해 모든 로직을 구현해주도록 합니다.
// MMItemBox Header

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Interface/MMInteractionInterface.h"
#include "MMItemBox.generated.h"

UCLASS()
class MYSTICMAZE_API AMMItemBox : public AActor, public IMMInteractionInterface
{
	GENERATED_BODY()
	
public:	
	AMMItemBox();

protected:
	virtual void BeginPlay() override;
	virtual void PostInitializeComponents() override;

public:
	FORCEINLINE int32 GetItemQuantity() { return ItemQuantity; }
	FORCEINLINE int32 GetGold() { return Gold; }
	FORCEINLINE FName GetItemName() { return ItemName; }

	// 아이템 박스에 아이템 데이터를 추가하기 위한 함수
	void AddItemQuantity(int32 InQuantity);
	// 아이템 박스에 골드를 추가하기 위한 함수
	void AddMoney(int32 InMoney);

protected:
	// 충돌 시작시 호출되는 함수
	UFUNCTION()
	void OnBeginOverlap(class UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
    // 충돌 종료시 호출되는 함수
	UFUNCTION()
	void OnEndOverlap(class UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);

	// 충돌에 사용될 트리거
	UPROPERTY(VisibleAnywhere, Category = Box)
	TObjectPtr<class UBoxComponent> Trigger;
	
    // 아이템 박스 Mesh
	UPROPERTY(VisibleAnywhere, Category = Box)
	TObjectPtr<class UStaticMeshComponent> Mesh;

// Interaction Widget
protected:
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Widget, Meta = (AllowPrivateAccess = true))
	TSubclassOf<class UMMInteractionWidget> InteractionWidgetClass;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Widget, Meta = (AllowPrivateAccess = true))
	TObjectPtr<class UMMInteractionWidget> InteractionWidget;

private:
	// 아이템 수량
	UPROPERTY(VisibleAnywhere, Category = Item)
	int32 ItemQuantity;

	// 골드량
	UPROPERTY(VisibleAnywhere, Category = Item)
	int32 Gold;

	// 아이템 이름 (마나스톤 데이터 애셋)
	UPROPERTY(VisibleAnywhere, Category = Item)
	FName ItemName;

	// Widget에 설정할 TEXT
	FString HelpText;
};
// MMItemBox Cpp

#include "Item/MMItemBox.h"
#include "Collision/MMCollision.h"
#include "Item/MMItemData.h"
#include "UI/MMInteractionWidget.h"

#include "GameFramework/Character.h"
#include "Components/BoxComponent.h"
#include "Components/StaticMeshComponent.h"

// Sets default values
AMMItemBox::AMMItemBox()
{
	Trigger = CreateDefaultSubobject<UBoxComponent>(TEXT("TriggerBox"));
	Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));

	RootComponent = Trigger;
	Mesh->SetupAttachment(Trigger);

	Trigger->SetCollisionProfileName(MMTRIGGER);
	Trigger->SetBoxExtent(FVector(50.0f, 50.0f, 50.0f));

	static ConstructorHelpers::FObjectFinder<UStaticMesh> BoxMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/MysticMaze/Items/ItemBox/Environment/Props/SM_Env_Breakables_Box1.SM_Env_Breakables_Box1'"));
	if (BoxMeshRef.Object)
	{
		Mesh->SetStaticMesh(BoxMeshRef.Object);
	}
	Mesh->SetCollisionProfileName(TEXT("NoCollision"));

	ItemName = TEXT("DA_ManaStone");

	// Interaction Widget
	static ConstructorHelpers::FClassFinder<UMMInteractionWidget> InteractionWidgetRef(TEXT("/Script/UMGEditor.WidgetBlueprint'/Game/MysticMaze/UI/WBP_Interaction.WBP_Interaction_C'"));
	if (InteractionWidgetRef.Class)
	{
		InteractionWidgetClass = InteractionWidgetRef.Class;
	}

	HelpText = TEXT("Press 'F' to pick up the item.");
}

// Called when the game starts or when spawned
void AMMItemBox::BeginPlay()
{
	Super::BeginPlay();
	
    // 위젯을 생성하고 비활성화 합니다.
	InteractionWidget = CreateWidget<UMMInteractionWidget>(GetWorld(), InteractionWidgetClass);
	if (InteractionWidget)
	{
		InteractionWidget->SetHelpText(HelpText);
		InteractionWidget->AddToViewport();
		InteractionWidget->SetVisibility(ESlateVisibility::Hidden);
	}
}

void AMMItemBox::PostInitializeComponents()
{
	Super::PostInitializeComponents();
	
    // 충돌 시작, 종료 이벤트를 매핑합니다.
	Trigger->OnComponentBeginOverlap.AddDynamic(this, &AMMItemBox::OnBeginOverlap);
	Trigger->OnComponentEndOverlap.AddDynamic(this, &AMMItemBox::OnEndOverlap);
}

void AMMItemBox::AddItemQuantity(int32 InQuantity)
{
	if (InQuantity < 1) return;

	// 아이템 수량을 설정합니다.
	ItemQuantity = InQuantity;
}

void AMMItemBox::AddMoney(int32 InMoney)
{
	if (InMoney < 0) return;

	// 골드를 설정합니다.
	Gold = InMoney;
}

void AMMItemBox::OnBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	// 위젯이 존재하는 경우 위젯을 출력합니다.
	if (InteractionWidget)
	{
		InteractionWidget->SetVisibility(ESlateVisibility::Visible);
	}
}

void AMMItemBox::OnEndOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	// 위젯이 존재하는 경우 위젯을 숨겨줍니다.
	if (InteractionWidget)
	{
		InteractionWidget->SetVisibility(ESlateVisibility::Hidden);
	}
}

결과

게임 시작과 동시에 임시로 박스를 추가하고 확인해보도록 하겠습니다.

FTransform SpawnTransform;
SpawnTransform.SetLocation(GetActorLocation() - GetCapsuleComponent()->GetScaledCapsuleHalfHeight());
AMMItemBox* ItemBox = GetWorld()->SpawnActorDeferred<AMMItemBox>(AMMItemBox::StaticClass(), SpawnTransform);
if (ItemBox)
{
	ItemBox->AddItemQuantity(50);
	ItemBox->AddMoney(1000);
	ItemBox->FinishSpawning(SpawnTransform);
}

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

0개의 댓글