UE4 基于CustomThunk的泛型蓝图节点语法规范 (二)
1. Overview
上一期讲述了CustomThunk方式实现泛型蓝图节点中前二部分,泛型蓝图节点的声明以及自定义Thunk函数体的方法,本期将继续介绍第三部分:泛型函数(Generic)的实现。虽然不同泛型蓝图节点的功能千差万别,泛型函数代码也可能迥然不同,但是ue4编写中泛型函数与编写一般的c++函数完全相同。接下来将结合具体实例(如下图所示的3个泛型蓝图节点),让大家更全面的了解泛型蓝图节点的实现过程。特别提醒:在查看本期内容之前,请先了解上一期的内容。
2. Approach
2.1 Struct to Json String节点
功能:将任意类型结构体UStruct转换Json格式字符串
首先,在该模块的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,请自行修复。
2.2 Is Valid Index节点
功能:判断TargetArray是否(length > 0)为空,若为非空(length>0),执行IsValid PIN执行流引脚;如为空(length==0),执行Is Not Valid PIN执行流引脚,等价于如下图所示的蓝图宏
由于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,筛选数组成员
自定义筛选函数 FilterBy,必须为二个参数;第一个为输入参数,类型与数组类型相同;第二个为返回值,类型为bool,并命名为"ReturnValue"。如下图所示,筛选字符串数组中字符长度在5和8之间的字符串。
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的泛型蓝图节点实现过程已经全部讲完了,以上内容如觉有帮助,请不吝点赞;如觉无聊,可一带而过。