【UE4/5】利用反射系统写代码:根据函数名调用函数
背景:笔者在基于Unreal开发数字孪生开发平台时,为了方便设计师快速开发数字孪生应用,将一些常用的函数进行了封装,供设计师在Runtime通过可视化的形式进行使用;为了实现这个目的,特地开了一个大坑,做了一个Runtime的蓝图(为了与unreal的蓝图区分,下文简称RuntimeBP)。其中一项功能就是:
蓝图开发人员在unreal蓝图中开发一些Functions或者Event
根据名字,利用反射系统,找到对应的UFunction* 以及该Function对应的入参和出参的名字和类型RuntimeBP系统根据入参和出参的名字和类型,分别自动生成对应的pin脚,构成一个RuntimeBP节点
在RuntimeBP系统执行到该节点时,根据名字再次找到该UFunction,并且根据pin脚实时更新后的值执行该UFunction
其中主要要解决两个问题:
获取到UFunction*对应的入参和出参,这个比较简单,后面贴一下代码根据名字调用一个函数,参数的数量和类型是runtime时才知道的
第一个问题:
直接根据Function的NumParms和链表式的property链即可获取
UFunction* func = inClass->FindFunctionByName(FunctionFName);
if (func && (func->ParmsSize > 0))
{
int32 param_idx = 0;
FField* ChildProperty = func->ChildProperties;
TArray<FGPParameterInfo> infoList;
while (param_idx < func->NumParms)
{
FGPParameterInfo info;//记录了参数的名字和类型
GetPropertyInfo(Cast<FProperty>(ChildProperty), info);
ChildProperty = ChildProperty->Next;
infoList.Add(info);
param_idx++;
}
}
第二个问题:
参考 @大钊 的文章
根据名字调用UFunction的几种方式有:
定义一个结构体来包装参数和返回值:
struct MyClass_Func_Parms
{
float param1;
int32 ReturnValue;
};
UFunction* func = obj->FindFunctionChecked(functionName);
MyClass_Func_Parms params;
params.param1=param1;
obj->ProcessEvent(func, &params);2. 实现一个模板函数,大钊文章里给出了一个模板函数,很方便的可以在蓝图中调用,见上述文章链接
可以看到这两种方式用于实现我们的需求还是有一些棘手的,实际上我们可以根据pin脚拿到所有的变量值,却不好构造一个struct或者一个模板来快速方便的调用函数。
为了应对这个问题,我探索了两种方案来解决这个问题:
方案1:
分析一下方式1,实际上就是将参数组装成了一个结构体,并将这块内存传给ProcessEvent,那么我们能不能手动构造这块内存,来达到同样的效果呢?答案是可以的,这里我拿到pin脚的值后,直接将值赋给了Function中对应的Property,然后将所有参数对应的Property对应的内存拼接起来,当作ProcessEvent的参数,这样做有个好处就是自己不用去考虑内存对齐的问题,Property种已经含有了这个信息
代码如下:
TArray<uint8> ParamsBuffer;
for (int i = 0; i < func->NumParms; i++)
{
FProperty* Property = func->FindPropertyByName(FName(*InputPins.PinName));//根据pin脚的名字,找到对应的property
TArray<uint8> PropertyBuffer;
PropertyBuffer.SetNumZeroed(Property->GetSize());
switch (InputPins.VariableType)
{
case EVariableTypes::Bool:
{
auto result = GetInputValue(Index + i, Bool);//得到pin脚的value
PropertyBuffer = result;
break;
}
case EVariableTypes::Float:
{
auto result = GetInputValue(Index + i, Float);
FFloatProperty* FloatProperty = Cast<FFloatProperty>(Property);
void* valuePtrOfStruct = FloatProperty->ContainerPtrToValuePtr<void>(PropertyBuffer.GetData());
FloatProperty->SetPropertyValue(valuePtrOfStruct, result);
break;
}
case EVariableTypes::Int:
{
auto result = GetInputValue(Index + i, Int);
FIntProperty* IntProperty = Cast<FIntProperty>(Property);
void* valuePtrOfStruct = IntProperty->ContainerPtrToValuePtr<void>(PropertyBuffer.GetData());
IntProperty->SetPropertyValue(valuePtrOfStruct, result);
break;
}
...
}
ParamsBuffer.Append(PropertyBuffer);
}
owned_actor->ProcessEvent(func, ParamsBuffer.GetData());
方案2:
其实类似的需求非常的常见,如各种lua、js脚本语言调用UFunction,并传参过来,这里贴一段WebBrowser调用UFunction,将参数动态的组装起来的例子,其利用了Unreal自带的FStructDeserializer::Deserialize函数将反序列化后端IStructDeserializerBackend根据传入的struct类型信息,构造出一个指向要反序列化的结构的指针,这里UFunction本身就是继承自UStruct,直接当参数传入即可。
if (ParamsSize > 0)
{
// Convert cef argument list to a dictionary, so we can use FStructDeserializer to convert it for us
CefRefPtr<CefDictionaryValue> NamedArgs = CefDictionaryValue::Create();
int32 CurrentArg = 0;
CefRefPtr<CefListValue> CefArgs = MessageArguments->GetList(3);
for ( TFieldIterator<FProperty> It(Function); It; ++It )
{
FProperty* Param = *It;
if (Param->PropertyFlags & CPF_Parm)
{
if (Param->PropertyFlags & CPF_ReturnParm)
{
ReturnParam = Param;
}
else
{
FStructProperty *StructProperty = CastField<FStructProperty>(Param);
if (StructProperty && StructProperty->Struct->IsChildOf(FLancelotWebJSResponse::StaticStruct()))
{
PromiseParam = Param;
}
else
{
CopyContainerValue(NamedArgs, CefArgs, CefString(TCHAR_TO_WCHAR(*GetBindingName(Param))), CurrentArg);
CurrentArg++;
}
}
}
}
// UFunction is a subclass of UStruct, so we can treat the arguments as a struct for deserialization
Params.AddUninitialized(ParamsSize);
Function->InitializeStruct(Params.GetData());
FCEFJSStructDeserializerBackend Backend = FCEFJSStructDeserializerBackend(SharedThis(this), NamedArgs);
FStructDeserializer::Deserialize(Params.GetData(), *Function, Backend);
}
if (PromiseParam)
{
FLancelotWebJSResponse* PromisePtr = PromiseParam->ContainerPtrToValuePtr<FLancelotWebJSResponse>(Params.GetData());
if (PromisePtr)
{
*PromisePtr = FLancelotWebJSResponse(SharedThis(this), ResultCallbackId);
}
}
UE_LOG(LogTemp, Log, TEXT(&#34;FCEFJSScripting::HandleExecuteUObjectMethodMessage %s ready to start, Params.size = %d&#34;), *MethodName.ToString(), Params.Num());
Object->ProcessEvent(Function, Params.GetData());
最后,我觉得应该有更简单的方式来达到同样的需求,毕竟unreal 在编辑器中允许用户自定义struct,那么一定有方法组装strcut的成员变量,笔者源码阅读有限,还未找到对应的源码0.0,有更好的方法,还望不吝赐教~
【注:在做的平台是类似TwinMotion那种,经unreal开发, 但是用户们不会直接碰到unreal的开发,数字孪生应用又少不了编排各种逻辑,所以才诞生了runtimeBP这个想法,以提供更加简单便捷的节点给到设计师使用】 类似51的产品吧 超越51哈哈哈哈
页:
[1]