외부 데이터를 통한 캐릭터 구축

유영준·2023년 1월 15일
0
post-thumbnail

오늘은 외부 엑셀 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());
		}
	}
}

...

이후 캐릭터에 캐릭터스탯 컴포넌트를 보면 제대로 데이터가 들어가있는 것을 확인 할 수 있다

profile
토비폭스가 되고픈 게임 개발자

0개의 댓글