본문 바로가기

UE5 Cast의 내부 동작 원리

@iamrain2025. 10. 22. 09:56

1. Cast란?

Cast는 언리얼 엔진의 UObject 시스템에서 제공하는 핵심 기능 중 하나로, 특정 클래스 타입으로 객체 포인터를 안전하게 변환(다운캐스팅)하는 데 사용된다. C++의 dynamic_cast와 유사한 역할을 하지만, 언리얼 엔진의 리플렉션 시스템을 기반으로 동작하여 훨씬 뛰어난 성능을 보인다.

캐스팅이 성공하면 해당 타입의 유효한 포인터를 반환하고, 실패하면 nullptr를 반환하여 프로그램의 비정상적인 종료를 방지한다.

// SomeActor는 AActor 타입의 포인터
ACharacter* MyCharacter = Cast<ACharacter>(SomeActor);
if (MyCharacter)
{
    // SomeActor가 실제로는 ACharacter이거나 그 자식 클래스일 경우,
    // 이 블록이 실행됩니다.
    MyCharacter->Jump();
}

2. Cast의 동작 계층 구조

계층 1: 사용자 코드 (User Code)

개발자는 게임 로직에서 특정 UObject가 원하는 타입인지 확인하고 싶을 때 Cast를 사용한다.

void AMyTriggerBox::OnOverlapBegin(AActor* OverlappedActor, AActor* OtherActor)
{
    // 트리거 박스에 들어온 액터가 플레이어인지 확인
    APlayerCharacter* PlayerCharacter = Cast<APlayerCharacter>(OtherActor);
    if (PlayerCharacter)
    {
        PlayerCharacter->ApplyDamage(10.f);
    }
}

계층 2: 엔진 API (Casts.h)

Cast<T>()Engine/Source/Runtime/CoreUObject/Public/Templates/Casts.h에 정의된 템플릿 함수다. 이 함수는 다양한 C++ 타입에 대해 오버로딩 및 템플릿 특수화를 통해 최적의 캐스팅 방법을 제공한다. UObject를 상속받은 클래스의 경우, 내부적으로 UObject::IsA 함수를 호출하는 로직으로 이어진다.

// Casts.h의 UObject 관련 Cast 구현 일부 (개념적)
template <typename To, typename From>
FORCE_INLINE To* Cast(From* Src)
{
    // UObject에서 파생된 타입인지 확인 후,
    // 내부 캐스팅 함수를 호출한다.
    return TCastImpl<To, From>::DoCast(Src);
}

// TCastImpl의 UObject 특수화 버전 (개념적)
template <typename To, typename From>
struct TCastImpl<...UObject...>
{
    static To* DoCast(From* Src)
    {
        // IsA 함수로 타입 검사 후, static_cast로 변환
        return Src && Src->IsA(To::StaticClass()) ? static_cast<To*>(Src) : nullptr;
    }
};

계층 3: UObject의 IsA 함수 (Object.h)

UObject::IsA 함수는 객체가 특정 클래스(UClass)의 인스턴스이거나 해당 클래스로부터 파생되었는지를 검사하는 핵심 함수다. 이 함수는 dynamic_cast와 달리 C++ RTTI(Run-Time Type Information)를 사용하지 않는다.

// UObject.h의 IsA 함수 (개념적)
bool UObject::IsA(const UClass* SomeBase) const
{
    // 객체의 UClass 정보를 가져온다.
    const UClass* ThisClass = GetClass();
    // 부모 클래스 체인을 따라 올라가며 SomeBase와 일치하는지 확인
    return ThisClass->IsChildOf(SomeBase);
}

계층 4: UClass의 상속 체인 검사 (Class.h)

UClass::IsChildOf 함수는 리플렉션 데이터의 일부인 클래스 상속 구조 정보를 이용해 검사를 수행한다. 모든 UClass는 자신의 부모 UClass에 대한 포인터(SuperStruct)를 가지고 있으며, 이 포인터를 따라가며 상속 계층을 확인한다.

// UClass.h의 IsChildOf 함수 (개념적)
bool UClass::IsChildOf(const UStruct* SomeBase) const
{
    // 자기 자신부터 시작해서
    for (const UStruct* TempStruct = this; TempStruct; TempStruct = TempStruct->GetSuperStruct())
    {
        // 부모 체인 중에 SomeBase가 있다면 true
        if (TempStruct == SomeBase)
        {
            return true;
        }
    }
    return false;
}

이 방식은 단순한 포인터 비교의 연속이므로 매우 빠르다.

계층 5: UHT (Unreal Header Tool) 코드 생성

이 모든 과정이 가능하려면 각 클래스의 타입 정보(UClass)와 상속 구조가 엔진에 등록되어 있어야 한다. 이 역할을 Unreal Header Tool (UHT)이 담당한다.

개발자가 UCLASS() 매크로를 클래스에 사용하면, UHT는 빌드 과정에서 헤더 파일을 파싱하여 리플렉션 데이터를 담은 .generated.h 파일을 자동으로 생성한다. 이 파일에는 StaticClass() 함수, 부모 클래스 정보, 멤버 변수 및 함수 정보 등이 포함된다. StaticClass()는 해당 클래스의 UClass 싱글톤 객체를 반환하는 정적 함수로, CastIsA에서 클래스를 식별하는 데 사용된다.

3. Cast vs. C++ dynamic_cast

구분 UE5 Cast C++ dynamic_cast
대상 UObject를 상속받은 클래스 가상 함수가 하나 이상 포함된 다형적 C++ 클래스
성능 매우 빠름. 사전 생성된 상속 체인(포인터 배열)을 순회하는 방식. 상대적으로 느림. RTTI 정보를 이용해 런타임에 타입 문자열 비교 등 복잡한 연산을 수행.
메커니즘 언리얼 리플렉션 시스템 (UClass, SuperStruct) C++ RTTI (Run-Time Type Information)
메모리 리플렉션 데이터가 메모리를 차지하지만, 이는 직렬화, 블루프린트 등 다용도로 활용됨. RTTI 정보가 클래스마다 추가적인 메모리 오버헤드를 유발.
사용처 언리얼 엔진 프로젝트의 UObject 관련 코드 일반적인 C++ 프로젝트 또는 언리얼에서 UObject가 아닌 클래스

결론: 언리얼 엔진 환경에서 UObject 파생 클래스를 다룰 때는 성능상의 이점과 엔진 시스템과의 호환성을 위해 dynamic_cast 대신 Cast를 사용하는 것이 필수적입니다.

4. 언제, 어떻게 사용해야 하는가?

  1. 안전한 다운캐스팅이 필요할 때
    상위 타입 포인터를 하위 타입 포인터로 변환해야 할 때 사용한다. Cast는 타입 검사를 포함하므로 가장 안전하고 일반적인 방법이다.
    AActor* Actor = GetSomeActor();
    if (AMySpecificActor* SpecificActor = Cast<AMySpecificActor>(Actor))
    {
        SpecificActor->DoSomethingSpecific();
    }
  2. 인터페이스 구현 여부를 확인할 때
    Cast는 클래스 상속뿐만 아니라 인터페이스 구현 여부를 확인하는 데도 사용할 수 있다.
    if (IMyInterface* MyInterface = Cast<IMyInterface>(SomeActor))
    {
        MyInterface->Execute_MyInterfaceFunction(SomeActor);
    }
  3. 캐스팅 성공이 보장될 때: CastChecked
    코드의 논리상 캐스팅이 100% 성공해야만 하는 상황에서는 CastChecked를 사용할 수 있다. CastChecked는 실패 시 nullptr를 반환하는 대신 어설트(assert)를 발생시켜 개발 과정에서 문제를 즉시 발견하도록 돕는다. 릴리즈 빌드에서는 성능을 위해 검사 없이 static_cast처럼 동작할 수 있으므로, 실패 가능성이 조금이라도 있다면 사용해서는 안 된다.
    // 이 액터는 반드시 APlayerController여야만 함
    APlayerController* PC = CastChecked<APlayerController>(GetController());

5. 요약

UE5의 CastUObject 기반의 객체를 다른 타입으로 안전하게 변환하는 기능입니다. C++의 dynamic_cast와 역할은 비슷하지만, 내부 동작 방식과 성능에서 큰 차이가 있습니다.

dynamic_cast는 C++ 표준 기능인 RTTI를 사용해 런타임에 클래스 정보를 비교하는데, 이는 상대적으로 느린 작업입니다. 반면, 언리얼의 Cast는 UHT가 빌드 시점에 미리 생성해둔 리플렉션 데이터를 사용합니다. 이 데이터에는 각 클래스의 부모가 누구인지에 대한 정보가 단순한 포인터 연결 리스트처럼 저장되어 있습니다. Cast는 이 상속 체인을 따라가며 포인터를 몇 번 비교하는 것만으로 타입 검사를 끝내기 때문에 dynamic_cast보다 월등히 빠릅니다.

따라서 언리얼 엔진에서 UObject를 다룰 때는, 엔진의 최적화된 리플렉션 시스템을 최대한 활용하고 일관성을 유지하기 위해 반드시 Cast를 사용해야 합니다.

'Unreal' 카테고리의 다른 글

언리얼 엔진 리플리케이션 Replication  (1) 2025.10.24
언리얼 엔진 리플렉션 시스템 Unreal Engine Reflection System  (0) 2025.10.23
CDO (Class Default Object)  (0) 2025.10.21
UObject  (0) 2025.10.21
UPROPERTY와 UFUNCTION의 차이  (0) 2025.10.16
iamrain
@iamrain :: Annals of Unreal

iamrain 님의 블로그 입니다.

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차