게임 인스턴스
게임 앱을 관리 하는 용도로 언리얼 엔진에서 제공
캐릭터 스텟을 관리 하게 설계, 엑셀에 저장돼 있는 캐릭터 스텟 데이터 테이블을 사용
게임 앱이 초기화 될 때 캐릭터 스텟 테이터를 로드, 종료 시 스텟 데이터 보존
게임 인스턴스 생성 및 프로젝트에 적용
게임 앱 초기화 과정
UGameInstance::Init ▶ AActor::PostInitializeComponents ▶ ABGameMode :: PostLogin
▶ ABGameMode :: StartPlay, AActor ::BeginPlay
순으로 동작이 끝나야 게임 앱이 정상적으로 시작
엑셀 파일 로드 방법
1. 엑셀을 CSV 파일 형식으로 변환
2. CSV 파일의 테이블 각 열의 이름과 유형이 동일한 구조체 선언
3. FTableRowBase 구조체 상속 받은 FABCharacterData 구조체를 게임 인스턴스에 선언
구조체 선언 시 CSV Name 열 데이터는 언리얼 엔진에서 자동 키 값으로 사용
Name 열은 선언에서 제외
ABGameInstance 예제
// 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"
//구조체 선언, CSV 파일 읽기 위해서 FTableRowBase 상속
USTRUCT(BlueprintType)
struct FABCharacterData : public FTableRowBase
{
GENERATED_BODY()
public:
//CSV 파일의 열의 이름, 유형 선언
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;
};
4. 컴파일 완료 시 CSV 테이터를 임포트 후 데이터 확인
5. 데이블 데이터를 선언 및 테이블 접근 함수 선언
UCLASS()
class ARENABATTLE_API UABGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
UABGameInstance();
virtual void Init() override;
//Level 값을 입력 시 해당 테이블 리턴
FABCharacterData* GetABCharacterData(int32 Level);
private:
//테이블 로드 후 적용 시킬 테이블 객체
UPROPERTY()
class UDataTable* ABCharacterTable;
};
#include "ABGameInstance.h"
UABGameInstance::UABGameInstance()
{
//테이블 위치
FString CharacterDataPath = TEXT("/Game/Book/GameData/ABCharacterData.ABCharacterData");
static ConstructorHelpers::FObjectFinder<UDataTable> DT_ABCHARACTER(*CharacterDataPath);
ABCHECK(DT_ABCHARACTER.Succeeded());
//변수에 테이블값 적용
ABCharacterTable = DT_ABCHARACTER.Object;
//정확하게 들어 왔는지 확인
ABCHECK(ABCharacterTable->RowMap.Num() > 0);
}
void UABGameInstance::Init()
{
Super::Init();
//ABLOG(Warning, TEXT("DropExp of Level 20 ABCharacter : %d"), GetABCharacterData(20)->DropExp);
}
FABCharacterData* UABGameInstance::GetABCharacterData(int32 Level)
{
//레벨에 해당하는 행값 리턴
return ABCharacterTable->FindRow<FABCharacterData>(*FString::FromInt(Level), TEXT(""));
}
스텟 관리 액터 컴포넌트 제작
캐릭터에 부착 해 스탯에 대한 관리 하는 액터 컴포넌트 제작
레벨에 따라서 스탯 변경 및 게임 인스턴스에서 테이블 참조 해 관련 데이터 변경
ABCharacter 에 ABCharacterStatComponent 추가
UCLASS()
class ARENABATTLE_API AABCharacter : public ACharacter
{
GENERATED_BODY()
public:
UPROPERTY(VisibleAnywhere, Category = Stat)
class UABCharacterStatComponent* CharacterStat;
}
#include "ABCharacterStatComponent.h"
AABCharacter::AABCharacter()
{
CharacterStat = CreateDefaultSubobject<UABCharacterStatComponent>(TEXT("CHARACTERSTAT"));
}
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"
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);
//스탯 관련 변수는 private 로 지정
private:
struct FABCharacterData* CurrentStatData = nullptr;
UPROPERTY(EditInstanceOnly, Category = Stat, Meta = (AllowPrivateAccess = true))
int Level;
UPROPERTY(Transient, VisibleInstanceOnly, Category = Stat, Meta = (AllowPirvateAccess = true))
float CurrentHP;
public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
};
// 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()
{
// TickComponent 비활성화
PrimaryComponentTick.bCanEverTick = false;
//InitializeComponent 활성화
bWantsInitializeComponent = true;
Level = 1;
}
void UABCharacterStatComponent::InitializeComponent()
{
Super::InitializeComponent();
//처음 레벨로 초기화
SetNewLevel(Level);
}
void UABCharacterStatComponent::SetNewLevel(int32 NewLevel)
{
//월드에서 게임 인스턴스 객체 참조
auto ABGameInstance = Cast<UABGameInstance>(UGameplayStatics::GetGameInstance(GetWorld()));
//게임 인스턴스 참조 확인
ABCHECK(ABGameInstance != nullptr)
//새로운 레벨에 맞는 현재 스탯 지정
CurrentStatData = ABGameInstance->GetABCharacterData(NewLevel);
//데이터 업데이트
if (CurrentStatData != nullptr)
{
Level = NewLevel;
CurrentHP = CurrentStatData->MaxHP;
}
else
{
ABLOG(Error, TEXT("Level (%d) data doesn't exist"), NewLevel);
}
}
CurrentHP에 대미지 연동
대미지 만큼 CurrentHP 차감 및 0보다 작을면 캐럭터가 죽는 기능으로 수정
1. 기존은 캐릭터의 TakeDamge 함수로 처리 하는 부분 과 SetDamage 연결
2. ABCharacterStatComponent 에서 CurrentHP 소진 시 캐릭터에 알림 델리게이트 활용
DECLARE_MULTICAST_DELEGATE(FOnHPIsZeroDelegate);
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class ARENABATTLE_API UABCharacterStatComponent : public UActorComponent
{
GENERATED_BODY()
public:
//새로운 레벨 맞는 스탯 결정
void SetNewLevel(int32 NewLevel);
void SetDamage(float NewDamage);
float GetAttack();
FOnHPIsZeroDelegate OnHPIsZero;
}
void UABCharacterStatComponent::SetDamage(float NewDamage)
{
ABCHECK(CurrentStatData != nullptr);
//최소, 최대 값 지정
CurrentHP = FMath::Clamp<float>(CurrentHP - NewDamage, 0.0f, CurrentStatData->MaxHP);
//HP 가 0이하 시 델리게이트 실행
if (CurrentHP >= 0.0f)
OnHPIsZero.Broadcast();
}
float UABCharacterStatComponent::GetAttack()
{
ABCHECK(CurrentStatData != nullptr, 0.0f);
return CurrentStatData->Attack;
}
ABCharter 기능 추가
람다 형식으로 델리게이트에 함수 등록
SetDamage 함수 호출로 현재 HP 감소
캐릭터의 현재 공격 값을 가지고 AttackCheck () 호출
void AABCharacter::PostInitializeComponents()
{
CharacterStat->OnHPIsZero.AddLambda([this]() -> void {
ABLOG(Warning, TEXT("OnHPIsZero"));
ABAnim->SetDeadAnim();
SetActorEnableCollision(false);
});
}
float AABCharacter::TakeDamage(float DamageAmount, FDamageEvent const & DamageEvent, AController * EventInstigator, AActor * DamageCauser)
{
float FinalDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
ABLOG(Warning, TEXT("Actor : %s took Damage : %f"), *GetName(), FinalDamage);
//캐릭터 스텟에 대미지 전달
CharacterStat->SetDamage(FinalDamage);
return FinalDamage;
}
void AABCharacter::AttackCheck()
{
if (bResult)
{
//가비지 컬렉션 때문에 참조 문제가 생길 수 있어서 약 포인트 지정,
//Isvalid 로 액터 유효 확인
if (HitResult.Actor.IsValid())
{
ABLOG(Warning, TEXT("Hit Actor Name: %s"), *HitResult.Actor->GetName());
FDamageEvent DamageEvent;
//캐릭터의 스텟에 따라 대미지 변경
HitResult.Actor->TakeDamage(CharacterStat->GetAttack(), DamageEvent,
GetController(), this);
}
}
}
'언리얼 C++ 게임 개발의 정석 > 10. 게임 데이터와 UI 위젯' 카테고리의 다른 글
10.2 캐릭터 위젯 UI 제작 (1) | 2024.04.28 |
---|