1. Overview

上一期讲述了CustomThunk方式实现泛型蓝图节点中前二部分,泛型蓝图节点的声明以及自定义Thunk函数体的方法,本期将继续介绍第三部分:泛型函数(Generic)的实现。虽然不同泛型蓝图节点的功能千差万别,泛型函数代码也可能迥然不同,但是ue4编写中泛型函数与编写一般的c++函数完全相同。接下来将结合具体实例(如下图所示的3个泛型蓝图节点),让大家更全面的了解泛型蓝图节点的实现过程。特别提醒:在查看本期内容之前,请先了解上一期的内容。

图1. 泛型蓝图节点;

2. Approach

2.1 Struct to Json String节点

功能:将任意类型结构体UStruct转换Json格式字符串

图2. UStruct to Json String 使用示例;

图3. UStruct to Json String 输出结果;

首先,在该模块的Build.cs文件中引入JsonUtilities,和Json模块,如下代码所示位置

PrivateDependencyModuleNames.AddRange(
	new string[]
	{
	"CoreUObject",
	"Engine",
	"Slate",
	"SlateCore",
	// ... add private dependencies that you statically link with here ...
        "JsonUtilities",
        "Json",
	}
);

由于UStruct to Json String的泛型函数,在源代码JsonObjectConverter.h中已经存在了,所以无需自己编写泛型函数,直接调用即可。

代码实现如下:

//h 文件,引入头文件 #include "JsonObjectConverter.h"

/**
	* Save any type of struct object to JSON format string, no struct type restriction
	*
	* @param	StructReference		The UStruct instance to read from
	* @return	JSON String		Json Object string to be filled in with data from the ustruct
*/
UFUNCTION(BlueprintPure, CustomThunk, meta = (CustomStructureParam = "StructReference", DisplayName = "Struct to JSON String"), Category = "File|Json")
	static void UStructToJsonObjectString(const int32& StructReference, FString& JSONString);

/// Custom execFunciton thunk for function UStructToJsonObjectString.
DECLARE_FUNCTION(execUStructToJsonObjectString)
{
	//Get input wildcard single variable
	Stack.Step(Stack.Object, NULL);
	UStructProperty* StructProperty = ExactCast<UStructProperty>(Stack.MostRecentProperty);
	void* StructPtr = Stack.MostRecentPropertyAddress;

	//Get JsonString reference
	P_GET_PROPERTY_REF(UStrProperty, JSONString);
	P_FINISH;

	P_NATIVE_BEGIN;
	FJsonObjectConverter::UStructToJsonObjectString(StructProperty->Struct, StructPtr, JSONString, 0, 0);
	P_NATIVE_END;
}

特别提醒:从UStruct to Json String 输出截图,可以看到ue4内置的 FJsonObjectConverter :: UStructToJsonObjectString()函数存在二个小问题:其一:结构体的变量名首字母被改成小写字母;其二:在蓝图中定义的结构体的变量名与转换后JSONString中的变量名存在明显差异,原因在于如下图所示的源代码第 261行。修复方法:可以用一个自定义函数获取VariableName,请自行修复。

图4. UStructToJsonObjectString问题源代码;

2.2 Is Valid Index节点

功能:判断TargetArray是否(length > 0)为空,若为非空(length>0),执行IsValid PIN执行流引脚;如为空(length==0),执行Is Not Valid PIN执行流引脚,等价于如下图所示的蓝图宏

图5. Is Valid节点等价蓝图宏;

图6. Is Valid节点使用示例;

图7. Is Valid节点输出Log;

由于Is Valid节点含有多个输出执行流引脚,需要使用"ExpandEnumAsExecs"说明符,将枚举类型的成员作为执行流引脚。

Is Valid泛型蓝图节点实现代码:

实现定义执行流引脚的枚举类型(.h文件)

// Expand Enum As Execs
UENUM()
enum class EEvaluateArray : uint8
{
	/** Array length > 0. */
	IsValid,
	/** Array length == 0. */
	IsNotValid
};

// .h文件,声明泛型蓝图节点,实现Thunk函数体

/*
	*Determines if an aray is valid(length > 0) ?
	*
	*@param	TargetArray		The array to get the length
*/
UFUNCTION(BlueprintCallable, CustomThunk, meta = (DisplayName = "Is Valid ?", ArrayParm = "TargetArray", Keywords = "num, size,valid",ExpandEnumAsExecs = "EvaluateArrayPIN", BlueprintThreadSafe), Category = "Utilities|Array")
	static void Array_Validv2(const TArray<int32>& TargetArray, EEvaluateArray& EvaluateArrayPIN);
static void GenericArray_Validv2(void* ArrayAddr, UArrayProperty* ArrayProperty, EEvaluateArray& EvaluateArrayPIN);

DECLARE_FUNCTION(execArray_Validv2)
{
	Stack.MostRecentProperty = nullptr;
	Stack.StepCompiledIn<UArrayProperty>(NULL);
	void* ArrayAddr = Stack.MostRecentPropertyAddress;
	UArrayProperty* ArrayProperty = Cast<UArrayProperty>(Stack.MostRecentProperty);
	if (!ArrayProperty)
	{
		Stack.bArrayContextFailed = true;
		return;
	}

	P_GET_ENUM_REF(EEvaluateArray, EvaluateArrayPIN);

	P_FINISH;

	P_NATIVE_BEGIN;
	GenericArray_Validv2(ArrayAddr, ArrayProperty, EvaluateArrayPIN);
	P_NATIVE_END;
}

// .cpp文件,实现泛型函数

void UMiscAlgorithm::GenericArray_Validv2(void* ArrayAddr, UArrayProperty* ArrayProperty, EEvaluateArray& EvaluateArrayPIN)
{
	// Determine whether the array length is greater than 0
	bool bValidArray = UKismetArrayLibrary::GenericArray_Length(ArrayAddr, ArrayProperty) > 0;

	if (bValidArray == true)
	{
		// Exec IsValid PIN
		EvaluateArrayPIN = EEvaluateArray::IsValid;
	}
	else
	{
		// Exec Is Not Valid PIN
		EvaluateArrayPIN = EEvaluateArray::IsNotValid;
	}
}

2.3 Filter Array节点

功能:根据在蓝图中自定义的筛选函数FilterBy,筛选数组成员

图8. Filter 泛型蓝图节点使用示例;

自定义筛选函数 FilterBy,必须为二个参数;第一个为输入参数,类型与数组类型相同;第二个为返回值,类型为bool,并命名为"ReturnValue"。如下图所示,筛选字符串数组中字符长度在5和8之间的字符串。

图9. 自定义筛选函数示例;

图10. Filter节点输入Log;

Filter节点实现代码如下:

// .h文件

/*
	*Filter an array based on filter function of object.
	*
	*@param	Object		The owner of function
	*@param	FilterBy	Filter function name, this custom function with 2 parameters,
						1 input (Type same as array member), 1 return named "ReturnValue"(bool)
	*@param	TargetArray	 The array to filter from
	*@return	An array containing only those members which meet the filterBy condition.
*/
UFUNCTION(BlueprintCallable, CustomThunk, meta = (DisplayName = "Filter Array", CompactNodeTitle = "Filter", ArrayParm = "TargetArray,FilteredArray", ArrayTypeDependentParams = "TargetArray,FilteredArray", AutoCreateRefTerm = "FilteredArray", DefaultToSelf = "Object", AdvancedDisplay = "Object"), Category = "Utilities|Array")
	static void Array_Filter(const UObject* Object, const FName FilterBy, const TArray<int32>& TargetArray, TArray<int32>& FilteredArray);
static void GenericArray_Filter(UObject* Object, UFunction* FilterFunction, const UArrayProperty* ArrayProp, void* SrcArrayAddr, void* FilterArrayAddr);

DECLARE_FUNCTION(execArray_Filter)
{
	P_GET_OBJECT(UObject, OwnerObject);
	P_GET_PROPERTY(UNameProperty, FilterBy);

	//Find filter function
	UFunction* const FilterFunction = OwnerObject->FindFunction(FilterBy);
	// Fitler function must have two parameters(1 input / 1 output)
	if (!FilterFunction || (FilterFunction->NumParms != 2))
	{
		UE_LOG(LogTemp, Warning, TEXT("Tooltip -> Array_Filter -> Please check filter function %s "), *FilterBy.ToString());
		return;
	}

	// Get target array  address and ArrayProperty
	Stack.MostRecentProperty = nullptr;
	Stack.StepCompiledIn<UArrayProperty>(NULL);
	void* SrcArrayAddr = Stack.MostRecentPropertyAddress;
	UArrayProperty* SrcArrayProperty = Cast<UArrayProperty>(Stack.MostRecentProperty);
	if (!SrcArrayProperty)
	{
		Stack.bArrayContextFailed = true;
		return;
	}

	// Get filtered array address and arrayproperty
	Stack.MostRecentProperty = nullptr;
	Stack.StepCompiledIn<UArrayProperty>(NULL);
	void* FilterArrayAddr = Stack.MostRecentPropertyAddress;
	UArrayProperty* FilterArrayProperty = Cast<UArrayProperty>(Stack.MostRecentProperty);
	if (!FilterArrayProperty)
	{
		Stack.bArrayContextFailed = true;
		return;
	}
	P_FINISH;

	P_NATIVE_BEGIN;
	// ScrArrayProperty is equal to FilterArrayProperty
	GenericArray_Filter(OwnerObject, FilterFunction, SrcArrayProperty, SrcArrayAddr, FilterArrayAddr);
	P_NATIVE_END;
}

// .cpp文件

void UMiscAlgorithm::GenericArray_Filter(UObject* Object, UFunction* FilterFunction, const UArrayProperty* ArrayProp, void* SrcArrayAddr, void* FilterArrayAddr)
{
	//check input parameters
	if (!Object || !FilterFunction || !SrcArrayAddr)
	{
		return;
	}
	// filter function return property
	UBoolProperty* ReturnProp = Cast<UBoolProperty>(FilterFunction->GetReturnProperty());
	if (!ReturnProp)
	{
		/// The return Property of filter function must be bool and named "ReturnValue"
		UE_LOG(LogTemp, Warning, TEXT("Tooltip -> GenericArray_Filter -> Pleas check return value of filter function.(Type:bool, Name:ReturnValue)"));
		return;
	}

	// Get function parameter list
	TArray<UProperty*> ParamterList;
	for (TFieldIterator<UProperty> It(FilterFunction); It; ++It)
	{
		UProperty* FuncParameter = *It;
		/// Get filter function parameters
		ParamterList.Emplace(FuncParameter);
	}
	/// Make sure the first input parameters of filter function is same to array inner
	if (!ParamterList[0]->SameType(ArrayProp->Inner))
	{
		/// The  property of 1st input parameter of filter function must be same as array member
		UE_LOG(LogTemp, Warning, TEXT("Tooltip -> GenericArray_Filter -> Pleas check input parameter of filter function.(Type is same as array member)"));
		return;
	}

	FScriptArrayHelper ArrayHelper(ArrayProp, SrcArrayAddr);
	FScriptArrayHelper FilterArray(ArrayProp, FilterArrayAddr);

	UProperty* InnerProp = ArrayProp->Inner;
	const int32 PropertySize = InnerProp->ElementSize * InnerProp->ArrayDim;
	// filter function parameters address, 1 input parameter(array item) and 1 return parameter (bool)
	uint8* FilterParamsAddr = (uint8*)FMemory::Malloc(PropertySize + 1);
	for (int32 i = 0; i < ArrayHelper.Num(); i++)
	{
		FMemory::Memzero(FilterParamsAddr, PropertySize + 1);
		// get array member and assign value to filter function input parameter
		InnerProp->CopyCompleteValueFromScriptVM(FilterParamsAddr, ArrayHelper.GetRawPtr(i));
		//process filter function
		Object->ProcessEvent(FilterFunction, FilterParamsAddr);
		if (ReturnProp && ReturnProp->GetPropertyValue(FilterParamsAddr + PropertySize))
		{
			// add item to filter array
			UKismetArrayLibrary::GenericArray_Add(FilterArrayAddr, ArrayProp, ArrayHelper.GetRawPtr(i));
		}
	}
	// relesed memory
	FMemory::Free(FilterParamsAddr);
}

以上节点源代码可见:https://github.com/xusjtuer/NoteUE4

3. Conclusion

总结:到目前为止,已经使用泛型蓝图节点实现了多种功能,第2期 任意类型数组排序SORT节点,第3期 Object任意类型属性的GET/SET节点,以及这一期介绍的3个节点:任意类型结构体转字符串StructToJsonObjectString节点、判断数组是否为空IsValid节点、以及数组筛选Filter节点。泛型蓝图节点更多的应用,需要在实践中不断发掘与探索。未来,自己也会尝试将一些泛型蓝图节点提交到引擎源码中去,也是为虚幻引擎做出贡献。

至此,基于CustomThunk的泛型蓝图节点实现过程已经全部讲完了,以上内容如觉有帮助,请不吝点赞;如觉无聊,可一带而过。