蓝图模板函数.md 5.4 KB

CustomChunk

在计算机编程中,Thunk 是一个特殊的函数,其主要目的是延迟计算或调用,将一个多参数函数转换为一个单参数的延迟执行版本

UnrealThunk 用于将蓝图中的事件或者函数调用转换为 C++ 中的函数调用。 Thunk 函数充当中间层,确保蓝图和 C++ 之间的通信和数据传递能够正常运行

CustomThunk 顾名思义就是自定义 Thunk 函数,用于告诉引擎这个函数不是自动生成的,而是开发者提供的

这种自定义的 Thunk 函数允许开发者直接与蓝图虚拟机的堆栈进行交互,从而可以处理更复杂的逻辑,比如动态参数类型的处理、泛型编程或者在运行时进行特定的内存操作

CustomThunk 定义的函数,通常需要通过 DECLARE_FUNCTION 宏中以 exec 前缀来声明。这个 exec 是需要遵守的约定,因为 Unreal 的蓝图虚拟机在处理函数调用时,会查找以 exec 为前缀的函数来执行,相当于引擎内部的约定

声明 CustomThunk 函数需要使用 DECLARE_FUNCTION,定义函数使用宏 DEFINE_FUNCTION

// .h 文件
UFUNCTION(BlueprintCallable, CustomThunk, Category = "Utilities|Instanced Struct", meta = (CustomStructureParam = "Value", BlueprintInternalUseOnly="true", NativeMakeFunc))
static FInstancedStruct MakeInstancedStruct(const int32& Value);
DECLARE_FUNCTION(execMakeInstancedStruct);

// .cpp 文件
FInstancedStruct UStructUtilsFunctionLibrary::MakeInstancedStruct(const int32& Value)
{
	// We should never hit this! stubs to avoid NoExport on the class.
	checkNoEntry();
	return {};
}

DEFINE_FUNCTION(UStructUtilsFunctionLibrary::execMakeInstancedStruct)
{
    // 
}

CustomStructureParam 允许名为 Value 的参数接受多种不同类型的数据 AutoCreateRefTerm 会自动为标记为通配符的结构体参数创建引用术语

通过蓝图虚拟机的堆栈可以获得函数参数等信息

DEFINE_FUNCTION(UStructUtilsFunctionLibrary::execMakeInstancedStruct)
{	
	Stack.MostRecentPropertyAddress = nullptr;	
	Stack.MostRecentPropertyContainer = nullptr;
	Stack.StepCompiledIn<FStructProperty>(nullptr);	// 从蓝图虚拟机的堆栈中获取下一个 FStructProperty 类型的属性,这通常是函数的参数
	
	const FStructProperty* ValueProp = CastField<FStructProperty>(Stack.MostRecentProperty);	// 尝试将获取到的属性转换为 FStructProperty 类型,这代表结构体
	const void* ValuePtr = Stack.MostRecentPropertyAddress;			// 获取目标结构体的地址

	// ...
}

如果函数存在多个参数,那就需要多次调用 Stack.StepCompiledIn 直到参数全部获取,当然也 Unreal 也提供了一些宏来方便的获取参数

UAnimationAttributeBlueprintLibrary::execSetAttributeKey 为例

UFUNCTION(BlueprintCallable, CustomThunk, Category = "Animation|Attributes", meta=(CustomStructureParam="Value", BlueprintInternalUseOnly="true"))
static bool SetAttributeKey(TScriptInterface<IAnimationDataController> AnimationDataController, const FAnimationAttributeIdentifier& AttributeIdentifier, float Time, const int32& Value);

DEFINE_FUNCTION(UAnimationAttributeBlueprintLibrary::execSetAttributeKey)
{
	P_GET_TINTERFACE(IAnimationDataController, AnimationDataController);
	P_GET_STRUCT_REF(FAnimationAttributeIdentifier, AttributeIdentifier);
	P_GET_PROPERTY(FFloatProperty, TimeInterval);

	Stack.MostRecentProperty = nullptr;
	Stack.MostRecentPropertyAddress = nullptr;
	
	Stack.StepCompiledIn<FStructProperty>(nullptr);
	
	const FStructProperty* ItemProperty = CastField<FStructProperty>(Stack.MostRecentProperty);
	void* ItemDataPtr = Stack.MostRecentPropertyAddress;

	// ...
}

由于函数的前几个参数明确了类型 IAnimationDataControllerFAnimationAttributeIdentifierfloat,分别使用 P_GET_TINTERFACEP_GET_STRUCT_REFP_GET_PROPERTY 来获取对应的数据

P_GET_TINTERFACE 获取接口(Interface)类型属性 P_GET_STRUCT_REF 获取结构体(STRUCT)类型属性

最后一个传递的是 CustomStructureParam 标记的参数,可以接受任意类型,这里期望的是一个结构体,所以使用 FStructProperty 来接受,由于提前不知道结构体的类型,所以不能使用 P_GET_STRUCT_REF

根据 FStructProperty 可以得到结构体的属性列表和值,进而可以根据需要进行后续操作

DEFINE_FUNCTION(UJsonBlueprintFunctionLibrary::execGetField)
{
	P_GET_STRUCT_REF(FJsonObjectWrapper, JsonObject);
	P_GET_PROPERTY(FStrProperty, FieldName);

	Stack.StepCompiledIn<FProperty>(nullptr);
	FProperty* ValueProp = Stack.MostRecentProperty;
	void* ValuePtr = Stack.MostRecentPropertyAddress;

	P_FINISH;

	if (!ValueProp || !ValuePtr)
	{
		// 错误警告
	}

	bool bResult;
	if (FieldName.IsEmpty())
	{
		// 特殊处理
	}

	P_NATIVE_BEGIN
	bResult = JsonFieldToProperty(FieldName, JsonObject, ValueProp, ValuePtr);
	P_NATIVE_END

	*static_cast<bool*>(RESULT_PARAM) = bResult;
}

继续以 execMakeInstancedStruct 为例,需要注意的是 P_FINISHP_NATIVE_BEGINP_NATIVE_ENDRESULT_PARAM

  • P_FINISH: 宏之前为获取函数参数的代码块
  • P_NATIVE_BEGINP_NATIVE_END : 宏包裹着真正执行该函数功能的被调用函数,若是泛型蓝图节点,此处调用该函数的泛型版本(泛型函数)
  • RESULT_PARAM: 表示函数返回值