Bootstrap

UE中使用ProcessEvent实现C++对蓝图资源跨插件的函数调用

工作中经常需要调用美术同学制作的蓝图资产。当两边插件互不引用时,美术蓝图不能继承到程序的c++基类,这时用ProcessEvent实现对蓝图function的调用。

简单案例如下

首先定义一个存放传递目标函数信息的类,我们需要函数类型、参数类型这两个最基本的信息

struct FFuncContext

{

UFunction* Function;

TArray<FProperty*> InProperties;

};

我们要声明一个函数,根据传入目标蓝图函数的名称找到要调用的函数的信息

bool UFuncCallBPLibrary::GetFunctionContext(UClass* Class, const FName& FunctionName, FFuncContext& OutFunctionCtx)
{
	if (Class == nullptr) { return false; }

	UFunction* Function = Class->FindFunctionByName(FunctionName);

	if (!Function) { return false; }

	OutFunctionCtx.Function = Function; 

	for (TFieldIterator<FProperty> It(Function); It; ++It)
	{
		FProperty* Property = *It;

		if (!Property->HasAnyPropertyFlags(EPropertyFlags::CPF_Parm))
		{
			continue;
		}

		if (const bool IsInputParam = !Property->HasAnyPropertyFlags(EPropertyFlags::CPF_OutParm))
		{
			OutFunctionCtx.InProperties.Add(Property);
		} 
	}

	return true;
}

我们还需要一个函数把需要传递的参数存放入一个容器中,这里把参数转为json结构体再放入内存

void UFuncCallBPLibrary::SetContainer(const TArray<FProperty*>& Properties, void* Container, const TMap<FString, FString>& KeyValues)
{
	TSharedPtr<FJsonObject> Json = MakeShareable(new FJsonObject);
	for (const auto& KeyValue : KeyValues)
	{
		Json->SetStringField(KeyValue.Key, KeyValue.Value);
	}

	auto Values = Json->Values;

	for (FProperty* Property : Properties)
	{
		FString KeyName = Property->GetAuthoredName();

		const auto* Ptr = Values.Find(KeyName);
		if (!Ptr) { continue; }

		FJsonObjectConverter::JsonValueToUProperty(*Ptr, Property, Property->ContainerPtrToValuePtr<void>(Container), 0, 0);
	}
}

最后我们可以通过遍历场景中的Actor,传入函数名称和参数的TMap列表,对包含我们目标函数的所有Actor进行调用

void UFuncCallBPLibrary::FuncCall(AActor* InActor, FName FuncName, const TMap<FString, FString>& InKeyValues)
{
	if (!InActor)  return;

	UClass* Class = InActor->GetClass();

	FFuncContext FunctionCtx;
	//找到目标函数的类和参数的类
	if (GetFunctionContext(Class, FuncName, FunctionCtx))
	{
		//分配一块内存存放参数信息,使用独占指针避免内存泄漏
		TUniquePtr<uint8[]> Container;
		Container.Reset(new uint8[FunctionCtx.Function->ParmsSize]);

		for (FProperty* Property : FunctionCtx.InProperties)
		{
			Property->InitializeValue_InContainer(Container.Get());
		}

		const auto NumInProperties = FunctionCtx.InProperties.Num();
		if (NumInProperties > 1)
		{
			SetContainer(FunctionCtx.InProperties, Container.Get(), InKeyValues);
			InActor->ProcessEvent(FunctionCtx.Function, Container.Get());
		}
	}
}

最后创建一个目标函数SampleFunction测试调用功能

找到场景中Actor调用

	TArray<AActor*> FoundActors;
	UGameplayStatics::GetAllActorsWithTag(GetWorld(), FName(*TragetId), FoundActors);
	TMap<FString, FString> InKeyValues; 
	InKeyValues.Add("val1", "true");
	InKeyValues.Add("val2", "3.1415");
	InKeyValues.Add("val3", "received");
	for (AActor* Actor : FoundActors)
	{
		if (Actor)
		{			
	        FuncCall(Actor, FName("SampleFunction"), InKeyValues);
		}
	}

调用成功

;