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

우리가 언리얼 엔진 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' 포스팅을 참고하자.
사실 이번 포스팅은 이 이유 설명 때문에 작성한 거나 마찬가지다. 패키징 하고 나면 C++ 에서 class 가 로딩이 안되고 검색해 보면 '의례 붙이라고들 한다더라' 라고 하는 경우가 많았다.
아마 필자가 못 찾은 어느 Article 에서 분명히 설명되어 있겠지만, 그래도 우리가 한번 '_C' 를 붙여야 하는 이유를 직접 생각해보자. 사실 이유는 위의 'Class' 섹션 에 다 적혀 있다.
아래 Test는 UObejct 를 상속한 UMyTest 를 만들고, UMyTest 를 상속 받아 Blueprint 로 만들어둔 상태에서 진행한다. 해당 Blueprint 의 경로는 /Game/MyBlueprint/B_Test 이다.
아래 코드는 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());
}
}
}
[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 는 Editor 에서 생성한 Blueprint 자체이고, 우리가 관심있어 하는 B_Test_C 가 보인다. 타입은 BluerpintGeneratedClass 이다. 즉, Blueprint 를 컴파일 해서 나오는 UClass 의 이름이 B_Test_C 이다. 이게 우리가 _C 를 붙여야 하는 이유다.
그럼 계속 해서 Contents Cooking 후에 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 는 필요하지 않다.
계속해서 아래 코드를 테스트 해보자. 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("블루프린트 로드 실패"));
}
[2024.09.30-06.05.25:565][ 0]LogTemp: Warning: To UMyTestObj 캐스팅 성공
예상대로 잘 로딩 된다.
[2024.09.30-06.09.38:694][ 0]LogTemp: Warning: 블루프린트 로드 실패
오브젝트 자체가 패키지에 없으므로 로딩 실패한다.
바로 로딩해서 확인해 보자
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("로딩 실패"));
}
}
[2024.09.30-06.21.44:238][ 0]LogTemp: Warning: Class UMyTestObj 확인
[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 을 다시 살펴 보자.