- 아이템 데이터 애셋 제작하기
- 상호작용 위젯 제작하기
- 아이템 박스 제작 및 테스트
- 아이템의 정보를 저장하기 위한 데이터 애셋을 추가해주도록 하겠습니다.
- 아이템의 종류는 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);
}