개인프로젝트

[Unreal_C++_DarkSoul]#4_문제 해결, Data Table 로드 함수

Hardii2 2022. 11. 27. 01:06

 

[Unreal_C++_DarkSoul]#4_리팩터링, Data Table 로드 함수, static 멤버 함수

 

Unreal C++ 개발 중 "Static 멤버 함수를 통한 리팩터링"에 대한 내용입니다.

포트폴리오 진행 사항을 기록하기 위한 포스팅입니다.

 


 

Overview

 

  1. 문제점
  2. 해결

 

#1. 문제점

 

1. 내용

  1. Unreal Editor에서 작성한 Player의 "Hit(피격)" 그리고 "Death(죽음)" 애니메이션 정보를 담은 데이터 테이블을 C++로 읽어오는 함수를 작성했습니다.
  2. 가독성과 성능이 비교적 나쁘지 않았지만, 중복 코드가 많아서 리팩토링을 하기로 결정했습니다.
  3. Data Table의 행 이름을 Player의 공격 모드 이름과 일일이 비교하는 여러개의 조건문로 인해 코드 중복이 발생하는 문제를 발경했습니다.
  4. 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

 

  1. 특정 Enum Type(EPlayerCombatType)의 멤버들을 "E_"를 제외한 뒷 문자열을 "FString"으로 변환 후 반환하는 함수 작성
  2. 참조형 배열을 인자로 받아, 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문이 줄지어 있던 수정 전의 코드보다, 가독성도 향상되었고 코드 중복을 많이 줄였습니다.
  • 다만, 몇 가지 불안점이 존재합니다.