[Unreal_C++_DarkSoul]#4_리팩터링, Data Table 로드 함수, static 멤버 함수
Unreal C++ 개발 중 "Static 멤버 함수를 통한 리팩터링"에 대한 내용입니다.
포트폴리오 진행 사항을 기록하기 위한 포스팅입니다.
Overview
- 문제점
- 해결
#1. 문제점
1. 내용
- Unreal Editor에서 작성한 Player의 "Hit(피격)" 그리고 "Death(죽음)" 애니메이션 정보를 담은 데이터 테이블을 C++로 읽어오는 함수를 작성했습니다.
- 가독성과 성능이 비교적 나쁘지 않았지만, 중복 코드가 많아서 리팩토링을 하기로 결정했습니다.
- Data Table의 행 이름을 Player의 공격 모드 이름과 일일이 비교하는 여러개의 조건문로 인해 코드 중복이 발생하는 문제를 발경했습니다.
- Enum 타입의 멤버들을 "FString"으로 변환하고 인자로 받은 배열에 넣어 반환하는 static 함수를 별도의 다용성 클래스에 정의했습니다.
2. PlayerCharacter::LoadHitAnimInfoFromDataTable()
void APlayerCharacter::LoadHitAnimInfoFromDataTable()
{
// TODO : Null Check
CheckNull(DataTable_HitAnimInfo);
TArray<FName>RowNames = DataTable_HitAnimInfo->GetRowNames();
FCommonAnimInfo CommonAninInfo_Struct;
for (auto RowName : RowNames)
{
FString RowNameStr = RowName.ToString();
// 1. Unarmed
if (RowNameStr.Contains("Unarmed"))
{
const FString ContextString(TEXT("Unarmed Hit Anim Info"));
FCommonAnimInfo* RowStruct = DataTable_HitAnimInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MmHitAnim.Add(EPlayerCombatType::E_Unarmed, CommonAninInfo_Struct);
}
// 2. Sword Master
else if (RowNameStr.Contains("Sword"))
{
const FString ContextString(TEXT("Sword Master Hit Anim Info"));
FCommonAnimInfo* RowStruct = DataTable_HitAnimInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MmHitAnim.Add(EPlayerCombatType::E_SwordMaster, CommonAninInfo_Struct);
}
// 3. Axe Master
else if (RowNameStr.Contains("Axe"))
{
const FString ContextString(TEXT("Axe Master Hit Anim Info"));
FCommonAnimInfo* RowStruct = DataTable_HitAnimInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MmHitAnim.Add(EPlayerCombatType::E_AxeMaster, CommonAninInfo_Struct);
}
// 3. Fighter
else if (RowNameStr.Contains("Fist"))
{
const FString ContextString(TEXT("Fighter Hit Anim Info"));
FCommonAnimInfo* RowStruct = DataTable_HitAnimInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MmHitAnim.Add(EPlayerCombatType::E_Fighter, CommonAninInfo_Struct);
}
// 4. Samurai
else if (RowNameStr.Contains("Katana"))
{
const FString ContextString(TEXT("Samurai Hit Anim Info"));
FCommonAnimInfo* RowStruct = DataTable_HitAnimInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MmHitAnim.Add(EPlayerCombatType::E_Samurai, CommonAninInfo_Struct);
}
// 5. Warrior
else if (RowNameStr.Contains("SwordAndShield"))
{
const FString ContextString(TEXT("Warrior Hit Anim Info"));
FCommonAnimInfo* RowStruct = DataTable_HitAnimInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MmHitAnim.Add(EPlayerCombatType::E_Warrior, CommonAninInfo_Struct);
}
// 6. Wizard
else if (RowNameStr.Contains("Magic"))
{
const FString ContextString(TEXT("Wizard Hit Anim Info"));
FCommonAnimInfo* RowStruct = DataTable_HitAnimInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MmHitAnim.Add(EPlayerCombatType::E_Wizard, CommonAninInfo_Struct);
}
}
}
void APlayerCharacter::LoadDeathAnimInfoFromDataTable()
{
// TODO : Null Check
CheckNull(DataTable_DeathAnimInfo);
TArray<FName>RowNames = DataTable_DeathAnimInfo->GetRowNames();
FCommonAnimInfo CommonAninInfo_Struct;
for (auto RowName : RowNames)
{
FString RowNameStr = RowName.ToString();
// 1. Unarmed
if (RowNameStr.Contains("Unarmed"))
{
const FString ContextString(TEXT("Unarmed Death Anim Info"));
FCommonAnimInfo* RowStruct = DataTable_DeathAnimInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MDeathAnim.Add(EPlayerCombatType::E_Unarmed, CommonAninInfo_Struct);
}
// 2. Sword Master
else if (RowNameStr.Contains("Sword"))
{
const FString ContextString(TEXT("Sword Master Death Anim Info"));
FCommonAnimInfo* RowStruct = DataTable_DeathAnimInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MDeathAnim.Add(EPlayerCombatType::E_SwordMaster, CommonAninInfo_Struct);
}
// 3. Axe Master
else if (RowNameStr.Contains("Axe"))
{
const FString ContextString(TEXT("Axe Mater Death Anim Info"));
FCommonAnimInfo* RowStruct = DataTable_DeathAnimInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MDeathAnim.Add(EPlayerCombatType::E_AxeMaster, CommonAninInfo_Struct);
}
// 4. Fighter
else if (RowNameStr.Contains("Fist"))
{
const FString ContextString(TEXT("Fighter Death Anim Info"));
FCommonAnimInfo* RowStruct = DataTable_DeathAnimInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MDeathAnim.Add(EPlayerCombatType::E_Fighter, CommonAninInfo_Struct);
}
// 5. Samurai
else if (RowNameStr.Contains("Katana"))
{
const FString ContextString(TEXT("Samurai Death Anim Info"));
FCommonAnimInfo* RowStruct = DataTable_DeathAnimInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MDeathAnim.Add(EPlayerCombatType::E_Samurai, CommonAninInfo_Struct);
}
// 5. Warrior
else if (RowNameStr.Contains("SwordAndShield"))
{
const FString ContextString(TEXT("Warrior Death Anim Info"));
FCommonAnimInfo* RowStruct = DataTable_DeathAnimInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MDeathAnim.Add(EPlayerCombatType::E_Warrior, CommonAninInfo_Struct);
}
// 6. Wizard
else if (RowNameStr.Contains("Magic"))
{
const FString ContextString(TEXT("Wizard Death Anim Info"));
FCommonAnimInfo* RowStruct = DataTable_DeathAnimInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MDeathAnim.Add(EPlayerCombatType::E_Wizard, CommonAninInfo_Struct);
}
}
}
Details
- 읽어온 Data Table의 행이름을 공격 모드와 비교하기 위해 작성한 하드 코딩 + 중복 코드
- if문과 여러 개의 else-if 문이 줄지어 있습니다.
#2. 해결
1. CHelpers.h
// Remark [CHelpers, Static]
static FString GetEPlayerCombatTypeAsString(EPlayerCombatType EnumValue)
{
const UEnum* enumPtr = FindObject<UEnum>(ANY_PACKAGE, TEXT("EPlayerCombatType"), true);
if (!enumPtr)
{
return FString("Invalid");
}
return enumPtr->GetNameStringByIndex((int32)EnumValue).Right(enumPtr->GetNameStringByIndex((int32)EnumValue).Len() - 2);
}
// Remark [CHelpers, Static]
static void GetEPlayerCombatTypeAsStringArray(TArray<FString>& OutArr)
{
// CombatType
for (int32 i = 0; i < static_cast<int>(EPlayerCombatType::E_Max); i++)
{
EPlayerCombatType CombatType = static_cast<EPlayerCombatType>(i);
FString CombatTypeAsString = GetEPlayerCombatTypeAsString(CombatType);
OutArr.Add(CombatTypeAsString);
}
}
Details
- 특정 Enum Type(EPlayerCombatType)의 멤버들을 "E_"를 제외한 뒷 문자열을 "FString"으로 변환 후 반환하는 함수 작성
- 참조형 배열을 인자로 받아, EPlayerCombatType의 모든 멤버들을 "FString"으로 변환하여 해당 배열에 저장합니다.
2. Player Character::LoadHitAnimInfoFromDataTable()
void APlayerCharacter::LoadHitAnimInfoFromDataTable()
{
// TODO : Null Check
CheckNull(DataTable_HitAnimInfo);
TArray<FName>RowNames = DataTable_HitAnimInfo->GetRowNames();
FCommonAnimInfo CommonAninInfo_Struct;
for (auto RowName : RowNames)
{
// Row Name As String
FString RowNameStr = RowName.ToString();
// Remark [CHelpers, Static]
TArray<FString> CombatTypeAsStrings;
CHelpers::GetEPlayerCombatTypeAsStringArray(CombatTypeAsStrings);
for (int32 i =0; i<CombatTypeAsStrings.Num(); i++)
{
if (RowNameStr.Contains(CombatTypeAsStrings[i]))
{
const FString ContextString(TEXT("Hit Anim Info"));
FCommonAnimInfo* RowStruct = DataTable_HitAnimInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MmHitAnim.Add(static_cast<EPlayerCombatType>(i), CommonAninInfo_Struct);
break;
}
}
}
}
void APlayerCharacter::LoadDeathAnimInfoFromDataTable()
{
// TODO : Null Check
CheckNull(DataTable_DeathAnimInfo);
TArray<FName>RowNames = DataTable_DeathAnimInfo->GetRowNames();
FCommonAnimInfo CommonAninInfo_Struct;
for (auto RowName : RowNames)
{
FString RowNameStr = RowName.ToString();
// Remark [CHelpers, Static]
TArray<FString> CombatTypeAsStrings;
CHelpers::GetEPlayerCombatTypeAsStringArray(CombatTypeAsStrings);
// 1. Unarmed
for (int32 i = 0; i < CombatTypeAsStrings.Num(); i++)
{
if (RowNameStr.Contains(CombatTypeAsStrings[i]))
{
CustomLog::Log(CombatTypeAsStrings[i]);
const FString ContextString(TEXT("Death Anim Info"));
FCommonAnimInfo* RowStruct = DataTable_DeathAnimInfo->FindRow<FCommonAnimInfo>(RowName, ContextString, true);
CommonAninInfo_Struct = *RowStruct;
MDeathAnim.Add(static_cast<EPlayerCombatType>(i), CommonAninInfo_Struct);
}
}
}
}
Details
- if문과 else-if문이 줄지어 있던 수정 전의 코드보다, 가독성도 향상되었고 코드 중복을 많이 줄였습니다.
- 다만, 몇 가지 불안점이 존재합니다.
'개인프로젝트' 카테고리의 다른 글
[Unreal_C++_DarkSoul]#6_기능 구현, Targeting 기능 (0) | 2022.12.10 |
---|---|
[Unreal_C++_DarkSoul]#5_기능 구현, Target Point, Enemy Spawn 위치 (0) | 2022.12.10 |
[Unreal_C++_DarkSoul]#3_기능 구현, Custom Structure, Custom Enumeration (0) | 2022.11.23 |
[Unreal_C++_DarkSoul]#2_기능 구현, Custom Log Class (0) | 2022.11.23 |
[Unreal_C++_DarkSoul]#1_공부, Interaface 클래스 (0) | 2022.11.22 |