1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

UnrealEngineのBlueprintをリフレクションで解析してみる!

1
Posted at

概要

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);

}

出力例

変数+イベントディスパッチャーの生成例

変数やイベントディスパッチを出力して見た図
特に面白みはないです

image.png

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()
	{
	};
};

関数の生成例

image.png

	// 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;
}


イベントグラフの生成例

image.png

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();
};
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?