오늘은 외부 엑셀 csv 파일을 통해 캐릭터 데이터베이스를 받아와 저장되어 있는 스탯 등을 사용하고
받은 데이터를 토대로 체력이 0 이 되면 죽도록 변경해주겠다
사용한 csv 파일은 링크를 통해 받을 수 있다
Resoure 폴더의 챕터 11이다
언리얼 엔진은 게임을 관리하는 용도의 게임 인스턴스라는 오브젝트를 제공한다
이 클래스를 활용해서 캐릭터 데이터를 관리하려고 한다
먼저 부모클래스를 GameInstance
로 하는 ABGameInstance 클래스를 만들자
csv 파일을 언리얼 엔진에서 읽기 위해서는 테이블 데이터의 이름과 유형이 동일한 구조체를 선언해주어야 한다
헤더에 형식에 맞게 구현해준다 이때 가장 왼쪽 항목은 자동으로 키값으로 사용되기 때문에 선언하지 않는다
ABGameInstance.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "ArenaBattle.h"
#include "Engine/DataTable.h"
#include "Engine/GameInstance.h"
#include "ABGameInstance.generated.h"
USTRUCT(BlueprintType)
struct FABCharacterData : public FTableRowBase
{
GENERATED_BODY()
public:
FABCharacterData() : Level(1), MaxHP(100.0f), Attack(10.0f), DropExp(10), NextExp(30) {}
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Data")
int32 Level;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Data")
float MaxHP;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Data")
float Attack;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Data")
int32 DropExp;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Data")
int32 NextExp;
};
/**
*
*/
UCLASS()
class ARENABATTLE_API UABGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
UABGameInstance();
virtual void Init() override;
FABCharacterData* GetABCharacterData(int32 Level);
private:
UPROPERTY()
class UDataTable* ABCharacterTable;
};
언리얼 엔진에 csv 파일을 임포트 하게 되면 저장된 테이블을 확인 할 수 있다
이제 캐릭터와 연결해주기 위한 변수와 함수를 cpp 파일에서 정의해주도록 한다
ABGameInstance.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABGameInstance.h"
UABGameInstance::UABGameInstance()
{
FString CharacterDataPath = TEXT("/Game/Book/GameData/ABCharacterData.ABCharacterData");
static ConstructorHelpers::FObjectFinder<UDataTable> DT_ABCHARACTER(*CharacterDataPath);
ABCharacterTable = DT_ABCHARACTER.Object;
}
void UABGameInstance::Init()
{
Super::Init();
}
FABCharacterData* UABGameInstance::GetABCharacterData(int32 Level)
{
return ABCharacterTable->FindRow<FABCharacterData>(*FString::FromInt(Level), TEXT(""));
}
액터 컴포넌트는 말 그대로 액터에 부착할 수 있는 컴포넌트이다
이것을 통해 캐릭터 스탯을 관리하는 컴포넌트를 제작해주자
액터 컴포넌트를 부모로 하는 ABCharacterStatComponent 를 생성하자
ABCharacterStatComponent.h 에 레벨 설정, 현재 레벨, 데미지 등을 관리 할 수 있게 변수를 설정해준다
이때 CurrentHP 변수는 직렬화가 필요하지 않기 때문에 Transient 키워드를 넣어 직렬화에서 제외한다
ABCharacterStatComponent.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "ArenaBattle.h"
#include "Components/ActorComponent.h"
#include "ABCharacterStatComponent.generated.h"
DECLARE_MULTICAST_DELEGATE(FOnHPIsZeroDelegate);
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class ARENABATTLE_API UABCharacterStatComponent : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UABCharacterStatComponent();
protected:
// Called when the game starts
virtual void BeginPlay() override;
virtual void InitializeComponent() override;
public:
void SetNewLevel(int32 NewLevel);
void SetDamage(float NewDamage);
float GetAttack();
FOnHPIsZeroDelegate OnHPIsZero;
private:
struct FABCharacterData* CurrentStatData = nullptr;
UPROPERTY(EditInstanceOnly, Category = Stat, Meta = (AllowPrivateAccess = true))
int32 Level;
UPROPERTY(Transient, VisibleInstanceOnly, Category = Stat, Meta = (AllowPrivateAccess = true))
float CurrentHP;
};
액터 컴포넌트는
PostInitializeComponents
에 대응하는InitializeComponent
함수가 있다
이를 설정하려면bWantsInitializeComponent
변수를 true 로 바꿔줘야한다
ABCharacterStatComponent.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABCharacterStatComponent.h"
#include "ABGameInstance.h"
// Sets default values for this component's properties
UABCharacterStatComponent::UABCharacterStatComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = false;
bWantsInitializeComponent = true;
Level = 1;
}
// Called when the game starts
void UABCharacterStatComponent::BeginPlay()
{
Super::BeginPlay();
}
void UABCharacterStatComponent::InitializeComponent()
{
Super::InitializeComponent();
SetNewLevel(Level);
}
void UABCharacterStatComponent::SetNewLevel(int32 NewLevel)
{
auto ABGameInstance = Cast<UABGameInstance>(UGameplayStatics::GetGameInstance(GetWorld()));
CurrentStatData = ABGameInstance->GetABCharacterData(NewLevel);
if (nullptr != CurrentStatData)
{
Level = NewLevel;
CurrentHP = CurrentStatData->MaxHP;
}
}
void UABCharacterStatComponent::SetDamage(float NewDamage)
{
CurrentHP = FMath::Clamp<float>(CurrentHP - NewDamage, 0.0f, CurrentStatData->MaxHP);
if (CurrentHP <= 0.0f)
OnHPIsZero.Broadcast();
}
float UABCharacterStatComponent::GetAttack()
{
return CurrentStatData->Attack;
}
먼저 ABCharacter.h 클래스에 CharacterStatComponent
포인터 변수를 추가해준다
ABCharacter.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "ArenaBattle.h"
#include "GameFramework/Character.h"
#include "ABCharacter.generated.h"
UCLASS()
class ARENABATTLE_API AABCharacter : public ACharacter
{
GENERATED_BODY()
...
public:
...
void SetWeapon(class AABWeapon* NewWeapon);
UPROPERTY(VisibleAnywhere, Category = Weapon)
class AABWeapon* CurrentWeapon;
UPROPERTY(VIsibleAnywhere, Category = Stat)
class UABCharacterStatComponent* CharacterStat;
...
}
이후 지금까지는 한번만 맞아도 캐릭터가 죽게 설정이 되어 있었는데, CharacterStatComponent
에 저장된 데미지만큼만
피해를 입도록 수정해준다
ABCharacter.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABCharacter.h"
#include "ABAnimInstance.h"
#include "DrawDebugHelpers.h"
#include "ABWeapon.h"
#include "ABCharacterStatComponent.h"
// Sets default values
AABCharacter::AABCharacter()
{
...
CharacterStat = CreateDefaultSubobject<UABCharacterStatComponent>(TEXT("CHARACTERSTAT"));
void AABCharacter::PostInitializeComponents()
{
...
CharacterStat->OnHPIsZero.AddLambda([this]() -> void
{
ABAnim->SetDeadAnim();
SetActorEnableCollision(false);
});
}
float AABCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
float FinalDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
CharacterStat->SetDamage(FinalDamage);
return FinalDamage;
}
...
void AABCharacter::AttackCheck()
{
...
if (bResult)
{
if (HitResult.GetActor()->IsValidLowLevel())
{
UGameplayStatics::ApplyDamage(HitResult.GetActor(), CharacterStat->GetAttack(),
GetController(), this, UDamageType::StaticClass());
}
}
}
...
이후 캐릭터에 캐릭터스탯 컴포넌트를 보면 제대로 데이터가 들어가있는 것을 확인 할 수 있다