아이템 박스 만들기

sssukh·2024년 3월 22일
post-thumbnail

이번엔 아이템이 들어있는 상자를 만들어보고 캐릭터가 해당 상자에 부딫히면 상자가 부서지면서 아이템이 나오도록 해보겠다.

아이템 상자 액터를 생성해준다.

<ABItemBox.h>
public:	
	// Sets default values for this actor's properties
	AABItemBox();

protected:
	// 루트 컴포넌트
	UPROPERTY(VisibleAnywhere,Category=Box)
	TObjectPtr<class UBoxComponent> Trigger;

	UPROPERTY(VisibleAnywhere, Category = Box)
	TObjectPtr<class UStaticMeshComponent> Mesh;

	UPROPERTY(VisibleAnywhere, Category = Box)
	TObjectPtr<class UParticleSystemComponent> Effect;
	
<ABItemBox.cpp>
AABItemBox::AABItemBox()
{
    Trigger = CreateDefaultSubobject<UBoxComponent>(TEXT("TriggerBox"));
    Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
    Effect = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("Effect"));

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

    Trigger->SetCollisionProfileName(CPROFILE_ABTRIGGER);
    Trigger->SetBoxExtent(FVector(40.0f, 42.0f, 30.0f));
    Trigger->OnComponentBeginOverlap.AddDynamic(this, &AABItemBox::OnOverlapBegin);

    static ConstructorHelpers::FObjectFinder<UStaticMesh> BoxMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/ArenaBattle/Environment/Props/SM_Env_Breakables_Box1.SM_Env_Breakables_Box1'"));
    if (BoxMeshRef.Object)
    {
        Mesh->SetStaticMesh(BoxMeshRef.Object);
    }
    Mesh->SetRelativeLocation(FVector(0.0f, -3.5f, -30.0f));
    // 충돌 x
    Mesh->SetCollisionProfileName(TEXT("NoCollision"));
	
    static ConstructorHelpers::FObjectFinder<UParticleSystem> EffectRef(TEXT("/Script/Engine.ParticleSystem'/Game/ArenaBattle/Effect/P_TreasureChest_Open_Mesh.P_TreasureChest_Open_Mesh'"));
    if (EffectRef.Object)
    {
        Effect->SetTemplate(EffectRef.Object);
        // 바로 발동되지 않음
        Effect->bAutoActivate = false;
    }

ItemBox에 트리거인 UBoxComponent, 메쉬 UStaticMeshComponent, 이펙트 UParticleSystemComponent 를 각각 추가해주고 생성자에서 설정을 해준다.

Subobject로 등록을 해주고 Mesh와 충돌하지 않도록 Collision을 해제, 이펙트에서 bAutoActivate를 false로 두어서 바로 발동되지 않도록 한다.

BoxComponent인 Trigger에는 오버랩이벤트를 감지하기위한 델리게이트가 선언되어있다.

이펙트가 종료되면 발동되는 델리게이트도 있는데 이들을 이용해서 박스와 부딫혔을 때 이펙트가 출력되도록, 이펙트가 종료되면 박스가 사라지도록 구현하였다.

<ABItemBox.h>
UFUNCTION()
void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult);

UFUNCTION()
void OnEffectFinished(class UParticleSystemComponent* ParticleSystem);

<ABItemBox.cpp>

AABItemBox::AABItemBox()
{
	//...
 	Trigger->SetCollisionProfileName(CPROFILE_ABTRIGGER);
    Trigger->SetBoxExtent(FVector(40.0f, 42.0f, 30.0f));
    Trigger->OnComponentBeginOverlap.AddDynamic(this, &AABItemBox::OnOverlapBegin);
    //...
}

void AABItemBox::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult)
{
    Effect->Activate(true);
    Mesh->SetHiddenInGame(true);
    SetActorEnableCollision(false);
    // 이펙트가 종료되면 발동되는 델리게이트
    Effect->OnSystemFinished.AddDynamic(this, &AABItemBox::OnEffectFinished);
}

void AABItemBox::OnEffectFinished(UParticleSystemComponent* ParticleSystem)
{
    Destroy();
}

각각 델리게이트에 연결할 함수들을 선언해주고 각각 생성자와 OnOverlapBegin() 함수에서 델리게이트와 연결시켜준다. (여기서는 AddDynamic()을 이용했다.)

상자가 잘 사라지고 이펙트가 나오는 것을 볼 수 있다.

<ABItemData.h>
UENUM(BlueprintType)
enum class EItemType : uint8
{
	Weapon=0,
	Potion,
	Scroll
};

/**
 * 
 */
UCLASS()
class ARENABATTLE_API UABItemData : public UPrimaryDataAsset
{
	GENERATED_BODY()
public:
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category=Type)
	EItemType Type;
};

ItemData를 담을 ItemDataAsset를 만들고 아이템의 종류를 관리하도록 Enum을 선언해준다.

<WeaponItemData.h>
UCLASS()
class ARENABATTLE_API UABWeaponItemData : public UABItemData
{
	GENERATED_BODY()
public:
	UPROPERTY(EditAnywhere, Category =Weapon)
	TSoftObjectPtr<USkeletalMesh> WeaponMesh;
};

그리고 우리가 만든 ItemData를 상속받아 무기의 정보를 담을 WeaponItemData를 생성한다.

다음과 같이 세개의 아이템 종류 각각 아이템 정보를 생성해준다.

Weapon은 아이템 종류와 Mesh를 설정해주고 Weapon을 제외한 다른 아이템들은 따로 클래스를 만들지 않았기 때문에 기본 ItemData로 종류만 설정해준다.

<ABCharacterItemInterface.h>
class ARENABATTLE_API IABCharacterItemInterface
{
	GENERATED_BODY()

	// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
	virtual void TakeItem(class UABItemData* InItemData) = 0;
};

ItemBox에서 캐릭터에 접근할 수 있도록 Interface를 생성한다.

캐릭터에서 인터페이스를 상속받아서 TakeItem()을 구현하도록 한다.

<ABItemBox.h>
UPROPERTY(EditAnywhere, Category = Item)
	TObjectPtr<class UABItemData> Item;

<ABItemBox.cpp>
void AABItemBox::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult)
{
    if (nullptr == Item)
    {
        Destroy();
        return;
    }
    IABCharacterItemInterface* OverlappingPawn = Cast<IABCharacterItemInterface>(OtherActor);
    if (OverlappingPawn)
    {
        OverlappingPawn->TakeItem(Item);
    }

    Effect->Activate(true);
    Mesh->SetHiddenInGame(true);
    SetActorEnableCollision(false);
    // 이펙트가 종료되면 발동되는 델리게이트
    Effect->OnSystemFinished.AddDynamic(this, &AABItemBox::OnEffectFinished);
}

아이템박스에 아이템 변수를 추가시키고 박스에 부딫힌 캐릭터의 정보를 받아서 우리가 구현한 인터페이스를 상속받았는지 확인하고 TakeItem() 함수를 호출시킨다.

<ABCharacterBase.h>

DECLARE_LOG_CATEGORY_EXTERN(LogABCharacter, Log, All);


DECLARE_DELEGATE_OneParam(FOnTakeItemDelegate, class UABItemData* /*InItemData*/);
USTRUCT(BlueprintType)
struct FTakeItemDelegateWrapper
{
	GENERATED_BODY()
	FTakeItemDelegateWrapper()	{};
	FTakeItemDelegateWrapper(const FOnTakeItemDelegate& InItemDelegate) : ItemDelegate(InItemDelegate) {};

	FOnTakeItemDelegate ItemDelegate;
};

/...

UPROPERTY()
TArray<FTakeItemDelegateWrapper> TakeItemActions;

virtual void DrinkPotion(class UABItemData* InItemData);
virtual void ReadScroll(class UABItemData* InItemData);
virtual void EquipWeapon(class UABItemData* InItemData);

<ABCharacterBase.cpp>

DEFINE_LOG_CATEGORY(LogABCharacter);


AABCharacterBase::AABCharacterBase()
{
	// ...
     TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::EquipWeapon)));
    TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::DrinkPotion)));
    TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::ReadScroll)));
    
}

void AABCharacterBase::TakeItem(class UABItemData* InItemData)
{
    if (InItemData)
    {
        TakeItemActions[(uint8)InItemData->Type].ItemDelegate.ExecuteIfBound(InItemData);
    }
}

void AABCharacterBase::TakeItem(class UABItemData* InItemData)
{
    UE_LOG(LogABCharacter, Log, TEXT("Equip Weapon"));
}

void AABCharacterBase::DrinkPotion(UABItemData* InItemData)
{
    UE_LOG(LogABCharacter, Log, TEXT("Drink Potion"));
}

void AABCharacterBase::ReadScroll(UABItemData* InItemData)
{
    UE_LOG(LogABCharacter, Log, TEXT("Read Scroll"));

}

TakeItem()을 구현할 때 받아오는 아이템에 따라 다른 함수를 호출할 수 있도록 해야하는데 여기서는 델리게이트를 이용해서 구현한다.

ReadScroll(), EquipWeapon(), DrinkPotion() 으로 각각 아이템 종류에 따라 호출되도록 함수를 선언해주고 CreateUObject로 즉석에서 델리게이트를 생성하고 연결시켜준다.

잘 동작하는지 확인하기 위해 로그를 넣어서 확인해보자.

<ABCharacterBase.h>
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Equipment, Meta = (AllowPrivateAccess = "true"))
	TObjectPtr<class USkeletalMeshComponent> Weapon;
    
<ABCharacterBase.cpp>
AABCharacterBase::AABCharacterBase()
{
	//...
	// Weapon Component
    Weapon = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Weapon"));
    Weapon->SetupAttachment(GetMesh(), TEXT("hand_rSocket"));
}
void AABCharacterBase::EquipWeapon(UABItemData* InItemData)
{
    Weapon->SetSkeletalMesh(WeaponItemData->WeaponMesh);
}

무기를 장착해보기 위해 Subobject로 weapon을 등록해주고 무기 상자를 열면 무기가 메시에 세팅되어 소켓에 장착되도록 한다.

skeleton에 가서 소켓 정보를 조금 다듬으면 다음과같이 무기가 장착되는것을 볼 수 있다.

profile
한번 해보자

0개의 댓글