[Unreal_C++_DarkSoul]#15_Data Table 로드 함수
Data Table 정보를 가져오는 Load 함수를 구현합니다.
Overview
- 개요
- 코드
- 결론
#1. 개요
1. 문제
- CHelpers(Utility 클래스) 클래스에서 Data Table 종류별 Load 함수를 static 멤버 함수로 정의합니다.
- 각 액터 컴포넌트들은 GameObject 객체로부터 가져온 Data Asset을 통해 Data Table을 전달받고, CHelpers가 제공하는 Load 정적 함수를 통해 전달받은 Data Table로부터 필요한 정보를 Load 할 수 있습니다.
- 이때, Game Object가 Load한 정보들을 Game Object의 액터 컴포넌트들로 가져오기 위해선 Tick() 함수를 정의해야 합니다. Tick() 함수는 런 타임 중 매 프레임마다 호출되는 함수로 게임 성능에 영향을 끼칩니다. 물론, 적절한 Tick() 함수(Update 함수)는 성능에 크게 영향을 주지 않지만, 소극적 사용이 권장됩니다.
2. 해결 방안
- Game Object 객체의 BeginPlay() 함수 내부에서 모든 Data Table을 Load 하는 것보다 각 컴포넌트들이 각자가 필요한 Data Table을 Game Object 객체로부터 가져와 별도의 Load 함수를 정의하는 것이 적절해 보입니다. 더불어, 각 컴포넌트들이 필요로 하는 정보들이 중복되는 경우가 있기 때문에, CHelper 클래스에 static 함수로 정의해 코드 중복을 방지했습니다.
#2. 코드
1. C_HealthComponent::LoadSpecInfoFromDataTable()
void UC_HealthComponent::LoadSpecInfoFromDataTable()
{
CheckNull(DataTable_SpecInfo);
TArray<FName>RowNames = DataTable_SpecInfo->GetRowNames();
for (auto RowName : RowNames)
{
FString RowNameStr = RowName.ToString();
FString OwnerName = OwnerObject->GetName();
if (OwnerName.Contains(RowNameStr))
{
const FString ContextString(TEXT("Owner Spec Info"));
FSpecInfo* RowStruct = DataTable_SpecInfo->FindRow<FSpecInfo>(RowName, ContextString, true);
SpecInfo_Struct = *RowStruct;
}
}
}
Details
- C_HealthComponent는 GameObject가 갖는 액터 컴포넌트로, GameObject 객체의 기본 Spec 정보를 통해 피격 시 전달 받을 대미지 계산, 현재 HP의 계산 등의 동작들을 수행합니다. 따라서, Game Object들의 SpecInfo를 담고 있는 Data Table을 Load 하는 함수를 별도로 정의해야 합니다.
2. C_PowerComponent::LoadPowerInfoFromDataTable()
void UC_PowerComponent::LoadPowerInfoFromDataTable()
{
CheckNull(DataTable_PowerInfo);
TArray<FName>RowNames = DataTable_PowerInfo->GetRowNames();
for (auto RowName : RowNames)
{
FString RowNameStr = RowName.ToString();
FString OwnerName = OwnerWeapon->GetName().Mid(3, OwnerWeapon->GetName().Len() - 7);
if (OwnerName.Contains(RowNameStr))
{
const FString ContextString(TEXT("Weapon Power Info"));
FPowerInfo* RowStruct = DataTable_PowerInfo->FindRow<FPowerInfo>(RowName, ContextString, true);
PowerInfo_Struct = *RowStruct;
}
}
}
Details
- C_PowerComponent는 Weapon 객체가 갖는 기본 액터 컴포넌트로, Weapon의 기본 공격력 및 그 외 정보들을 갖는 FPowerInfo 구조체 기반의 Data Table을 Load 하는 함수를 별도로 정의합니다.
3. C_MontageComponent::LoadHitAnimInfoFromDataTable()
void UC_MontageComponent::LoadHitAnimInfoFromDataTable()
{
if (!!DataAsset)
{
UDataTable* HitInfo = DataAsset->GetHitInfo();
TArray<FName>RowNames = HitInfo->GetRowNames();
FCommonAnimInfo CommonAninInfo_Struct;
TArray<FString> WeaponTypeAsStrings;
CHelpers::GetEWeaponTypeAsStringArray(WeaponTypeAsStrings);
for (auto RowName : RowNames)
{
FString RowNameStr = RowName.ToString();
for (int32 i = 0; i < WeaponTypeAsStrings.Num(); i++)
{
if (RowNameStr.Contains(WeaponTypeAsStrings[i]))
{
const FString ContextString(TEXT("Hit Anim Info"));
FCommonAnimInfo* RowStruct = HitInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MmHitAnims.Add(static_cast<EWeaponType>(i), CommonAninInfo_Struct);
}
}
}
}
}
Details
- Montage 컴포넌트는 Game Object 객체의 상태 별 재생할 애님 몽타주를 결정하고 재생합니다. 따라서, Montage 컴포넌트는 FCommonAnimInfo 구조체 기반의 Data Table을 Load 하는 함수를 정의합니다.
4. CHelpers.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/ConstructorHelpers.h"
#include "Engine/World.h"
#include "Global/Custom_Enums.h"
#include "Global/Custom_Structs.h"
#include "DataAssets/DA_Weapon.h"
class DARKSOUL_API CHelpers
{
public:
//...
static void LoadSpecInfoFromDataTable(UDataTable** InDataTable, FSpecInfo& OutStruct, FString InString)
{
if (!!(*InDataTable))
{
TArray<FName>RowNames = (*InDataTable)->GetRowNames();
for (auto RowName : RowNames)
{
FString RowNameStr = RowName.ToString();
const FString ContextString(TEXT("Owner Spec Info"));
if (InString.Contains(RowNameStr))
{
FSpecInfo* RowStruct = (*InDataTable)->FindRow<FSpecInfo>(RowName, ContextString, true);
OutStruct = *RowStruct;
}
}
}
}
static void LoadPowerInfoFromDataTable(UDataTable** InDataTable, FPowerInfo& OutStruct, FString InString)
{
if (!!(*InDataTable))
{
TArray<FName>RowNames = (*InDataTable)->GetRowNames();
const FString ContextString(TEXT("Weapon Power Info"));
for (auto RowName : RowNames)
{
FString RowNameStr = RowName.ToString();
if (InString.Contains(RowNameStr))
{
FPowerInfo* RowStruct = (*InDataTable)->FindRow<FPowerInfo>(RowName, ContextString, true);
OutStruct = *RowStruct;
}
}
}
}
static void LoadCommonAnimInfoFromDataTable(UDataTable** InDataTable, TMultiMap<EWeaponType, FCommonAnimInfo>& InMMap)
{
if (!!(*InDataTable))
{
TArray<FName>RowNames = (*InDataTable)->GetRowNames();
FCommonAnimInfo CommonAninInfo_Struct;
TArray<FString> WeaponTypeAsStrings;
CHelpers::GetEWeaponTypeAsStringArray(WeaponTypeAsStrings);
const FString ContextString(TEXT("Common Anim Info"));
for (auto RowName : RowNames)
{
FString RowNameStr = (RowName.ToString()).LeftChop(2);
for (int32 i = 0; i < WeaponTypeAsStrings.Num(); i++)
{
if (RowNameStr.Equals(WeaponTypeAsStrings[i]))
{
FCommonAnimInfo* RowStruct = (*InDataTable)->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
InMMap.Add(static_cast<EWeaponType>(i), CommonAninInfo_Struct);
}
}
}
}
}
static void LoadComboTypeInfoFromDataTable(TArray<UDataTable*>* InDataTables, TMap<EWeaponType, TArray<FComboAnimInfo*>>& OutMap)
{
TArray<FString> WeaponTypeAsStrings;
CHelpers::GetEWeaponTypeAsStringArray(WeaponTypeAsStrings);
TArray<FComboAnimInfo*> ComboAnimInfo_Structs;
if (!!(*InDataTables).IsValidIndex(0))
{
for (int i = 0; i < (*InDataTables).Num(); i++)
{
TArray<FName>RowNames = (*InDataTables)[i]->GetRowNames();
FString RowNameStr = (RowNames[0].ToString()).LeftChop(2);
const FString ContextString(TEXT("Combo Info"));
for (uint8 j = 0; j < WeaponTypeAsStrings.Num(); j++)
{
if (WeaponTypeAsStrings[j].Equals(RowNameStr))
{
(*InDataTables)[i]->GetAllRows<FComboAnimInfo>(ContextString, ComboAnimInfo_Structs);
OutMap.Add(static_cast<EWeaponType>(j), ComboAnimInfo_Structs);
ComboAnimInfo_Structs.Empty();
break;
}
}
}
}
}
static void LoadSkillInfoFromDataTable(TArray<UDataTable*>* InDataTables, TMap<EWeaponType, TArray<FSkillAnimInfo*>>& OutMap)
{
TArray<FString> WeaponTypeAsStrings;
CHelpers::GetEWeaponTypeAsStringArray(WeaponTypeAsStrings);
TArray<FSkillAnimInfo*> SkillInfo_Structs;
if (!!(*InDataTables).IsValidIndex(0))
{
for (int i = 0; i < (*InDataTables).Num(); i++)
{
TArray<FName>RowNames = (*InDataTables)[i]->GetRowNames();
FString RowNameStr = (RowNames[0].ToString()).LeftChop(2);
const FString ContextString(TEXT("Attack Skill Info"));
for (uint8 j = 0; j < WeaponTypeAsStrings.Num(); j++)
{
if (WeaponTypeAsStrings[j].Equals(RowNameStr))
{
(*InDataTables)[i]->GetAllRows<FSkillAnimInfo>(ContextString, SkillInfo_Structs);
OutMap.Add(static_cast<EWeaponType>(j), SkillInfo_Structs);
SkillInfo_Structs.Empty();
break;
}
}
}
}
}
};
Details
- LoadSpecInfoFromDataTable : 전체 GameObject 객체들의 기본 Spec 정보를 담고 있는 Data Table을 Load 하는 정적 함수입니다. 먼저, Data Table의 모든 열들의 이름을 가져옵니다. 다음으로, 인자로 전달받은 문자열과 비교하고, RowName이 해당 문자열을 포함하는 문자열이라면, RowName이 속하는 Row를 구조체 형태로 가져와 인자로 전달받은 FSpecInfo 구조체에 저장합니다.
- LoadPowerInfoFromDataTable : 위와 동일
- LoadCommonAnimInfoFromDataTable : 전체 GameObject 객체들의 Hit Animation과 Death Animation 정보들을 담고 있는 Data Table을 Load 하는 정적 함수 입니다. TMultimap(multimap 자료구조)가 사용됩니다. 하나의 키 값이 여러 데이터 값을 갖는 자료구조로 WeaponType이 키 값, 여러 Hit Animation 혹은 Death Animation을 값들로 Mapping 됩니다.
- LoadComboTypeInfoFromDataTable : 특정 Weapon 객체가 갖는 Combo Type 별 재생할 Combo Animation과 그 외 Combo 공격 정보들을 담고 있는 Data Table을 Load합니다. TMap(map 자료구조)가 사용됩니다. TMultimap(multimap 자료구조) 대신, Value의 자료형으로 TArray를 활용합니다. 왜냐하면, ComboWeapon 클래스를 상속받는 클래스들은 "Combo Count"를 계산해 Count 별로 재생할 Animation을 결정합니다. Combo Count를 Index로, TArray에 임의의 접근이 가능하도록 의도했습니다.
- LoadSkillInfoFromDataTable : 특정 Weapon 객체가 갖는 Skill Animation과 그 외 Skill 관련 정보들을 담고 있는 Data Table을 Load하는 정적 함수입니다. 위와 같이 TMap(map 자료구조)를 활용합니다.
5. 수정 버전, UC_HealthComponent::LoadSpecInfoFromDataTable()
#include "Global/Global.h" // CHelpers.h 가져오기
void UC_HealthComponent::LoadSpecInfoFromDataTable()
{
if (!!DataTable_SpecInfo)
{
FString OwnerName = OwnerObject->GetName();
CHelpers::LoadSpecInfoFromDataTable(&DataTable_SpecInfo, SpecInfo_Struct, OwnerName);
}
}
Details
- CHelpers.h를 #incldue 하는 Global.h 파일을 #include 합니다.
- Game Object 객체로부터 가져온 Data Table과 함께, FSpecInfo 구조체 변수와 Owner(컴포넌트가 멤버로 속한 Game Object 객체)의 이름을 인자로 전달해 Data Table로부터 필요한 정보를 가져올 수 있습니다.
#2. 결론
1. 요약
- 정리하면, Game Object의 컴포넌트들은 각자가 필요로하는 정보들을 Data Table로부터 Load 하기 위해 별도의 Load 함수들을 정의하지만, 컴포넌트들 간 필요로 하는 Data Table들이 중복되어 코드 중복이 발생합니다. 따라서, 각 Load 함수들을 CHelpers 클래스의 Static 함수로 정의함으로써, 액터 컴포넌트들의 Tick() 함수 선언 및 정의와 함께 코드 중복을 방지했습니다.
'개인프로젝트' 카테고리의 다른 글
[Unreal_C++_DarkSoul]#17_기능 구현, Grid 클래스 (0) | 2023.06.03 |
---|---|
[Unreal_C++_DarkSoul]#16_문제 해결, 런 타임 AI 실행 여부 (0) | 2023.05.27 |
[Unreal_C++_DarkSoul]#14_문제 해결, Actor Component 간 소통 (0) | 2023.03.26 |
[Unreal_C++_DarkSoul]#13_기능 구현, Impact Effect (0) | 2023.02.05 |
[Unreal_C++_DarkSoul]#12_문제 해결, Targeting 회전 속도 (0) | 2022.12.25 |