728x90
반응형
- 정리
- 아이템 상자와 캐릭터의 적용
- 기믹 구현을 위한 트리거 액터의 설계
- 데이터 에셋을 활용한 아이템 데이터 관리
- 의존성 분리를 위한 설계 구현
- 메모리 최적화를 위한 소프트 레퍼런싱의 구현
- 아이템 상자와 캐릭터의 적용
- 트리거 박스의 구현
- 루트에 트리거를 설정, 자식에 메시 컴포넌트를 부착
- 이펙트는 기본 값으로 비활성, 오버랩 이벤트 발생시 발동되도록 설정
- 이펙트 종료시 액터가 제거되도록 설정
#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);
};
- AABItemBox.h
- 아이템 박스를 만들기 위해 필요한 class들과 함수들을 추가해준다.
- Trigger, Mesh, Effect
- Trigger에 반응할 수 있는 OnOverlapBegin, Effect가 끝났을때를 알려주는 OnEffectFinished
#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"));
// 루트 컴포넌트 설정
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));
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;
}
}
// 바인딩 된 함수 설정 및 로직 구성
void AABItemBox::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult)
{
Effect->Activate(true);
Mesh->SetHiddenInGame(true);
SetActorEnableCollision(false);
// 이펙트가 끝날때 발생하는 델리게이트에 끝날때 Destroy를 해주는 함수를 바인딩해준다.
Effect->OnSystemFinished.AddDynamic(this, &AABItemBox::OnEffectFinished);
}
void AABItemBox::OnEffectFinished(UParticleSystemComponent* ParticleSystem)
{
Destroy();
}
- AABItemBox.cpp
- Actor 클래스를 상속받은 ABItemBox 클래스를 만들어 아이템박스를 구현해준다.
- 액터를 구성해주고, 루트 컴포넌트와 콜리전 정보를 설정해주고, 오버랩될 경우 발생하는 델리게이트에 함수를 바인딩해준다.
- 바인딩 된 함수가 실행되면 이펙트가 발생하게 되고, 아이템 박스의 메쉬는 사라지고, 콜리전 또한 반복 되지않도록 꺼준뒤, 아이템 박스 획득 이펙트가 종료되면 발생하는 델리게이트에 함수를 바인딩시켜 Destory()되는 로직을 추가시켜 준다.
- 다음은 아이템을 획득하면 아이템 정보와 함께 무기를 습득할 수 있도록 기능 구현
- DataAsset을 활용하여 아이템 에셋을 구성해준다.
- PrimaryDataAsset을 상속 받아 ABItemData 클래스를 만들어준다.
- ABItemData를 상속받은 WeaponItemData 클래스를 만들어 확장시켜준다.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "ABItemData.generated.h"
UENUM(BlueprintType)
enum class EItemType :uint8
{
Weapon = 0,
Potion,
Scroll
};
UCLASS()
class ARENABATTLE_API UABItemData : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Type)
EItemType Type;
};
- ABItemData.h
#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.h
- 아이템 에셋
- 총 3가지 종류로 간단히 정리
- 무기 타입
- 캐릭터에 무기 부착
- 무기에 의한 부가 스탯 강화
- 포션 타입
- HP 회복
- 스크롤 타입
- 기본 스탯 상승
- 아이템 에셋의 관리
- ItemData를 부모 클래스로 상속받은 세 가지 종류의 아이템 클래스를 선언
의존성 분리를 위한 설계 규칙 세우기
- 프로젝트의 주요 레이어
- 데이터 레이어 : 게임을 구성하는 기본 데이터
- 스탯 정보, 캐릭터 레벨 테이블 등등..
- 미들웨어 레이어 : 게임에 사용되는 미들웨어 모듈
- UI, 아이템, 애니메이션, AI 등등..
- 게임 레이어 : 게임 로직을 구체적으로 구현하는데 사용
- 캐릭터, 게임 모드 등등..
- 위에서 아래로는 직접 참조하되, 아래에서 위로는 인터페이스를 통해 접근하도록 설정하기
- 데이터 레이어 : 게임을 구성하는 기본 데이터
- 상단의 레이어들은 하단의 레이어들을 직접 헤더를 추가해서 참조할 수 있지만, 하단에서 상단의 레이어는 직접 참조하지 않도록 규칙을 설계한다.
- 하단에서 상단으로의 명령을 보내려고 할때는 인터페이스를 사용해서 전달한다.
#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
- 아이템을 먹었다는걸 캐릭터에게 전달해주기 위하여 ABCharacterItemInterface클래스를 만들어 준다.
// ...
#include "Interface/ABCharacterItemInterface.h"
#include "ABCharacterBase.generated.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;
};
UCLASS()
class ARENABATTLE_API AABCharacterBase : public ACharacter, public IABAnimationAttackInterface, public IABCharacterWidgetInterface, public IABCharacterItemInterface // 인터페이스 상속
{
// ...
// Item Section
protected:
//FTakeItemDelegateWrapper를 관리해줄 수 있는 배열 선언 (TakeItemActions)
UPROPERTY()
TArray<FTakeItemDelegateWrapper> TakeItemActions;
virtual void TakeItem(class UABItemData* InItemData) override;
// TakeItemActions에 바인딩될 함수들
virtual void DrinkPostion(class UABItemData* InItemData);
virtual void EquipWeapon(class UABItemData* InItemData);
virtual void ReadScroll(class UABItemData* InItemData);
}
- ABCharacterBase.h
- 아이템을 처리할 수 있도록 델리게이트를 선언해주고, 아이템의 경우 여러 아이템이 있고 다수를 관리해야하기 때문에 배열로 관리하기 위해 FTakeItemDelegateWrapper라는 구조체를 만들어 관리해준다.
- 생성자와 매개 변수를 가지는 생성자, 델리게이트를 구조체 내부에 선언해준다.
- TArray를 통해 FTakeItemDelegateWrapper 를 관리할 구조체 배열을 만들어주고, 해당 구조체에 바인딩할 함수들도 선언해준다.
// ...
#include "Item/ABWeaponItemData.h"
// 확인용 로그
DEFINE_LOG_CATEGORY(LogABCharacter);
// Sets default values
AABCharacterBase::AABCharacterBase() // 생성자
{
// ...
// Item Action
// 인자를 받는 생성자에 즉석에서 생성해서 TakeItemActions 배열에 집어넣기 (열거형 순)
TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::EquipWeapon)));
TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::DrinkPostion)));
TakeItemActions.Add(FTakeItemDelegateWrapper(FOnTakeItemDelegate::CreateUObject(this, &AABCharacterBase::ReadScroll)));
}
// 아이템데이터를 인터페이스로부터 받았을때
void AABCharacterBase::TakeItem(UABItemData* InItemData)
{
if (InItemData)
{
// Type을 가져오기 위해 #inlcude "Item/ABWeaponItemData"
// ExecuteIfBound를 통해 해당 아이템 넘겨주기
TakeItemActions[(uint8)InItemData->Type].ItemDelegate.ExecuteIfBound(InItemData);
}
}
void AABCharacterBase::DrinkPostion(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
- ABItemData를 상속받은 ABWeaponItemData를 inclue하고, 생성자에서 TakeItemActions에 인자를 받는 생성자로 즉석에서 생성하여 배열에 집어넣어준다.
- 정상적으로 동작하는지를 위해 로그도 심어준다.
UCLASS()
class ARENABATTLE_API AABItemBox : public AActor
{
// ...
// 부모 클래스형을 지정했기 때문에 Weapon, Potion, Scroll 중 하나가 여기에 대응된다.
UPROPERTY(EditAnywhere, Category = Item)
TObjectPtr<class UABItemData> Item;
// ...
};
- ABItemBox.h
- ABItemBox클래스에는 아이템 정보를 넣어주기 위해 ABItemData변수를 만들어 준다.
// ...
#include "Interface/ABCharacterItemInterface.h"
// ...
// 바인딩 된 함수 설정 및 로직 구성
// 아이템 획득했을 때
void AABItemBox::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult)
{
// 아이템에 꽝이 존재할 경우
if (nullptr == Item)
{
Destroy();
return;
}
// Item이 있다면 TakeItem에 인자로 넘겨주기
IABCharacterItemInterface* OverlappingPawn = Cast<IABCharacterItemInterface>(OtherActor);
if (OverlappingPawn)
{
OverlappingPawn->TakeItem(Item);
}
// ...
}
- ABItemBox.cpp
- 인터페이스를 include 하고, OnOverlapBegin함수를 통해 아이템을 획득했을 때 아이템 정보에 따라 로직을 구성해준다.
AABCharacterBase::AABCharacterBase()
{
// 생성자 ...
// Weapon Component
// Weapon을 만들어준뒤, 캐릭터의 Hand_rSocket이라는 조인트에 Attach해준다.
Weapon = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Weapon"));
Weapon->SetupAttachment(GetMesh(), TEXT("hand_rSocket"));
}
void AABCharacterBase::EquipWeapon(UABItemData* InItemData)
{
UE_LOG(LogABCharacter, Log, TEXT("Equip Weapon"));
// ABItemData인 InItemData를 ABWeaponItemData로 캐스팅해준뒤, 올바른 캐스팅이라면 Weapon의 스켈레탈 메쉬를 WeaponItemData의 WeaponMesh로 변경해준다.
UABWeaponItemData* WeaponItemData = Cast<UABWeaponItemData>(InItemData);
if (WeaponItemData)
{
Weapon->SetSkeletalMesh(WeaponItemData->WeaponMesh);
}
}
- ABCharacterBase.cpp
- Weapon에 대한 내용을 초기화해준다. 캐릭터의 hand_rSocket이라는 조인트에 장착될 수 있도록 위치시켜준다. (현재는 무기 1종만 구현하기 위해)
- 캐릭터가 Weapon을 획득했을 때, 무기를 장착할 수 있도록 해준다.
- 만약, 무기 위치가 맞지 않는다면 캐릭터 스켈레톤의 hand_rSocket 위치를 조금 수정해주도록 하자.
- 소프트 레퍼런싱 vs 하드 레퍼런싱
- 액터 로딩시 TObjectPtr로 선언한 언리얼 오브젝트도 따라서 메모리에 로딩이 된다. 이를 하드 레퍼런싱이라고 한다.
- 게임 진행에 필수적인 언리얼 오브젝트의 경우 이렇게 선언해도 되지만, 아이템의 경우 데이터 라이브러리에 1000종의 아이템 목록이 있을 때 이를 모두 다 로딩한다는건 큰 부담이 될 것이다.
- 그렇기 때문에 필요한 데이터만 로딩하도록 TSoftObjectPtr로 선언하고 대신 에셋 주소 문자열을 지정한다. 필요시에 에셋을 로딩하도록 구현을 변경할 수 있으나 에셋 로딩 시간이 소요 된다.
- 플레이를 하고 `키를 눌러 Obj List Class=SkeletalMesh 명령어를 입력하면 현재 로딩된 SkeletalMesh에 대한 정보가 나오게 된다.
- 아직 아이템을 먹지 않았는데도 Weapon 아이템 박스를 획득했을 때 나오는 SkeletalMesh를 하드 레퍼런싱으로 참조하고 있기 때문에 기본으로 로딩되고 있다.
#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)
TSoftObjectPtr<USkeletalMesh> WeaponMesh;
};
- ABWeaponItemData.h
- TObjectPtr을 TSoftObjectPtr로 변경해준다.
void AABCharacterBase::EquipWeapon(UABItemData* InItemData)
{
//UE_LOG(LogABCharacter, Log, TEXT("Equip Weapon"));
// ABItemData인 InItemData를 ABWeaponItemData로 캐스팅해준뒤, 올바른 캐스팅이라면 Weapon의 스켈레탈 메쉬를 WeaponItemData의 WeaponMesh로 변경해준다.
UABWeaponItemData* WeaponItemData = Cast<UABWeaponItemData>(InItemData);
if (WeaponItemData)
{
// 아직 로딩이 되지 않았다면
if (WeaponItemData->WeaponMesh.IsPending())
{
// LoadSynchronous()를 통해 로딩
WeaponItemData->WeaponMesh.LoadSynchronous();
}
//Weapon->SetSkeletalMesh(WeaponItemData->WeaponMesh);
Weapon->SetSkeletalMesh(WeaponItemData->WeaponMesh.Get());
}
}
- ABWeaponItemData.cpp
- TSoftObjectPtr로 변경했기 때문에 로딩이 안될 경우가 존재한다. 그렇기 때문에 IsPending()을 통해 아직 로딩이 되지 않았다면 LoadSynchronous()로 로딩을 해주고, Get()을통해 SkeletalMesh를 가져와 준다.
- 아이템을 획득하기전에는 SkeletalMesh가 2개로 나오지만, 아이템을 획득하고 나면 SkeletalMesh가 3개 로딩되어 있는걸 확인할 수 있다.
- 이렇게 초기의 게임이 로드가 될 때 메모리 양을 최소화시키는 것이 언리얼엔진 최적화의 중요한 요소라고 할 수 있다.
해당 포스트는 인프런의 <이득우의 언리얼 프로그래밍 Part2 -언리얼 게임 프레임웍의 이해>
강의를 수강하고 정리한 내용입니다.
이득우의 언리얼 프로그래밍 Part2 - 언리얼 게임 프레임웍의 이해 | 이득우 - 인프런
청강문화산업대학교에서 언리얼 엔진, 게임 수학, UEFN 게임제작을 가르치고 있습니다. - 이득우의 언리얼 C++ 프로그래밍, 넥슨 코리아 공식 교육 교재 선정 2023 - 스마일게이트 언리얼 프로그래
www.inflearn.com
728x90
'공부 > 이득우의 언리얼 프로그래밍' 카테고리의 다른 글
[Study] Part 2 - 게임데이터 관리 (10/15) (0) | 2024.06.18 |
---|---|
[Study] Part 2 - 무한맵의 제작 (9/15) (0) | 2024.06.17 |
[Study] Part 2 - 캐릭터 스탯과 위젯 (7/15) (0) | 2024.06.04 |
[Study] Part 2 - 캐릭터 공격 판정 (6/15) (0) | 2024.06.03 |
[Study] Part 2 - 캐릭터 콤보 액션 (5/15) (2) | 2024.06.02 |