게임 개발/언리얼 5

언리얼5 - 아이템 시스템

싹난 감자 2024. 12. 10. 23:21


InfinityBladeWeapons 애셋 Import

 


트리거 박스의 설정

트리거 박스의 구현 

아이템 구현을 위해 루트에 트리거를 설정하고 자식에 메시 컴포넌트를 부착

이펙트는 기본 값으로 비활성화 상태로 두고 오버랩  이벤트 발생 시 발도되도록 설정

이펙트 종료 시 액터가 제거되도록 설정

Actor를 상속받은 ABItemBox 클래스 생성

 

사용한 MeshComponent의 주소는 Environment - Props의 박스,

ParticleSystem의 주소는 Effect의 Chest Open 파티클

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ABItemBox.generated.h"

UCLASS()
class ARENABATTLE_API AABItemBox : public AActor
{
	GENERATED_BODY()
	
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.h

// Fill out your copyright notice in the Description page of Project Settings.


#include "Item/ABItemBox.h"
#include "Components/BoxComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "Physics/ABCollision.h"

// Sets default values
AABItemBox::AABItemBox()
{
    // 각 포인터에 대한 객체 생성
    Trigger = CreateDefaultSubobject<UBoxComponent>(TEXT("TriggerBox"));
    Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
    Effect = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("Effect"));

    // 액터 내부에 Scene 셋업
    RootComponent = Trigger;
    Mesh->SetupAttachment(Trigger);
    Effect->SetupAttachment(Trigger);

    // 트리거 설정
    // 설정해둔 콜리전 프로필 사용
    Trigger->SetCollisionProfileName(CPROFILE_ABTRIGGER);

    // 메쉬 컴포넌트 설정
    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));
    // 충돌 사용 안함
    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)
    {
        // SetTemplate 함수로 이펙트 지정
        Effect->SetTemplate(EffectRef.Object);
        // 처음에 바로 발동하지 않도록 bAutoActivate 설정 끄기
        Effect->bAutoActivate = false;
    }
}

ABItemBox.cpp

 

 

End키로 바닥에 붙이고 Alt+드래그로 액터 복사.

상자를 레벨에 배치

상자에 오버랩 이벤트를 추가해 캐릭터가 상자에 닿았을 때 트리거가 발동되면서 이펙트가 터지도록 함.

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ABItemBox.generated.h"

UCLASS()
class ARENABATTLE_API AABItemBox : public AActor
{
	GENERATED_BODY()

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 = Effect)
	TObjectPtr<class UParticleSystemComponent> Effect;


	// 트리거 컴포넌트의 델리게이트에 연결될 함수를 액터에 선언.
	UFUNCTION()
	void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32	OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult);

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

ABItemBox.h

// Fill out your copyright notice in the Description page of Project Settings.


#include "Item/ABItemBox.h"
#include "Components/BoxComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "Physics/ABCollision.h"

// Sets default values
AABItemBox::AABItemBox()
{
	// 각 포인터에 대한 객체 생성
	Trigger = CreateDefaultSubobject<UBoxComponent>(TEXT("TriggerBox"));
	Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
	Effect = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("Effect"));
    
	// 액터 내부에 Scene 셋업
	RootComponent = Trigger;
	Mesh->SetupAttachment(Trigger);
	Effect->SetupAttachment(Trigger);

	// 트리거 설정
	// 설정해둔 콜리전 프로필 사용
	Trigger->SetCollisionProfileName(CPROFILE_ABTRIGGER);
    
	// 델리게이트 연결
	// 박스 컴포넌트에 델리게이트가 있으니 일단 사이즈 지정
	Trigger->SetBoxExtent(FVector(40.0f, 42.0f, 30.0f));
	// 다이나믹 델리게이트이기 때문에 AddDynamic함수 사용
	// AddDymanic(this(현재 객체에 대해),&AABItemBox::OnOverlapBegin(AABItemBox클래스의 OnOverlapBegin함수를 바인딩))
	// 인텔리센스가 잘 동작하지 않을 수 있음. 그래도 그냥 진행
	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));
	// 충돌 사용 안함
	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)
	{
		// SetTemplate 함수로 이펙트 지정
		Effect->SetTemplate(EffectRef.Object);
		// 처음에 바로 발동하지 않도록 bAutoActivate 설정 끄기
		Effect->bAutoActivate = false;
	}
}

// 트리거가 발동하면 호출됨
void AABItemBox::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult)
{
	// 이펙트 재생
	Effect->Activate(true);
	// 메쉬 숨기기
	Mesh->SetHiddenInGame(true);
	// 액터에 대한 콜리전 모두 끄기
	SetActorEnableCollision(false);

	// 발동된 이벤트가 종료될 때,
	// 이펙트가 종료될 때 발동되는 델리게이트가 또 있음
	// 마찬가지로 AddDynamic으로 설정
	// Dynamic이기 때문에 헤더에서 연결할 UFUNCTION 함수 선언해줘야함
	Effect->OnSystemFinished.AddDynamic(this, &AABItemBox::OnEffectFinished);
}

void AABItemBox::OnEffectFinished(UParticleSystemComponent* ParticleSystem)
{
	// 액터가 스스로 없어지도록 설정
	Destroy();
}

ABItemBox.cpp

 

트리거 컴포넌트는 OnComponentBeginOverlap이라는 델리게이트를 제공.
f12로 함수의 위치를 확인하고 그 안의 함수 시그니처 선언값을 f12로 따라가면

OnOverlapBegin 함수에서 사용된 인자들을 볼 수 있음.
이렇게 만들어진 델리게이트의 경우 블루프린트에서도 사용할 수 있도록 다이나믹 델리게이트로 선언되어있음.

그렇기 때문에 연결하는 함수를 UFUNCTION으로 지정해줘야함.

 

캐릭터가 아이템박스와 닿으면 이펙트가 발생하고 제거됨.

배치된 상자에 아이템 정보를 넣어 상자가 가진 아이템 정보를 통해 캐릭터가 무기를 습득하는 기능을 추가.

 

PrimaryDataAsset을 상속받는 ABItemData 클래스 생성

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

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

// 블루프린트에서 접근할 수 있도록 BlueprintType 지정자 추가
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;
};

ABItemData.h

 

만들어진 ABItemData를 상속받는 ABWeaponItemData 클래스 생성(기능 확장 구현)

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Item/ABItemData.h"
#include "ABWeaponItemData.generated.h"

/**
 * 
 */
UCLASS()
class ARENABATTLE_API UABWeaponItemData : public UABItemData
{
	GENERATED_BODY()
	
public:
	// 무기에 대한 스켈레탈 메쉬
	UPROPERTY(EditAnywhere, Category="Weapon")
	TObjectPtr<USkeletalMesh> WeaponMesh;
};

ABWeaponItemData.cpp

 

아이템들을 에셋으로 관리하기 위해 Item 폴더 생성

기본 아이템 데이터로  임시 생성

 

 

Weapon은 만들어둔ABWeaponItemData를 사용해 생성

 

각 에셋들에 대한 Type 값을 지정해줘야하는데 일단 수동으로 지정하고

나중에 클래스 선언할 때 클래스 생성자에서 기본값을 지정하도록 수정

Weapon의 스켈레탈 메쉬와 타입도 지정

 


아이템 애셋의 설계

프로젝트에서 사용할 아이템 애셋

총 3가지 종류의 아이템 타입을 지정

무기 타입 : 캐릭터에 무기를 장착(무기에 의한 부가 스탯 강화)

포션 타입 : 캐릭터의 HP를 회복

스크롤 타입 : 캐릭터의 기본 스탯을 상승

실제 스탯의 구현은 차후 강좌에서 진행

 

아이템 애셋의 관리

ItemData를 부모 클래스로 상속받은 세 가지 종류의 아이템 클래스를 선언

 

의존성 분리를 위한 설계 규칙

프로젝트의 주요 레이어

캐릭터는 각각 습득한 아이템 종류별로 다른 처리를 해야함.

기획적으로 새로운 아이템이 추가될 때마다 함수를 만들고 로직을 추가하는 것은 굉장히 번거로움.

→ 의존성을 최대한 분리해 설계

데이터 레이어 : 게임을 구성하는 기본 데이터만 보관 (스탯 정보, 캐릭터 레벨 테이블 등)

미들웨어 레이어 : 게임에 사용되는 독립적인 모듈. 공용으로 사용되는 미들웨어 모듈. (UI, 아이템, 애니메이션, AI 등)

게임 레이어 : 게임 로직을 구체적으로 구현하는데 사용 (캐릭터, 게임 모드 등)

위에서 아래로는 직접 참조(직접 헤더 추가), 아래에서 위로는 인터페이스를 통해 간접적으로 접근

 

Character 폴더와 Game 폴더가 게임 레이어,

외에 Item, Animation, CharacterStat, UI 등의 폴더는 다양한 종류의 캐릭터에 서비스를 공급하는 미들웨어 개념.

Item 등의 미들웨어 레이어의 cpp 파일에서는 캐릭터의 헤더 정보를 직접 include 하지 않도록 규칙을 만들어줄 필요가 있음.

 

설계 원칙을 정해두어야 기능을 확장하는데 유연하게 대처할 수 있음.

 


 

세 가지 아이템을 얻었을 때 캐릭터에 설정된 세 가지 로직에 각각 바인딩해서 호출되도록 최대한 의존성을 없앤 구조를 구현.

 

ItemBox에서 아이템을 습득했을 때 캐릭터로부터 아이템을 습득했다고 전달할

ABCharacterItemInterface 인터페이스 생성

 

아이템은 미들웨어, 캐릭터는 게임로직이기 때문에 인터페이스를 사용해 간접 호출

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "ABCharacterItemInterface.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UABCharacterItemInterface : public UInterface
{
	GENERATED_BODY()
};

/**
 * 
 */
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;
};

ABCharacterItemInterface.h

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ABItemBox.generated.h"

UCLASS()
class ARENABATTLE_API AABItemBox : public AActor
{
	GENERATED_BODY()

public:
	// Sets default values for this actor's properties
	AABItemBox();

protected:

	...

	// 아이템 정보
	UPROPERTY(EditAnywhere, Category="Item")
	// 부모 클래스형을 지정해 어떤 아이템이든 대응
	TObjectPtr<class UABItemData> Item;

	...
    
};

ABItemBox.h

// Fill out your copyright notice in the Description page of Project Settings.


#include "Item/ABItemBox.h"
#include "Components/BoxComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "Physics/ABCollision.h"
#include "Interface/ABCharacterItemInterface.h"

// Sets default values
AABItemBox::AABItemBox()
{
	...
}

// 트리거가 발동하면 호출됨
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);

	// 발동된 이벤트가 종료될 때,
	// 이펙트가 종료될 때 발동되는 델리게이트가 또 있음
	// 마찬가지로 AddDynamic으로 설정
	// Dynamic이기 때문에 헤더에서 연결할 UFUNCTION 함수 선언해줘야함
	Effect->OnSystemFinished.AddDynamic(this, &AABItemBox::OnEffectFinished);
}

void AABItemBox::OnEffectFinished(UParticleSystemComponent* ParticleSystem)
{
	// 액터가 스스로 없어지도록 설정
	Destroy();
}

ABItemBox.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Interface/ABAnimationAttackInterface.h"
#include "Interface/ABCharacterWidgetInterface.h"
#include "Interface/ABCharacterItemInterface.h"
#include "ABCharacterBase.generated.h"

// 아이템 델리게이트가 잘 연결되었는지 로그 찍기
// LogABCharacter라는 카테고리로 지정, cpp에서 로그 카테고리 구현
DECLARE_LOG_CATEGORY_EXTERN(LogABCharacter, Log, All);

// 컨트롤 데이터 ENUM
UENUM()
enum class ECharacterControlType : uint8
{
	Shoulder,
	Quater
};

// 아이템을 처리할 델리게이트
DECLARE_DELEGATE_OneParam(FOnTakeItemDelegate, class UABItemData* /*InItemData*/);
// FOnTakeItemDelegate를 배열로 관리하려고 하는 델리게이트.
// 델리게이트 자체는 함수의 인자로 쓸 수 없음.
// 쉽게 배열로 관리하려면 구조체로 감싸면 됨
USTRUCT(BlueprintType)
struct FTakeItemDelegateWrapper
{
	GENERATED_BODY()
	FTakeItemDelegateWrapper() {}
	FTakeItemDelegateWrapper(const FOnTakeItemDelegate& InItemDelegate) : ItemDelegate(InItemDelegate) {}
	FOnTakeItemDelegate ItemDelegate;
};

UCLASS()
class ARENABATTLE_API AABCharacterBase : public ACharacter, public IABAnimationAttackInterface, public IABCharacterWidgetInterface, public IABCharacterItemInterface
{
	GENERATED_BODY()

public:
	AABCharacterBase();

	...

//Item Section
protected:
	UPROPERTY()
	TArray<FTakeItemDelegateWrapper> TakeItemActions;

	virtual void TakeItem(class UABItemData* InItemData) override;
	// TakeItemActions에 바인딩할 함수
	virtual void DrinkPotion(class UABItemData* InItemData);
	virtual void EquipWeapon(class UABItemData* InItemData);
	virtual void ReadScroll(class UABItemData* InItemData);

};

ABCharacterBase.h

 

캐릭터가 아이템을 받았을  아이템 데이터의 열거형 값에 따라 서로 다른 액션을 수행해주면 좋음

// Fill out your copyright notice in the Description page of Project Settings.


#include "Character/ABCharacterBase.h"
#include "Components/CapsuleComponent.h"
// Component's'임
#include "ABCharacterControlData.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Animation/AnimMontage.h"
#include "ABComboActionData.h"
#include "Physics/ABCollision.h"
#include "Engine/DamageEvents.h"
#include "CharacterStat/ABCharacterStatComponent.h"
#include "UI/ABWidgetComponent.h"
// WidgetComponent가 아닌 ABWidgetComponent로 확장
#include "UI/ABHpBarWidget.h"
// UI폴더 같은 경우 직접 만든 기본 컴포넌트, 엔진이라고 생각하면 됨. 추가해도 됨
#include "Item/ABWeaponItemData.h"

// 로그 매크로의 카테고리 구현
DEFINE_LOG_CATEGORY(LogABCharacter);

// Sets default values
AABCharacterBase::AABCharacterBase()
{
	
    ...

    // Item Actions
    // FTakeItemDelegateWrapper(구조체)의 인자로
    // 클래스 멤버함수와 바인딩된 델리게이트를
    // CreateUObject로 바로 생성해서 집어넣기
    // CreateUObject(현재 객체 인스턴스, 연결할 함수)
    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(UABItemData* InItemData)
{
    // 받은 아이템 데이터의 열거형 값에 따라 서로 다른 액션을 수행
    // 스위치문을 사용해도 되지만 델리게이트를 사용

    if (InItemData)
    {
        // InItemData의 타입 정보를 정수형으로 변환, 
        // TakeItemActions 배열에 들어가게 되면
        // 구조체로 접근할 수 있게 됨
        // 델리게이트에 ExecuteIfBound 함수를 사용해
        // 아이템 정보를 넘겨줌
        TakeItemActions[(uint8)InItemData->Type].ItemDelegate.ExecuteIfBound(InItemData);
    }
}

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

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

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

ABCharacterBase.cpp

 

각 상자에 아이템 설정

 

아이템을 얻으면 로그 표시됨

 

캐릭터 손에 무기 추가하기

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Interface/ABAnimationAttackInterface.h"
#include "Interface/ABCharacterWidgetInterface.h"
#include "Interface/ABCharacterItemInterface.h"
#include "ABCharacterBase.generated.h"

// 아이템 델리게이트가 잘 연결되었는지 로그 찍기
// LogABCharacter라는 카테고리로 지정, cpp에서 로그 카테고리 구현
DECLARE_LOG_CATEGORY_EXTERN(LogABCharacter, Log, All);


// 컨트롤 데이터 ENUM
UENUM()
enum class ECharacterControlType : uint8
{
	Shoulder,
	Quater
};

// 아이템을 처리할 델리게이트
DECLARE_DELEGATE_OneParam(FOnTakeItemDelegate, class UABItemData* /*InItemData*/);
// FOnTakeItemDelegate를 배열로 관리하려고 하는 델리게이트.
// 델리게이트 자체는 함수의 인자로 쓸 수 없음.
// 쉽게 배열로 관리하려면 구조체로 감싸면 됨
USTRUCT(BlueprintType)
struct FTakeItemDelegateWrapper
{
	GENERATED_BODY()
	FTakeItemDelegateWrapper() {}
	FTakeItemDelegateWrapper(const FOnTakeItemDelegate& InItemDelegate) : ItemDelegate(InItemDelegate) {}
	FOnTakeItemDelegate ItemDelegate;
};

UCLASS()
class ARENABATTLE_API AABCharacterBase : public ACharacter, public IABAnimationAttackInterface, public IABCharacterWidgetInterface, public IABCharacterItemInterface
{
	GENERATED_BODY()

	...
    
//Item Section
protected:
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Equipment", meta = (AllowPrivateAccess = "true"))
	TObjectPtr<class USkeletalMeshComponent> Weapon;

	UPROPERTY()
	TArray<FTakeItemDelegateWrapper> TakeItemActions;

	virtual void TakeItem(class UABItemData* InItemData) override;
	// TakeItemActions에 바인딩할 함수
	virtual void DrinkPotion(class UABItemData* InItemData);
	virtual void EquipWeapon(class UABItemData* InItemData);
	virtual void ReadScroll(class UABItemData* InItemData);

};

ABCharacterBase.h

// Fill out your copyright notice in the Description page of Project Settings.


#include "Character/ABCharacterBase.h"
#include "Components/CapsuleComponent.h"
// Component's'임
#include "ABCharacterControlData.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Animation/AnimMontage.h"
#include "ABComboActionData.h"
#include "Physics/ABCollision.h"
#include "Engine/DamageEvents.h"
#include "CharacterStat/ABCharacterStatComponent.h"
#include "UI/ABWidgetComponent.h"
// WidgetComponent가 아닌 ABWidgetComponent로 확장
#include "UI/ABHpBarWidget.h"
// UI폴더 같은 경우 직접 만든 기본 컴포넌트, 엔진이라고 생각하면 됨. 추가해도 됨
#include "Item/ABWeaponItemData.h"

// 로그 매크로의 카테고리 구현
DEFINE_LOG_CATEGORY(LogABCharacter);

// Sets default values
AABCharacterBase::AABCharacterBase()
{
	
    ...

    // Item Actions
    // FTakeItemDelegateWrapper(구조체)의 인자로
    // 클래스 멤버함수와 바인딩된 델리게이트를
    // CreateUObject로 바로 생성해서 집어넣기
    // CreateUObject(현재 객체 인스턴스, 연결할 함수)
    TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::EquipWeapon)));
    TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::DrinkPotion)));
    TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::ReadScroll)));

    // Weapon Component
    Weapon = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Weapon"));
    // 해당 컴포넌트를 부착할 때 직접 트랜스폼을 정해주는 것이 아니라
    // 캐릭터의 특정 본에 무기가 항상 부착되어 돌아다닐 수 있도록
    // 소켓 이름을 지정해줌.
    // 이 소켓은 애셋의 스켈레탈 메쉬에 이미 지정되어 있는 이름
    Weapon->SetupAttachment(GetMesh(), TEXT("hand_rSocket"));
}

...

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

void AABCharacterBase::EquipWeapon(UABItemData* InItemData)
{
    UABWeaponItemData* WeaponItemData = Cast<UABWeaponItemData>(InItemData);
    if (WeaponItemData)
    {
        Weapon->SetSkeletalMesh(WeaponItemData->WeaponMesh);
    }
}

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

ABCharacterBase.cpp

 

지정해두었던 hand_rSocket의 위치 값 조정

무기를 얻으면 손에 장착됨

소프트 레퍼런싱

소프트 레퍼런싱 VS 하드 레퍼런싱

하드 레퍼런싱 :

액터 로딩 시 TObjectPtr로 선언한 언리얼 오브젝트도 따라서 메모리에 로딩됨.

 

게임 진행에 필수적인 언리얼 오브젝트는 이렇게 선언해도 되지만 아이템의 경우는?

데이터 라이브러리에 1000종의 아이템 목록이 있을 때 이를 모두 다 로딩할 경우 메모리에 큰 부담.

 

소프트 레퍼런싱 : 

필요한 데이터만 로딩하기 위해 멤버변수를 선언하되 로딩하지 않게

TSoftObjectPtr로 선언하면 애셋 대신 애셋 주소 문자열이 지정됨.

필요 시에 애셋을 로딩하도록 구현을 변경할 수 있으나 애셋 로딩 시간이 소요됨.

 

게임 실행 후 `(틸드)키를 눌러 콘솔을 열고 Obj List Class=SkeletaMesh 를 입력하면 

현재 게임에서 로딩되어 있는 스켈레탈 메시의 목록 확인할 수 있음

아이템 박스의 오브젝트를 하드 레퍼런싱으로 참조하고 있어 맵이 로딩될 때 드래곤 소드(무기) 애셋이 기본으로 로딩됨

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Item/ABItemData.h"
#include "ABWeaponItemData.generated.h"

/**
 * 
 */
UCLASS()
class ARENABATTLE_API UABWeaponItemData : public UABItemData
{
	GENERATED_BODY()
	
public:
	// 무기에 대한 스켈레탈 메쉬
	UPROPERTY(EditAnywhere, Category="Weapon")
	// 소프트 레퍼런싱을 위해 TObjectPtr이 아닌
	// TSoftObjectPtr을 사용
	TSoftObjectPtr<USkeletalMesh> WeaponMesh;
};

ABWeaponItemData.h

 

TObjectPtr을 TSoftObjectPtr로 바꾼뒤 빌드하면 에러 발생,

소프트 레퍼런싱으로 아직 로딩이 됐는지 알 수 없는 상태이기 때문에 바로 사용할 수는 없고

해당 오브젝트를 로딩시켜주는 부분을 추가해야 함.

// Fill out your copyright notice in the Description page of Project Settings.


#include "Character/ABCharacterBase.h"
#include "Components/CapsuleComponent.h"
// Component's'임
#include "ABCharacterControlData.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Animation/AnimMontage.h"
#include "ABComboActionData.h"
#include "Physics/ABCollision.h"
#include "Engine/DamageEvents.h"
#include "CharacterStat/ABCharacterStatComponent.h"
#include "UI/ABWidgetComponent.h"
// WidgetComponent가 아닌 ABWidgetComponent로 확장
#include "UI/ABHpBarWidget.h"
// UI폴더 같은 경우 직접 만든 기본 컴포넌트, 엔진이라고 생각하면 됨. 추가해도 됨
#include "Item/ABWeaponItemData.h"

// 로그 매크로의 카테고리 구현
DEFINE_LOG_CATEGORY(LogABCharacter);

... 

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

void AABCharacterBase::EquipWeapon(UABItemData* InItemData)
{
    UABWeaponItemData* WeaponItemData = Cast<UABWeaponItemData>(InItemData);
    if (WeaponItemData)
    {
        // TSoftObjectPtr은 바로 설정할 수 없음
        // Weapon->SetSkeletalMesh(WeaponItemData->WeaponMesh);

        // 오브젝트가 로딩되어있는지 확인
        if (WeaponItemData->WeaponMesh.IsPending())
        {
            // 로딩되어 있지 않다면 동기적으로 로딩시키기
            WeaponItemData->WeaponMesh.LoadSynchronous();
        }
        // Get함수로 가져와서 오브젝트 설정하기
        Weapon->SetSkeletalMesh(WeaponItemData->WeaponMesh.Get());
    }
}

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

ABCharacterBase.cpp

 

애셋 포인터를 TSoftObjectPtr로 변경한 직후에는 Obj List Class=SkeletalMesh 명령어를 입력해도 레퍼런싱이 그대로 남아있을 수 있음. 무기 아이템을 재설정해 저장하고 에디터를 재시작하면 적용된 것을 확인할 수 있음.

 

TSoftObjectPtr로 변경하고 실행하면 무기가 미리 로딩되어 있지 않음

아이템을 얻고 다시 확인해보면 무기가 로딩되어있음.

소프트 레퍼런싱을 활용해 초기의 게임이 로드될 때 메모리 양을 최소화시키는 것이 최적화에 중요.


https://www.inflearn.com/course/%EC%9D%B4%EB%93%9D%EC%9A%B0-%EC%96%B8%EB%A6%AC%EC%96%BC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-part-2/dashboard

 

이득우의 언리얼 프로그래밍 Part2 - 언리얼 게임 프레임웍의 이해 강의 | 이득우 - 인프런

이득우 | 대기업 현업자들이 수강하는 언리얼 C++ 프로그래밍 전문 과정입니다. 언리얼 C++ 프로그래밍을 사용해 핵&슬래시 로그라이크 게임 예제를 처음부터 끝까지 체계적으로 제작하는 방법을

www.inflearn.com