개발/Unreal Engine / / 2022. 1. 30. 14:12

Learning Unreal 4 언리얼 공부일지 - GameInstance로 데이터 만들어 사용해보기

반응형

GameInstance라는 클래스를 이용해서 데이터를 담는 클래스로 사용해보자.

 

// MyGameInstance.h

// DataTable 헤더 필요
#include "Engine/DataTable.h"
#include "MyGameInstance.generated.h"

USTRUCT()
struct FMyCharacterData : public FTableRowBase
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	int32 Level;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	int32 Attack;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	int32 MaxHp;
};

 

 

정수형 프로퍼티를 struct안에 선언한다.

 

엔진에서 콘텐츠 브라우저 우클릭 → 기타 → 데이터 테이블로 데이터 테이블을 생성할 수 있다.

 

// MyGameInstance.cpp

#include "MyGameInstance.h"

UMyGameInstance::UMyGameInstance()
{
	// DataTable 선언
	static ConstructorHelpers::FObjectFinder<UDataTable> DATA(TEXT("DataTable경로"));

	MyStats = DATA.Object;
}

void UMyGameInstance::Init() 
{
	Super::Init();

	// 1번 row의 Attack값을 출력해본다
	UE_LOG(LogTemp, Warning, TEXT("MyGameInstance %d"), GetStatData(1)->Attack);
}

// Data를 반환하는 함수
FMyCharacterData* UMyGameInstance::GetStatData(int32 Level)
{
	return MyStats->FindRow<FMyCharacterData>(*FString::FromInt(Level), TEXT(""));
}

cpp파일에서는 DataTable경로를 통해 Data를 가져와서 Object를 UDataTable변수에 대입하고, get함수를 통해 Data를 반환할 수 있도록 한다.

 

결과를 확인하기 위해서는 GameInstance클래스 설정이 필요한데, 

프로젝트 세팅 → 맵 & 모드 → 게임 인스턴스 클래스를 생성한 GameInstance 클래스로 변경해준다.

 


ActorComponent 클래스를 이용해서 다른 클래스에 연결하는 용도의 Component를 만들 수 있다.

 

// MyStatComponent.h

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "MyStatComponent.generated.h"

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class UNREALINTRODUCTION_API UMyStatComponent : public UActorComponent
{
	/.../
protected:
	// Called when the game starts
	virtual void BeginPlay() override;
	// Component를 초기화하는 함수
	virtual void InitializeComponent() override;

public:	
	// 레벨 정하는 함수
	void SetLevel(int32 Level);
	// 공격당했을 때 호출 함수
	void OnAttacked(float DamageAmount);
	// 능력치들을 get하는 함수 선언
	int32 GetLevel() { return Level; }
	int32 GetHp() { return Hp; }
	int32 GetAttack() { return Attack; }

private:
	// 각각 능력치 프로퍼티 선언
	UPROPERTY(EditAnywhere, Category = Stat, Meta = (AllowPrivateAccess = true))
	int32 Level;
	UPROPERTY(EditAnywhere, Category = Stat, Meta = (AllowPrivateAccess = true))
	int32 Hp;
	UPROPERTY(EditAnywhere, Category = Stat, Meta = (AllowPrivateAccess = true))
	int32 Attack;
};

 

// MyStatComponent.cpp

#include "MyStatComponent.h"
#include "MyGameInstance.h"
#include "Kismet/GameplayStatics.h"

UMyStatComponent::UMyStatComponent()
{
	PrimaryComponentTick.bCanEverTick = true;
	
    // InitializeComponent를 실행하기 위해 true로 설정
	bWantsInitializeComponent = true;
	Level = 1;
}

/ ... /

// 컴포넌트 초기화 함수
void UMyStatComponent::InitializeComponent()
{
	Super::InitializeComponent();

	SetLevel(Level);
}

void UMyStatComponent::SetLevel(int32 NewLevel)
{
	auto MyGameInstance = Cast<UMyGameInstance>(UGameplayStatics::GetGameInstance(GetWorld()));
	if(MyGameInstance)
	{
    	// GameInstance의 data structor값을 가져와 statdata에 세팅한다.
		auto StatData = MyGameInstance->GetStatData(NewLevel);
		if (StatData)
		{
			Level = StatData->Level;
			Hp = StatData->MaxHp;
			Attack = StatData->Attack;
		}
	}
}

// Attack (충돌) 되었을 때 실행되는 함수
void UMyStatComponent::OnAttacked(float DamageAmount)
{
	Hp -= DamageAmount;
	if (Hp < 0)
		Hp = 0;

	UE_LOG(LogTemp, Warning, TEXT("OnAttacked %d"), Hp);
}

특별히 주목할 점이 있다면, InitializeComponent를 실행하기 위해서 bWantsInitializeComponent이라는 값을 true로 설정해주어야한다는 점이다.

 

 

// MyCharacter.h

UCLASS()
class UNREALINTRODUCTION_API AMyCharacter : public ACharacter
{
	/.../
    
public:
	// TakeDamage 함수 선언
    virtual float TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;
	// Stat 컴포넌트 선언
    UPROPERTY(VisibleAnywhere)
	class UMyStatComponent* Stat;
}

Character header에서는 TakeDamage를 선언하는데 이미 언리얼 Actor 라이브러리에 포함되어있는 함수이다. 이 가상화되어있는 TakeDamage를 사용하기 나름으로 바꾸기 위해 재선언하는 것이다.

 

// MyCharacter.cpp

/.../

AMyCharacter::AMyCharacter()
{
	/.../
    
    // stat 컴포넌트를 찾아서 할당
	Stat = CreateDefaultSubobject<UMyStatComponent>(TEXT("STAT"));
}

/.../

void AMyCharacter::AttackCheck()
{
	/.../

	if (bResult && HitResult.Actor.IsValid())
	{
		UE_LOG(LogTemp, Log, TEXT("Hit Actor : %s"), *HitResult.Actor->GetName());

		// 충돌된 상대방 오브젝트로부터 TakeDamage를 실행한다.
		FDamageEvent DamageEvent;
		HitResult.Actor->TakeDamage(Stat->GetAttack(), DamageEvent, GetController(), this);
	}
}

/.../

// 재정의한 TakeDamage
float AMyCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	Stat->OnAttacked(DamageAmount);

	return DamageAmount;
}

 

AttackCheck함수 안의 TakeDamage는 이미 선언되어있는 Actor의 함수를 사용하는 것이지만, 밑에서 TakeDamage를 재정의하였기 때문에, 재정의된 TakeDamage의 내용으로 함수가 실행된다.

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유