switch 그만. 코드가 길어진단 말야.

김지윤·2025년 3월 3일
0

UE5

목록 보기
10/16

switch는 물론 좋은 문법이다. 조건이 많아질 경우 if문보다 가시성이 좋으며, 컴파일 타임에 최적화되기 때문에 실행 속도도 매우 빠르다.
하지만 여기저기 남발해선 안 된다.
추가될 때마다 코드가 길어진다는 단점이 있는데, 이게 부각되는 상황이 하나 있다.

void ABlasterCharacter::PlayReloadMontage()
{
	if (!IsWeaponEquipped()) return;

	UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
	if (AnimInstance && ReloadMontage)
	{
		AnimInstance->Montage_Play(ReloadMontage);
		FName SectionName;
		switch (Combat->EquippedWeapon->GetWeaponType())
		{
		case EWeaponType::EWT_AssaultRifle:
			SectionName = FName("AssaultRifle");
			break;
		case EWeaponType::EWT_RocketLauncher:
			SectionName = FName("RocketLauncher");
			break;
		case EWeaponType::EWT_Pistol:
			SectionName = FName("Pistol");
			break;
		case EWeaponType::EWT_SubmachineGun:
			SectionName = FName("SubmachineGun");
			break;
		case EWeaponType::EWT_Shotgun:
			SectionName = FName("Shotgun");
			break;
		case EWeaponType::EWT_SniperRifle:
			SectionName = FName("SniperRifle");
			break;
		}
		
		AnimInstance->Montage_JumpToSection(SectionName);
	}
}

위는 내가 수강 중인 강의 프로젝트의 코드 일부다.
보면 WeaponType에 따라 다른 장전 애니메이션을 재생하기 위해 장착 중인 무기의 WeaponType을 받아오는 걸 알 수 있다.
switch문을 통해 분기를 나누고, 다른 FName을 저장해 그에 해당하는 몽타주 섹션으로 이동해 애니메이션을 재생한다.

그럼 WeaponType은 어디에서 정의하는가?

// WeaponTypes.h
UENUM(BlueprintType)
enum class EWeaponType : uint8
{
	EWT_AssaultRifle UMETA(DisplayName = "Assault Rifle"),
	EWT_RocketLauncher UMETA(DisplayName = "Rocket Launcher"),
	EWT_Pistol UMETA(DisplayName = "Pistol"),
	EWT_SubmachineGun UMETA(DisplayName = "SubmachineGun"),
	EWT_Shotgun UMETA(DisplayName = "Shotgun"),
	EWT_SniperRifle UMETA(DisplayName = "Sniper Rifle"),
	
	EWT_MAX UMETA(DisplayName = "DefualtMax"),
};

WeaponTypes라는 헤더파일에서 정의하고 있다.
그렇다. 여기서 정의한 뒤 캐릭터 cpp 파일로 찾아가서 추가적으로 switch문을 적어줘야 한다.
하나라면 괜찮을 수도 있겠지만, 나중에 WeaponType에 따라 다른 작동을 하는 함수가 추가된다면?
거기서도 switch로 하겠지?
그럼 enum 추가될 때마다 장전 애니메이션 호출 함수에 추가로 작성하고 거기도 가서 작성해야겠지?
아 벌써 어지럽다.
무조건 해결해야 한다.
마침 enum을 정의했고, enum과 가장 궁합이 좋은 TMap을 쓸 수 있다.
캐릭터한테 TMap을 갖게 하면 다른 곳에서 참조하는 게 까다로워지므로 WeaponTypes의 헤더파일 안에서 선언해주면 좋겠다.

#pragma once

#include "CoreMinimal.h"
#include "WeaponTypes.generated.h"

#define TRACE_LENGTH 80000.f;

UENUM(BlueprintType)
enum class EWeaponType : uint8
{
	...
};

USTRUCT()
struct BLASTER_API FWeaponTypes
{
	GENERATED_BODY()

	FWeaponTypes()
	{
		WeaponName.Add(EWeaponType::EWT_AssaultRifle, TEXT("Rifle"));
        ...
	}

	static const FWeaponTypes& GetWeaponTypeInstance()
	{
		static FWeaponTypes Instance;
		return Instance;
	}

	UPROPERTY()
	TMap<EWeaponType, FName> WeaponName;
};

구조체 안에 TMap을 선언, 생성자에서 Add를 통해 해당 TMap을 초기화한다.
이러면 enum에 해당하는 FName을 같은 파일에서 초기화해줄 수 있기 때문에 가시성이 훌륭하다.
또 데이터만 갖고 있는 매우 간단한 구조체기 때문에 static으로 선언했다.
데이터가 필요할 때마다 생성하는 건 성능상 문제가 있을 거라 판단했기 때문이다.

static은 참조할 때 딱 한 번만 생성되고, 메모리에서 내려가지 않으며 계속 해당 인스턴스를 참조하게 된다.
남발하면 끔찍하지만 적절하게 쓴다면 메모리 관리는 물론 최적화면에서도 훌륭한 문법이다.

void ABlasterCharacter::PlayReloadMontage()
{
	if (!IsWeaponEquipped()) return;

	UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
	if (AnimInstance && ReloadMontage)
	{
		AnimInstance->Montage_Play(ReloadMontage);
		const FWeaponTypes& WeaponTypes = FWeaponTypes::GetWeaponTypeInstance();
		const FName SectionName = WeaponTypes.WeaponName.FindRef(Combat->EquippedWeapon->GetWeaponType());
		
		AnimInstance->Montage_JumpToSection(SectionName);
	}
}

다시 장전 애니메이션 재생 함수를 보면 이전과 비교해 말도 안 되게 짧아졌다.
switch를 남발하는 게 성능면에서 이득일 수 있지만, 사실 그리 큰 차이도 아니고 프로젝트가 커질수록 유지보수성, 가시성 등에서 끔찍한 모습이 되어버릴 수 있으니 잘 저울질해보자.

profile
공부한 거 시간 날 때 작성하는 곳

0개의 댓글