일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- rpc
- unity
- 유니티
- local prediction
- map design
- os
- gameplay tag
- ability task
- UI
- animation
- stride
- 게임개발
- Aegis
- Multiplay
- Unreal Engine
- MAC
- 언리얼엔진
- CTF
- photon fusion2
- Replication
- 언리얼 엔진
- nanite
- gravity direction
- 게임 개발
- listen server
- gas
- gameplay ability system
- dirty cow
- gameplay effect
- attribute
- Today
- Total
Replicated
[UE Game Framework] #10 Game Data Management 본문

엑셀 csv로 바꾸면 언리얼에서 임포트 가능

같은 이름을 가지는 구조체 필요
액셀 데이터의 임포트
- DataAsset과 유사하게 FTableRowBase를 상속받은 구조체를 선언
- 액셀의 Name 컬럼을 제외한 컬럼과 동일하게 UPROPERTY 속성을 선언
* 엑셀에 Name이라는게 존재는 해야 하는데, 이게 키값이 되기 때문
- 엑셀 데이터를 csv로 익스포트 한 후 언리얼 엔진에 임포트
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataTable.h"
#include "ABCharacterStat.generated.h"
USTRUCT(BlueprintType)
struct FABCharacterStat : public FTableRowBase
{
GENERATED_BODY()
public:
FABCharacterStat() : MaxHp(0.0f), Attack(0.0f), AttackRange(0.0f), AttackSpeed(0.0f), MovementSpeed(0.0f) {}
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Stat)
float MaxHp;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Stat)
float Attack;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Stat)
float AttackRange;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Stat)
float AttackSpeed;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Stat)
float MovementSpeed;
FABCharacterStat operator+(const FABCharacterStat& Other) const
{
const float* const ThisPtr = reinterpret_cast<const float* const>(this);
const float* const OtherPtr = reinterpret_cast<const float* const>(&Other);
FABCharacterStat Result;
float* ResultPtr = reinterpret_cast<float*>(&Result);
int32 StatNum = sizeof(FABCharacterStat) / sizeof(float);
for (int32 i = 0; i < StatNum; i++)
{
ResultPtr[i] = ThisPtr[i] + OtherPtr[i];
}
return Result;
}
};
ABCharacterStat.h
그냥 전부 float로 해주기

위에서 선언한 구조체로 데이터 테이블 만들고


리임포트로 받아올 수 있음
데이터를 관리할 싱글톤 클래스의 설정
언리얼 엔진에서 제공하는 싱글톤 클래스
- 게임 인스턴스
- 애셋 매니저
- 게임 플레이 관련 액터 (게임 모드, 게임 스테이트)
- 프로젝트에 싱글톤으로 등록한 언리얼 오브젝트
* 언리얼 오브젝트 생성자에서 사용하지 않도록 주의
* 게임 인스턴스는 보통 어플리케이션의 데이터를 관리하는 용도
하나 만들어서 쓸 거임

만들어서

프로젝트 세팅 들어가서 등록해줌
그 후 에디터 재시작
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "ABCharacterStat.h"
#include "ABGameSingleton.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(LogABGameSingleton, Error, All);
/**
*
*/
UCLASS()
class ARENABATTLE_API UABGameSingleton : public UObject
{
GENERATED_BODY()
public:
UABGameSingleton();
static UABGameSingleton& Get();
// Character Set Data
public:
FORCEINLINE FABCharacterStat GetCharacterStat(int32 InLevel) const
{
return CharacterStatTable.IsValidIndex(InLevel) ? CharacterStatTable[InLevel] : FABCharacterStat();
}
UPROPERTY()
int32 CharacterMaxLevel;
private:
TArray<FABCharacterStat> CharacterStatTable;
};
헤더
캐릭터 스탯 테이블에 값을 옮겨받을 거임
그리고 레벨에 따른 스탯을 받는 기능이 있음
// Fill out your copyright notice in the Description page of Project Settings.
#include "GameData/ABGameSingleton.h"
DEFINE_LOG_CATEGORY(LogABGameSingleton);
UABGameSingleton::UABGameSingleton()
{
static ConstructorHelpers::FObjectFinder<UDataTable> DataTableRef(TEXT("/Script/Engine.DataTable'/Game/ArenaBattle/GameData/ABCharacterStatTable.ABCharacterStatTable'"));
if (nullptr != DataTableRef.Object)
{
const UDataTable* DataTable = DataTableRef.Object;
check(DataTable->GetRowMap().Num() > 0);
TArray<uint8*> ValueArray;
DataTable->GetRowMap().GenerateValueArray(ValueArray);
Algo::Transform(ValueArray, CharacterStatTable,
[](uint8* Value)
{
return *reinterpret_cast<FABCharacterStat*>(Value);
}
);
}
CharacterMaxLevel = CharacterStatTable.Num();
ensure(CharacterMaxLevel > 0);
}
UABGameSingleton& UABGameSingleton::Get()
{
UABGameSingleton* Singleton = CastChecked<UABGameSingleton>(GEngine->GameSingleton);
if (Singleton)
{
return *Singleton;
}
UE_LOG(LogABGameSingleton, Error, TEXT("Invalid Game Singleton"));
return *NewObject<UABGameSingleton>();
}
Get은 그냥 싱글톤 가져와서 리턴하는 함수

데이터 테이블에서 데이터 들고오는 코드
uint8* -> Value의 시작 주소임
그거를 기준으로 구조체에 집어넣으면 알아서 들어감 (reinterpret_cast)
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "GameData/ABCharacterStat.h"
#include "ABCharacterStatComponent.generated.h"
DECLARE_MULTICAST_DELEGATE(FOnHpZeroDelegate);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnHpChangedDelegate, float /*CurrentHp*/);
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;
public:
FOnHpZeroDelegate OnHpZero;
FOnHpChangedDelegate OnHpChanged;
void SetLevelStat(int32 InNewLevel);
FORCEINLINE float GetCurrentLevel() const { return CurrentLevel; }
FORCEINLINE void SetModifierStat(const FABCharacterStat& InModifierStat) { ModifierStat = InModifierStat; }
FORCEINLINE float GetCurrentHp() const { return CurrentHp; }
FORCEINLINE FABCharacterStat GetTotalStat() const { return (BaseStat + ModifierStat); }
float ApplyDamage(float InDamage);
protected:
void SetHp(float NewHp);
UPROPERTY(Transient, VisibleInstanceOnly, Category = Stat)
float CurrentHp;
UPROPERTY(Transient, VisibleInstanceOnly, Category = Stat)
float CurrentLevel;
UPROPERTY(Transient, VisibleInstanceOnly, Category = Stat, Meta = (AllowPrivateAccess = "true"))
FABCharacterStat BaseStat;
UPROPERTY(Transient, VisibleInstanceOnly, Category = Stat, Meta = (AllowPrivateAccess = "true"))
FABCharacterStat ModifierStat;
};
캐릭터 스탯 컴포넌트 헤더
베이스 스탯이랑 변경 스탯을 가짐
// Fill out your copyright notice in the Description page of Project Settings.
#include "CharacterStat/ABCharacterStatComponent.h"
#include "GameData/ABGameSingleton.h"
// Sets default values for this component's properties
UABCharacterStatComponent::UABCharacterStatComponent()
{
CurrentLevel = 1;
}
// Called when the game starts
void UABCharacterStatComponent::BeginPlay()
{
Super::BeginPlay();
SetLevelStat(CurrentLevel);
SetHp(BaseStat.MaxHp);
}
void UABCharacterStatComponent::SetLevelStat(int32 InNewLevel)
{
CurrentLevel = FMath::Clamp(InNewLevel, 1, UABGameSingleton::Get().CharacterMaxLevel);
BaseStat = UABGameSingleton::Get().GetCharacterStat(CurrentLevel);
check(BaseStat.MaxHp > 0.0f);
}
float UABCharacterStatComponent::ApplyDamage(float InDamage)
{
const float PrevHp = CurrentHp;
const float ActualDamage = FMath::Clamp(InDamage, 0.0f, InDamage);
SetHp(PrevHp - ActualDamage);
if ( CurrentHp < KINDA_SMALL_NUMBER )
{
OnHpZero.Broadcast();
}
return ActualDamage;
}
void UABCharacterStatComponent::SetHp(float NewHp)
{
CurrentHp = FMath::Clamp(NewHp, 0.0f, BaseStat.MaxHp);
OnHpChanged.Broadcast(CurrentHp);
}
싱글톤에서 스탯 가져와서 적용시키는 컴포넌트임

캐릭터에서 스탯 컴포넌트에서 뽑아서 사용
이제 스테이지 클리어 시 NPC 레벨이 올라가도록 할 것
기믹 건드려야 함


* OnGateTriggerBeginOverlap 내부

플레이어에 레벨 설정 넣어주고

액터 스폰할 때 레벨 설정해주기

근데 CurrentHp가 제대로 설정되지 않았음
이유
SpawnActor로 액터 생성 시
액터와 액터의 모든 컴포넌트의 BeginPlay 함수가 바로 호출됨
컴포넌트 코드에서 BeginPlay를 보면 SpawnActor가 수행되자마자 SelLevelStat과 SetHp 함수가 호출되는데,
이러면 Hp는 1레벨 만큼 차오름
그 이후 SetLevel을 해봤자 Hp 설정은 이후에 이루어짐
유니티도 마찬가지이긴 한데, 언리얼은 이런 문제 해결을 위해 SpawnActorDefferred 제공 (지연 생성)
마지막에 FinishSpawning 함수를 호출해여 BeginPlay 호출됨

이렇게 해주면 됨

클래스 정보는 필수로 저장해줘야 함
** PostInitializeComponents도 Finishing 이후임
보상 상자 등등 이렇게 설정해줌
이제 무기에다 스탯 넣어주기


아이템에 스탯이 추가됨
공격력 100, 범위 60으로 설정

장착 시 스탯 변화 설정
강해지고 거리 늘어난 것 확인 가능
다음은 NPC가 다른 캐릭터로 스폰되도록 하기

ABCharacterNonPlayer에 NPCMeshes가 선언되어 있으면 경로 따라가서 추가함

Config에 ArenaBattle 설정하면 DefaultArenaBattle.ini 불러 쓰겠다는 의미
속성에 Config 설정하면 해당 설정 이용하겠다는 의미
프로젝트가 로딩될 때 자동으로 값들이 채워지는데, 비동기적으로 처리하고 싶음
일단 소프트 레퍼런싱으로 NPCMeshes 설정해놓으면 일단 주소값은 엔진 켜질때 ini읽어지면서 로딩되는데
메시는 로딩 안되고 있음
=> PostInitializeComponents()에서 로딩
void AABCharacterNonPlayer::PostInitializeComponents()
{
Super::PostInitializeComponents();
ensure(NPCMeshes.Num() > 0);
int32 RandIndex = FMath::RandRange(0, NPCMeshes.Num() - 1);
NPCMeshHandle = UAssetManager::Get().GetStreamableManager().RequestAsyncLoad(NPCMeshes[RandIndex], FStreamableDelegate::CreateUObject(this, &AABCharacterNonPlayer::NPCMeshLoadCompleted));
}
인덱스 랜덤으로 하나 만들고 비동기로드, 델리케이트 연결 및 핸들을 저장
void AABCharacterNonPlayer::NPCMeshLoadCompleted()
{
if (NPCMeshHandle.IsValid())
{
USkeletalMesh* NPCMesh = Cast<USkeletalMesh>(NPCMeshHandle->GetLoadedAsset());
if (NPCMesh)
{
GetMesh()->SetSkeletalMesh(NPCMesh);
GetMesh()->SetHiddenInGame(false);
}
}
NPCMeshHandle->ReleaseHandle();
}
로드 완료되면 메시 설정해주고 핸들 놔줌
적 메시 설정이 랜덤하게 이루어지는 것을 확인 가능
'언리얼 엔진 > Unreal Game Framework' 카테고리의 다른 글
[UE Game Framework] #9 Infinity Map (0) | 2025.01.06 |
---|---|
[UE Game Framework] #8 Item System (0) | 2025.01.06 |
[UE Game Framework] #7 Character Stat & Widget (0) | 2025.01.05 |
[UE Game Framework] #6 Character Attack Hit Check (0) | 2025.01.05 |
[UE Game Framework] #5 Character Combo Action (0) | 2025.01.04 |