UE5 BlueprintClass with '_C'

에크까망·2024년 9월 30일

들어가기 전에

  • 단순화 하기 위해서 문체를 단정적으로 사용하지만 지극히 개인 의견일 뿐 입니다.
  • 본문과 관련해서 오류 지적이나 의견 있으시면 꼭 댓글 부탁드립니다.
  • 글 작성 시점은 2024/09 입니다.
  • 글 작성 기준은 UE5.4.4 입니다.
  • UClass ReflectionBuild Configuration 포스팅을 먼저 보신것으로 가정합니다.

들어가며

이번 포스팅에서는 언리얼 엔진에서 Blueprint 로 만든 Class 가 어떻게 다뤄 지는지 간략히 살펴보고 왜 Class 를 Loading 할때 '_C' 를 붙여야 하는지 알아보자.

Class

Diagram

UBlueprint

우리가 언리얼 엔진 Editor 에서 UObject 계열을 상속받아 블루프린트를 만들면 'UBlueprint' 를 만든게 된다. 그럼 UBlueprint 안에는 무슨 내용이 있을까? 일단 우리가 열심히 'Drawing' 한 Node 들이 있겠고 각종 주석도 있겠고 등등등 이 있을거다. 그리고 UBlueprint 로 만든 'UClass' 도 있겠다.

UBlueprint 에서 UClass 를 가져오기 위해서는 아래 함수를 이용하면 된다.

UClass* UBlueprintEditorLibrary::GeneratedClass(UBlueprint* BlueprintObj)  
{  
    if (BlueprintObj)  
    {       
	    return BlueprintObj->GeneratedClass->GetAuthoritativeClass();  
    }    
    
    return nullptr;  
}

C++ 에서 Blueprint Class 에 추가한 변수나 함수에 접근하기 위해서는 Reflection 기능을 이용하면 된다. 'UClass Reflection' 포스팅을 참고하자.

BlueprintClass 를 Loading 할때 왜 '_C' 를 붙여야 하지?

사실 이번 포스팅은 이 이유 설명 때문에 작성한 거나 마찬가지다. 패키징 하고 나면 C++ 에서 class 가 로딩이 안되고 검색해 보면 '의례 붙이라고들 한다더라' 라고 하는 경우가 많았다.

아마 필자가 못 찾은 어느 Article 에서 분명히 설명되어 있겠지만, 그래도 우리가 한번 '_C' 를 붙여야 하는 이유를 직접 생각해보자. 사실 이유는 위의 'Class' 섹션 에 다 적혀 있다.

아래 Test는 UObejct 를 상속한 UMyTest 를 만들고, UMyTest 를 상속 받아 Blueprint 로 만들어둔 상태에서 진행한다. 해당 Blueprint 의 경로는 /Game/MyBlueprint/B_Test 이다.

List In Package

아래 코드는 B_Test 패키지 내부 오브젝트 목록을 보여 준다. 각각 DebugGame Editor 와 쿠킹후 DebugGame 에서 실행해보자.

void PrintObjectsInPackage()  
{  
    UPackage* Package = LoadPackage(nullptr, TEXT("/Game/MyBlueprint/B_Test"), LOAD_None);  
    if (Package == nullptr)  
    {       
	    UE_LOG(LogTemp, Warning, TEXT("유효하지 않은 패키지입니다."));  
       return;  
    }  
    // UPackage 안에 포함된 모든 UObject를 순회하며 출력  
    TArray<UObject*> ObjectsInPackage;  
    GetObjectsWithOuter(Package, ObjectsInPackage);  
  
    if (ObjectsInPackage.Num() == 0)  
    {       
    	UE_LOG(LogTemp, Warning, TEXT("패키지에 오브젝트가 없습니다."));  
       return;  
    }  
    // 오브젝트 출력  
    for (UObject* Object : ObjectsInPackage)  
    {       
       if (Object)  
       {          
	       UE_LOG(LogTemp, Log, TEXT("오브젝트 이름: %s, 클래스: %s"), *Object->GetName(), *Object->GetClass()->GetName());  
       }    
    }
}

DebugGame Editor

[2024.09.30-05.25.14:977][498]LogTemp: 오브젝트 이름: PackageMetaData, 클래스: MetaData
[2024.09.30-05.25.14:977][498]LogTemp: 오브젝트 이름: B_Test_C, 클래스: BlueprintGeneratedClass
[2024.09.30-05.25.14:978][498]LogTemp: 오브젝트 이름: B_Test, 클래스: Blueprint
[2024.09.30-05.25.14:978][498]LogTemp: 오브젝트 이름: Default__B_Test_C, 클래스: B_Test_C
[2024.09.30-05.25.14:978][498]LogTemp: 오브젝트 이름: SKEL_B_Test_C, 클래스: BlueprintGeneratedClass
[2024.09.30-05.25.14:978][498]LogTemp: 오브젝트 이름: Default__SKEL_B_Test_C, 클래스: SKEL_B_Test_C
...

다른 오브젝트는 일단 제쳐 두고 두 개만 집중해서 보자.

  • B_Test_C : BlueprintGeneratedClass
  • B_Test : Blueprint

B_Test 는 Editor 에서 생성한 Blueprint 자체이고, 우리가 관심있어 하는 B_Test_C 가 보인다. 타입은 BluerpintGeneratedClass 이다. 즉, Blueprint 를 컴파일 해서 나오는 UClass 의 이름이 B_Test_C 이다. 이게 우리가 _C 를 붙여야 하는 이유다.

그럼 계속 해서 Contents Cooking 후에 DebugGame 으로 테스트 해보자.

DebugGame

[2024.09.30-05.32.11:988][  0]LogTemp: 오브젝트 이름: B_Test_C, 클래스: BlueprintGeneratedClass
[2024.09.30-05.32.11:988][  0]LogTemp: 오브젝트 이름: Default__B_Test_C, 클래스: B_Test_C

B_Test 가 사라 졌다. 당연한 것이 Cooking 은 Runtime 을 위해서 Asset 을 가공한다. 그러므로 B_Test_C: BlueprintGeneratedClass 를 만들기 위해서 필요한 B_Test: Blueprint 는 필요하지 않다.

Load Blueprint

계속해서 아래 코드를 테스트 해보자. Blueprint 를 로딩해서, GeneratedClass 의 CDO 를 형변환 테스트 하는 코드이다.

void TestBlueprint()  
{  
    UObject* BlueprintAsset = StaticLoadObject(UObject::StaticClass(), nullptr, TEXT("/Game/MyBlueprint/B_Test.B_Test"));  
    if (BlueprintAsset)  
    {       
	    UBlueprint* LoadedBlueprint = Cast<UBlueprint>(BlueprintAsset);  
       if (LoadedBlueprint && LoadedBlueprint->GeneratedClass)  
       {          UMyTestObj* myTestObj = Cast<UMyTestObj>(LoadedBlueprint->GeneratedClass->GetDefaultObject());  
          if (myTestObj)  
          {             
          	UE_LOG(LogTemp, Warning, TEXT("To UMyTestObj 캐스팅 성공"));  
          }          
          else  
          {  
             UE_LOG(LogTemp, Warning, TEXT("To UMyTestObj 캐스팅 실패"));  
          }       
	   }       
	    else  
       {  
          UE_LOG(LogTemp, Warning, TEXT("블루프린트 로드 실패 또는 클래스 없음"));  
       }    
    }    
    else  
    {  
       UE_LOG(LogTemp, Warning, TEXT("블루프린트 로드 실패"));  
    }	

DebugGame Editor

[2024.09.30-06.05.25:565][  0]LogTemp: Warning: To UMyTestObj 캐스팅 성공

예상대로 잘 로딩 된다.

DebugGame

[2024.09.30-06.09.38:694][  0]LogTemp: Warning: 블루프린트 로드 실패

오브젝트 자체가 패키지에 없으므로 로딩 실패한다.

Load Blueprint Generated Class

바로 로딩해서 확인해 보자

void AEkUe54GameMode::TestBlueprintClass()
{
	UClass* BlueprintClass = Cast<UClass>(StaticLoadObject(UObject::StaticClass(), nullptr, TEXT("/Game/MyBlueprint/B_Test.B_Test_C")));

	if (BlueprintClass)
	{
		if (BlueprintClass->IsChildOf(UMyTestObj::StaticClass()))
		{
			UE_LOG(LogTemp, Warning, TEXT("Class UMyTestObj 확인"));
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("Class UMyTestObj 확인 실패"));
		}
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("로딩 실패"));
	}
}

DebugGame Editor

[2024.09.30-06.21.44:238][  0]LogTemp: Warning: Class UMyTestObj 확인

DebugGame

[2024.09.30-06.24.04:253][  0]LogTemp: Warning: Class UMyTestObj 확인

역시 예상대로 잘 작동한다.

여기서 문제

위 Code 에서 이렇게 바꾸면

UClass* BlueprintClass = Cast<UClass>(StaticLoadObject(UMyTestObj::StaticClass(), nullptr, TEXT("/Game/MyBlueprint/B_Test.B_Test_C")));
[2024.09.30-06.25.36:493][  0]LogTemp: Warning: 로딩 실패

이다. 왜 그런지 이유를 생각해 보고 이해가 잘 되지 않으면 UClass Reflection 포스팅에 있는 Class Diagram 을 다시 살펴 보자.

profile
Game Client Programmer

0개의 댓글