模板是泛型编程的基础,即以一种独立于任何类型的方式编写代码。模板是创建一系列类(Class)或函数(Function)的通用标准或公式,在C++中有很多使用模板概念的例子。如:迭代器,向量等( vector 或 vector )
我们知道,在C++中定义一个普通函数模板并不难,如:
template
int compare(const T& left, const T& right) {
if (left < right) {
return -1;
}
if (right < left) {
return 1;
}
return 0;
}
compare(1, 2); //使用模板函数
以上代码定义了一个Compare的函数模板,可以支持多种类型的比较。
那么对于UE4而言,创建一个蓝图函数模板,以便于将C++泛型编程的思想运用于更多的场合是否可行呢?仔细分析,我们需要一个函数模板的原因,是因为我们需要一个能可以为任意类型的参数,即一个通用结构。那么问题就变成了如何创建一个可被蓝图调用的函数,该函数至少接收一个可能为任意类型的参数,即所谓的wildcard structs。
如何创建一个可被蓝图调用的函数?不会的要被打手手喔~
要创建一个wildcard struct,需要我们手动解析该Struct,即UProperty,这里额外补充一个各个属性的继承结构,如图:
详情见官方文档(https://api.unrealengine.com/INT/API/Runtime/CoreUObject/UObject/UField/index.html)
其中蓝图使用的结构为UScriptStruct,也就是说如果你是通过蓝图创建的结构,该结构的基类就是UScriptStruct。UScriptStruct比较折磨人的一点是在它生成的key后面带了一大串自动生成的后缀,好像是以"_"拼接的(感兴趣的可以自己去看看这个磨人的小妖精什么样儿~),因此在使用本结构搞事情的时候,如果没有得到意想中的结果,可往这方面调试。
手动解析UProperty需要在声明函数时添加宏 CustomThunk ,阻止UBT自动生成解析代码,还应添加 CustomStructureParam 表明我们为此函数添加手动解析,声明代码如下:
// 仅有一个通用变量
UFUNCTION(BlueprintCallable, Category = "TemplateSample", CustomThunk, meta = (CustomStructureParam = "AnyStruct"))
static void ReceiveAnyStruct(UProperty* AnyStruct);
我们对UStructProperty进行解析,解析代码如下:
//BlueprintFunctionTemplateSample.h
DECLARE_FUNCTION(execReceiveAnyStruct)
{
// Steps into the stack, walking to the next property in it
Stack.Step(Stack.Object, NULL);
// Grab the last property found when we walked the stack
// This does not contains the property value, only its type information
UStructProperty* StructProperty = ExactCast(Stack.MostRecentProperty);
// Grab the base address where the struct actually stores its data
// This is where the property value is truly stored
void* StructPtr = Stack.MostRecentPropertyAddress;
// We need this to wrap up the stack
P_FINISH;
// Iterate through the struct
IterateThroughStructProperty(StructProperty, StructPtr);
}
static void IterateThroughStructProperty(UStructProperty* StructProperty, void* StructPtr);
static void ParseProperty(UProperty* Property, void* ValuePtr);
//BlueprintFunctionTemplateSample.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "BlueprintFunctionTemplateSample.h"
DEFINE_LOG_CATEGORY(BlueprintFunctionTemplate);
void UBlueprintFunctionTemplateSample::IterateThroughStructProperty(UStructProperty* StructProperty, void* StructPtr)
{
// Walk the structs' properties
if (StructProperty == nullptr)
{
return;
}
UScriptStruct* Struct = StructProperty->Struct;
for (TFieldIterator It(Struct); It; ++It)
{
UProperty* Property = *It;
// This is the variable name if you need it
FString VariableName = Property->GetName();
// Never assume ArrayDim is always 1
for (int32 ArrayIndex = 0; ArrayIndex < Property->ArrayDim; ArrayIndex++)
{
// This grabs the pointer to where the property value is stored
void* ValuePtr = Property->ContainerPtrToValuePtr(StructPtr, ArrayIndex);
// Parse this property
ParseProperty(Property, ValuePtr);
}
}
}
void UBlueprintFunctionTemplateSample::ParseProperty(UProperty* Property, void* ValuePtr)
{
float FloatValue;
int32 IntValue;
bool BoolValue;
FString StringValue;
FName NameValue;
FText TextValue;
FString PropertyName;
Property->GetName(PropertyName);
// Here's how to read integer and float properties
if (UNumericProperty* NumericProperty = Cast(Property))
{
if (NumericProperty->IsFloatingPoint())
{
FloatValue = NumericProperty->GetFloatingPointPropertyValue(ValuePtr);
UE_LOG(BlueprintFunctionTemplate, Warning, TEXT("%s : %d"), *PropertyName, FloatValue);
}
else if (NumericProperty->IsInteger())
{
IntValue = NumericProperty->GetSignedIntPropertyValue(ValuePtr);
UE_LOG(BlueprintFunctionTemplate, Warning, TEXT("%s : %d"), *PropertyName, IntValue);
}
}
// How to read booleans
if (UBoolProperty* BoolProperty = Cast(Property))
{
BoolValue = BoolProperty->GetPropertyValue(ValuePtr);
UE_LOG(BlueprintFunctionTemplate, Warning, TEXT("%s : %s"), *PropertyName, BoolValue ? "True" : "False");
}
// Reading names
if (UNameProperty* NameProperty = Cast(Property))
{
NameValue = NameProperty->GetPropertyValue(ValuePtr);
UE_LOG(BlueprintFunctionTemplate, Warning, TEXT("%s : %s"), *PropertyName, *(NameValue.ToString()));
}
// Reading strings
if (UStrProperty* StringProperty = Cast(Property))
{
StringValue = StringProperty->GetPropertyValue(ValuePtr);
UE_LOG(BlueprintFunctionTemplate, Warning, TEXT("%s : %s"), *PropertyName, *StringValue);
}
// Reading texts
if (UTextProperty* TextProperty = Cast(Property))
{
TextValue = TextProperty->GetPropertyValue(ValuePtr);
UE_LOG(BlueprintFunctionTemplate, Warning, TEXT("%s : %s"), *PropertyName, *(TextValue.ToString()));
}
// Reading an array
if (UArrayProperty* ArrayProperty = Cast(Property))
{
// We need the helper to get to the items of the array
FScriptArrayHelper Helper(ArrayProperty, ValuePtr);
for (int32 i = 0, n = Helper.Num(); i < n; ++i)
{
ParseProperty(ArrayProperty->Inner, Helper.GetRawPtr(i));
}
}
// Reading a nested struct
if (UStructProperty* StructProperty = Cast(Property))
{
IterateThroughStructProperty(StructProperty, ValuePtr);
}
}
注,我们这里是对结构体进行解析哈,输入基本类型是不可以的~
输出结果:
其实自动解析在JSON自动解析那一节已经用到了,感兴趣的同学可以翻阅之前的文章。
如果有需要源码的同学可以私我~