概要
UBlueprintGeneratedClassのメンバーをリフレクションを使って解析してどうなっているのかを確認します
検証用のソースコード長いので展開してください
FString GetPropertyFlagsString(const FProperty* Property)
{
const TPair<EPropertyFlags, const TCHAR*> PropertyFlags[] =
{
{CPF_None, TEXT("CPF_None")},
{CPF_Edit, TEXT("CPF_Edit")},
{CPF_ConstParm, TEXT("CPF_ConstParm")},
{CPF_BlueprintVisible, TEXT("CPF_BlueprintVisible")},
{CPF_ExportObject, TEXT("CPF_ExportObject")},
{CPF_BlueprintReadOnly, TEXT("CPF_BlueprintReadOnly")},
{CPF_Net, TEXT("CPF_Net")},
{CPF_EditFixedSize, TEXT("CPF_EditFixedSize")},
{CPF_Parm, TEXT("CPF_Parm")},
{CPF_OutParm, TEXT("CPF_OutParm")},
{CPF_ZeroConstructor, TEXT("CPF_ZeroConstructor")},
{CPF_ReturnParm, TEXT("CPF_ReturnParm")},
{CPF_DisableEditOnTemplate, TEXT("CPF_DisableEditOnTemplate")},
{CPF_NonNullable, TEXT("CPF_NonNullable")},
{CPF_Transient, TEXT("CPF_Transient")},
{CPF_Config, TEXT("CPF_Config")},
{CPF_RequiredParm, TEXT("CPF_RequiredParm")},
{CPF_DisableEditOnInstance, TEXT("CPF_DisableEditOnInstance")},
{CPF_EditConst, TEXT("CPF_EditConst")},
{CPF_GlobalConfig, TEXT("CPF_GlobalConfig")},
{CPF_InstancedReference, TEXT("CPF_InstancedReference")},
{CPF_ExperimentalExternalObjects, TEXT("CPF_ExperimentalExternalObjects")},
{CPF_DuplicateTransient, TEXT("CPF_DuplicateTransient")},
{CPF_SaveGame, TEXT("CPF_SaveGame")},
{CPF_NoClear, TEXT("CPF_NoClear")},
{CPF_Virtual, TEXT("CPF_Virtual")},
{CPF_ReferenceParm, TEXT("CPF_ReferenceParm")},
{CPF_BlueprintAssignable, TEXT("CPF_BlueprintAssignable")},
{CPF_Deprecated, TEXT("CPF_Deprecated")},
{CPF_IsPlainOldData, TEXT("CPF_IsPlainOldData")},
{CPF_RepSkip, TEXT("CPF_RepSkip")},
{CPF_RepNotify, TEXT("CPF_RepNotify")},
{CPF_Interp, TEXT("CPF_Interp")},
{CPF_NonTransactional, TEXT("CPF_NonTransactional")},
{CPF_EditorOnly, TEXT("CPF_EditorOnly")},
{CPF_NoDestructor, TEXT("CPF_NoDestructor")},
{CPF_AutoWeak, TEXT("CPF_AutoWeak")},
{CPF_ContainsInstancedReference, TEXT("CPF_ContainsInstancedReference")},
{CPF_AssetRegistrySearchable, TEXT("CPF_AssetRegistrySearchable")},
{CPF_SimpleDisplay, TEXT("CPF_SimpleDisplay")},
{CPF_AdvancedDisplay, TEXT("CPF_AdvancedDisplay")},
{CPF_Protected, TEXT("CPF_Protected")},
{CPF_BlueprintCallable, TEXT("CPF_BlueprintCallable")},
{CPF_BlueprintAuthorityOnly, TEXT("CPF_BlueprintAuthorityOnly")},
{CPF_TextExportTransient, TEXT("CPF_TextExportTransient")},
{CPF_NonPIEDuplicateTransient, TEXT("CPF_NonPIEDuplicateTransient")},
{CPF_ExposeOnSpawn, TEXT("CPF_ExposeOnSpawn")},
{CPF_PersistentInstance, TEXT("CPF_PersistentInstance")},
{CPF_UObjectWrapper, TEXT("CPF_UObjectWrapper")},
{CPF_HasGetValueTypeHash, TEXT("CPF_HasGetValueTypeHash")},
{CPF_NativeAccessSpecifierPublic, TEXT("CPF_NativeAccessSpecifierPublic")},
{CPF_NativeAccessSpecifierProtected, TEXT("CPF_NativeAccessSpecifierProtected")},
{CPF_NativeAccessSpecifierPrivate, TEXT("CPF_NativeAccessSpecifierPrivate")},
{CPF_SkipSerialization, TEXT("CPF_SkipSerialization")},
{CPF_ExperimentalOverridableLogic, TEXT("CPF_ExperimentalOverridableLogic")},
{CPF_ExperimentalAlwaysOverriden, TEXT("CPF_ExperimentalAlwaysOverriden")},
{CPF_ExperimentalNeverOverriden, TEXT("CPF_ExperimentalNeverOverriden")},
{CPF_AllowSelfReference, TEXT("CPF_AllowSelfReference")},
};
FString PropertyFlagsStr;
for (auto& [Flag, Name] : PropertyFlags)
{
if (Property->HasAnyPropertyFlags(Flag))
{
PropertyFlagsStr = FString::Printf(TEXT("%s|%s"), *PropertyFlagsStr, Name);
}
}
return PropertyFlagsStr;
}
FString GetFunctionFlagsString(const UFunction* Function)
{
const TPair<EFunctionFlags, const TCHAR*> FunctionFlags[] =
{
{FUNC_None, TEXT("FUNC_None")},
{FUNC_Final, TEXT("FUNC_Final")},
{FUNC_RequiredAPI, TEXT("FUNC_RequiredAPI")},
{FUNC_BlueprintAuthorityOnly, TEXT("FUNC_BlueprintAuthorityOnly")},
{FUNC_BlueprintCosmetic, TEXT("FUNC_BlueprintCosmetic")},
{FUNC_Net, TEXT("FUNC_Net")},
{FUNC_NetReliable, TEXT("FUNC_NetReliable")},
{FUNC_NetRequest, TEXT("FUNC_NetRequest")},
{FUNC_Exec, TEXT("FUNC_Exec")},
{FUNC_Native, TEXT("FUNC_Native")},
{FUNC_Event, TEXT("FUNC_Event")},
{FUNC_NetResponse, TEXT("FUNC_NetResponse")},
{FUNC_Static, TEXT("FUNC_Static")},
{FUNC_NetMulticast, TEXT("FUNC_NetMulticast")},
{FUNC_UbergraphFunction, TEXT("FUNC_UbergraphFunction")},
{FUNC_MulticastDelegate, TEXT("FUNC_MulticastDelegate")},
{FUNC_Public, TEXT("FUNC_Public")},
{FUNC_Private, TEXT("FUNC_Private")},
{FUNC_Protected, TEXT("FUNC_Protected")},
{FUNC_Delegate, TEXT("FUNC_Delegate")},
{FUNC_NetServer, TEXT("FUNC_NetServer")},
{FUNC_HasOutParms, TEXT("FUNC_HasOutParms")},
{FUNC_HasDefaults, TEXT("FUNC_HasDefaults")},
{FUNC_NetClient, TEXT("FUNC_NetClient")},
{FUNC_DLLImport, TEXT("FUNC_DLLImport")},
{FUNC_BlueprintCallable, TEXT("FUNC_BlueprintCallable")},
{FUNC_BlueprintEvent, TEXT("FUNC_BlueprintEvent")},
{FUNC_BlueprintPure, TEXT("FUNC_BlueprintPure")},
{FUNC_EditorOnly, TEXT("FUNC_EditorOnly")},
{FUNC_Const, TEXT("FUNC_Const")},
{FUNC_NetValidate, TEXT("FUNC_NetValidate")},
{FUNC_AllFlags, TEXT("FUNC_AllFlags")},
};
FString FunctionFlagsStr;
for (auto& [Flag, Name] : FunctionFlags)
{
if (Function->HasAnyFunctionFlags(Flag))
{
FunctionFlagsStr = FString::Printf(TEXT("%s|%s"), *FunctionFlagsStr, Name);
}
}
return FunctionFlagsStr;
}
FString GetPropertyType(const FProperty* Property)
{
if (const FStructProperty* StructProperty = CastField<FStructProperty>(Property))
{
UStruct* PropertyStruct = StructProperty->Struct;
return FString::Printf(TEXT("F%s"), *PropertyStruct->GetName());
}
else if (const FClassProperty* ClassProperty = CastField<FClassProperty>(Property))
{
UClass* MetaClass = ClassProperty->MetaClass;
return FString::Printf(TEXT("TSubclassOf<U%s>"), *MetaClass->GetName());
}
else if (const FObjectProperty* ObjectProperty = CastField<FObjectProperty>(Property))
{
UClass* PropertyClass = ObjectProperty->PropertyClass;
return FString::Printf(TEXT("TObjectPtr<U%s>"), *PropertyClass->GetName());
}
else if (const FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Property))
{
FProperty* Inner = ArrayProperty->Inner;
return FString::Printf(TEXT("TArray<%s>"), *GetPropertyType(Inner));
}
else if (const FMapProperty* MapProperty = CastField<FMapProperty>(Property))
{
FProperty* KeyProp = MapProperty->KeyProp;
FProperty* ValueProp = MapProperty->ValueProp;
return FString::Printf(TEXT("TMap<%s, %s>"), *GetPropertyType(KeyProp), *GetPropertyType(ValueProp));
}
else if (const FSetProperty* SetProperty = CastField<FSetProperty>(Property))
{
FProperty* ElementProp = SetProperty->ElementProp;
return FString::Printf(TEXT("TSet<%s>"), *GetPropertyType(ElementProp));
}
return Property->GetCPPType();
};
void UMyEditorLibrary::AnalyzeBlueprint(UBlueprintGeneratedClass* InClass)
{
if (!IsValid(InClass))
{
return;
}
FStringBuilderBase SaveText;
SaveText.Appendf(TEXT("class U%s\r\n"), *InClass->GetName());
SaveText.Appendf(TEXT("\t: public %s\r\n"), *InClass->GetSuperStruct()->GetName());
SaveText.Append(TEXT("{\r\n"));
auto PrintPrppertys = [&SaveText](const UStruct* InStruct, EFieldIterationFlags Flags, const TCHAR* Indent)
{
for (FProperty* Property : TFieldRange<FProperty>(InStruct, Flags))
{
FString PropertyType = GetPropertyType(Property);
FText ToolTipText = Property->GetToolTipText();
if (!ToolTipText.IsEmpty())
{
SaveText.Appendf(TEXT("%s// %s\r\n"), Indent, *ToolTipText.ToString());
}
SaveText.Appendf(TEXT("%s// Flags=%s\r\n"), Indent, *LexToString(Property->GetFlags()));
SaveText.Appendf(TEXT("%s// PropertyFlags=%s\r\n"), Indent, *GetPropertyFlagsString(Property));
SaveText.Appendf(TEXT("%s%s %s;\r\n"), Indent, *PropertyType, *Property->GetName());
}
};
PrintPrppertys(InClass, EFieldIterationFlags::None, TEXT("\t"));
SaveText.Append(TEXT("\r\n"));
for (UFunction* Function : TFieldRange<UFunction>(InClass, EFieldIterationFlags::None))
{
FText ToolTipText = Function->GetToolTipText();
if (!ToolTipText.IsEmpty())
{
SaveText.Appendf(TEXT("\t// %s\r\n"), *ToolTipText.ToString());
}
SaveText.Appendf(TEXT("\t// FunctionFlags=%s\r\n"), *GetFunctionFlagsString(Function));
SaveText.Appendf(TEXT("\tauto %s()\r\n"), *Function->GetName());
SaveText.Appendf(TEXT("\t{\r\n"));
PrintPrppertys(Function, EFieldIterationFlags::Default, TEXT("\t\t"));
SaveText.Append(TEXT("\t};\r\n"));
SaveText.Append(TEXT("\r\n"));
}
SaveText.Appendf(TEXT("};\r\n"));
FString Dir = FPaths::ProjectSavedDir();
FString FullPath = Dir / FString::Printf(TEXT("%s.cpp"), *InClass->GetName());
// 書き込み
bool bSuccess = FFileHelper::SaveStringToFile(SaveText.ToView(), *FullPath, FFileHelper::EEncodingOptions::ForceUTF8);
GLog->Log(TEXT("---------------------------------------------------------------------"));
auto OnHyperlink = [FullPath]() { FPlatformProcess::ExploreFolder(*FullPath); };
FNotificationInfo Info(FText::FromString(TEXT("出力が完了しました")));
Info.FadeOutDuration = 0.2f;
Info.ExpireDuration = 5;
Info.bUseLargeFont = false;
Info.Hyperlink = FSimpleDelegate::CreateLambda(OnHyperlink);
Info.HyperlinkText = FText::FromString(FPaths::GetBaseFilename(FullPath));
FSlateNotificationManager::Get().AddNotification(Info);
}
出力例
変数+イベントディスパッチャーの生成例
変数やイベントディスパッチを出力して見た図
特に面白みはないです
class UBP_Obj_C
: public Object
{
// Flags=Public | LoadCompleted
// PropertyFlags=|CPF_Edit|CPF_BlueprintVisible|CPF_ZeroConstructor|CPF_DisableEditOnInstance|CPF_IsPlainOldData|CPF_NoDestructor|CPF_HasGetValueTypeHash
bool NewVar;
// Flags=Public | LoadCompleted
// PropertyFlags=|CPF_Edit|CPF_BlueprintVisible|CPF_ZeroConstructor|CPF_DisableEditOnInstance|CPF_IsPlainOldData|CPF_NoDestructor|CPF_HasGetValueTypeHash
int32 NewVar_0;
// Flags=Public | LoadCompleted
// PropertyFlags=|CPF_Edit|CPF_BlueprintVisible|CPF_ZeroConstructor|CPF_DisableEditOnInstance|CPF_BlueprintAssignable|CPF_BlueprintCallable
FNewEventDispatcher NewEventDispatcher;
// Flags=Public | LoadCompleted
// PropertyFlags=|CPF_Edit|CPF_BlueprintVisible|CPF_DisableEditOnInstance
TArray<FVector> NewVar_1;
// Flags=Public | LoadCompleted
// PropertyFlags=|CPF_Edit|CPF_BlueprintVisible|CPF_DisableEditOnTemplate|CPF_DisableEditOnInstance
TArray<TObjectPtr<UPawn>> NewVar_2;
// New Event Dispatcher Delegate Signature
// FunctionFlags=|FUNC_Public|FUNC_Delegate|FUNC_BlueprintCallable|FUNC_BlueprintEvent|FUNC_AllFlags
auto NewEventDispatcher__DelegateSignature()
{
};
};
関数の生成例
// Get Hoge
// FunctionFlags=|FUNC_Public|FUNC_HasOutParms|FUNC_HasDefaults|FUNC_BlueprintCallable|FUNC_BlueprintEvent|FUNC_AllFlags
auto GetHoge()
{
// Flags=Public
// PropertyFlags=|CPF_BlueprintVisible|CPF_BlueprintReadOnly|CPF_Parm|CPF_ZeroConstructor|CPF_HasGetValueTypeHash
FString NewParam1;
// Flags=Public
// PropertyFlags=|CPF_Parm|CPF_OutParm|CPF_ZeroConstructor|CPF_IsPlainOldData|CPF_NoDestructor|CPF_HasGetValueTypeHash
bool ResultsValue1;
// Flags=Public
// PropertyFlags=|CPF_Edit|CPF_BlueprintVisible|CPF_ZeroConstructor|CPF_HasGetValueTypeHash
FString NewLocalVar_0;
// Flags=Public
// PropertyFlags=|CPF_ZeroConstructor|CPF_IsPlainOldData|CPF_NoDestructor|CPF_HasGetValueTypeHash
int32 Temp_int_Loop_Counter_Variable;
// Flags=Public
// PropertyFlags=|CPF_ZeroConstructor|CPF_IsPlainOldData|CPF_NoDestructor|CPF_HasGetValueTypeHash
int32 CallFunc_Add_IntInt_ReturnValue;
// Flags=Public
// PropertyFlags=|CPF_ZeroConstructor|CPF_IsPlainOldData|CPF_NoDestructor|CPF_HasGetValueTypeHash
int32 Temp_int_Array_Index_Variable;
// Flags=Public
// PropertyFlags=|CPF_ReferenceParm
TArray<TObjectPtr<UPawn>> CallFunc_GetAllActorsOfClass_OutActors;
// Flags=Public
// PropertyFlags=|CPF_ZeroConstructor|CPF_NoDestructor|CPF_HasGetValueTypeHash
TObjectPtr<UPawn> CallFunc_Array_Get_Item;
// Flags=Public
// PropertyFlags=|CPF_ZeroConstructor|CPF_IsPlainOldData|CPF_NoDestructor|CPF_HasGetValueTypeHash
int32 CallFunc_Array_Length_ReturnValue;
// Flags=Public
// PropertyFlags=|CPF_ZeroConstructor|CPF_HasGetValueTypeHash
FString CallFunc_GetObjectName_ReturnValue;
// Flags=Public
// PropertyFlags=|CPF_ZeroConstructor|CPF_IsPlainOldData|CPF_NoDestructor|CPF_HasGetValueTypeHash
bool CallFunc_Less_IntInt_ReturnValue;
};
上記のように出力されます。
UFunction::ChildPropertiesのプロパティがUFunctionで関数を呼び出す度にアロケーションで生成されます。
関数が呼び出されるたびに、FFrame上に上の変数がアロケーションされて変数を使えるようになります
関数の戻り値毎にも変数が生成されるようです
イメージコード
auto GetHoge()
{
FGetHogeLocalVariables
{
...
};
// アロケーションする
FGetHogeLocalVariables* Variable = new FGetHogeLocalVariables();
Variable->CallFunc_GetAllActorsOfClass_OutActors = GetAllActorOfClass(APawn::StaticClass());
foreach( APawn* Pawn, int32 Index )
{
Variable->NewLocalVar_0 = Pawn->GetObjectName();
}
Variable->ResultsValue1 = true;
// 関数から抜けるときにDeleteされる
delete Variable;
}
イベントグラフの生成例
class UBP_Actor_C
: public Actor
{
// Flags=Public | LoadCompleted
// PropertyFlags=|CPF_ZeroConstructor|CPF_Transient|CPF_DuplicateTransient
FPointerToUberGraphFrame UberGraphFrame;
// Flags=Public | LoadCompleted
// PropertyFlags=|CPF_BlueprintVisible|CPF_ZeroConstructor|CPF_InstancedReference|CPF_NonTransactional|CPF_NoDestructor|CPF_HasGetValueTypeHash
TObjectPtr<USceneComponent> DefaultSceneRoot;
// Flags=Public | LoadCompleted
// PropertyFlags=|CPF_Edit|CPF_BlueprintVisible|CPF_ZeroConstructor|CPF_DisableEditOnTemplate|CPF_DisableEditOnInstance|CPF_NoDestructor|CPF_HasGetValueTypeHash
TObjectPtr<UPawn> Pawn;
// Event when play begins for this actor.
// FunctionFlags=|FUNC_Event|FUNC_Protected|FUNC_BlueprintEvent|FUNC_AllFlags
auto ReceiveBeginPlay()
{
};
// Execute Ubergraph BP Actor
// FunctionFlags=|FUNC_Final|FUNC_UbergraphFunction|FUNC_AllFlags
auto ExecuteUbergraph_BP_Actor()
{
// Flags=Public
// PropertyFlags=|CPF_BlueprintVisible|CPF_BlueprintReadOnly|CPF_Parm|CPF_ZeroConstructor|CPF_IsPlainOldData|CPF_NoDestructor|CPF_HasGetValueTypeHash
int32 EntryPoint;
// Flags=Public
// PropertyFlags=|CPF_ZeroConstructor|CPF_NoDestructor|CPF_HasGetValueTypeHash
TObjectPtr<UPawn> CallFunc_GetPlayerPawn_ReturnValue;
};
};
イベントグラフを定義すると、下記が生成されます。
- ExecuteUbergraph_ブループリントクラス名という関数
- FPointerToUberGraphFrame UberGraphFrame;というプロパティ
UBlueprintGeneratedClass::CreatePersistentUberGraphFrame経由で
UberGraphFrameの中に、UBlueprintGeneratedClass::UberGraphFunctionのプロパティをすべて作ります。
関数での実装とは違って、UberGraphFrameに関数のプロパティを常に持つのでイベントグラフが大きくなるほど、メモリ消費量が多くなります
イメージコード
class UBP_Actor_C
: public Actor
{
struct FPointerToUberGraphFrame
{
int32 EntryPoint;
TObjectPtr<UPawn> CallFunc_GetPlayerPawn_ReturnValue;
};
FPointerToUberGraphFrame UberGraphFrame;
TObjectPtr<USceneComponent> DefaultSceneRoot;
TObjectPtr<UPawn> Pawn;
auto ReceiveBeginPlay()
{
// イベントグラフを呼び出す
UberGraphFrame.EntryPoint = 1; // エントリーポイントを変える事で実行するノードが変わる
ExecuteUbergraph_BP_Actor();
};
auto ExecuteUbergraph_BP_Actor();
};


