|
使用UE4的开发同学一定对"*.generated.h"这个名字不陌生,Unreal定义了一系列的宏,来帮助开发者将自定义的字段和函数添加至反射系统,其他模块就可以利用这些反射信息来做很多事情,比如蓝图系统利用反射系统可以对标记为UPROPERTY的变量生成UI
UPROPERTY(Category = "Road", VisibleDefaultsOnly, BlueprintReadOnly)
FVector position;
但是这个生成UI的过程仅仅限于编辑器阶段,我们想在Runtime实现同等的效果,举例说明:
- 设计师在xxx.blueprint中定义了一些变量,编辑器节点会同步自动生成UI,并可以进行参数的调整
- 在Runtime阶段,希望能够同样生成对应的UI,并进行参数的调整,特别是在一些配置项的调整上
实际上我们需要解决三个问题:
- 获取到所有需要生成UI的变量
- 根据变量类型生成对应的Item
- 将UI的变更,映射到对应的变量值上
【本文假设读者已经对反射系统有所了解,并只记录其中的关键代码】
第一个问题
我们把所有的需要反射生成UI的变量放在一个category中,或者命名时都加个前缀等等,那么遍历UObject的FProperty,即可获取到所有想要的变量的名字和类型【放在一个枚举值中】
USTRUCT(BlueprintType)
struct FGPParameterInfo
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
EVariableTypes type;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString struct_member_name;
FGPParameterInfo() {}
FGPParameterInfo(EVariableTypes _type, FString _name) :type(_type), struct_member_name(_name) {}
};
TArray<FGPParameterInfo> UXXXXXXX::GetAllPropertiesInfoUnderCategory(UObject* inObject, const FString& category)
{
UClass* inClass = inObject->GetClass();
TArray<FGPParameterInfo> result;
for (TFieldIterator<FProperty> It(inClass, EFieldIteratorFlags::ExcludeSuper); It; ++It)
{
if (FProperty* StructMember= *It)
{
#if WITH_EDITORONLY_DATA
auto map = StructMember->GetMetaDataMap();
if (map && map->Contains(&#34;Category&#34;) && (*map)[&#34;Category&#34;] == category)
#endif
{
FGPParameterInfo info;
info.struct_member_name = StructMember->GetName();
FString type = StructMember->GetCPPType();
if (type == FString(&#34;float&#34;))
{
info.type = EVariableTypes::Float;
}
else if (type == FString(&#34;int32&#34;))
info.type = EVariableTypes::Int;
else if (type == FString(&#34;FString&#34;))
info.type = EVariableTypes::String;
else if (type == FString(&#34;FLinearColor&#34;))
info.type = EVariableTypes::LinearColor;
else if (type == FString(&#34;bool&#34;))
info.type = EVariableTypes::Bool;
else if (type == FString(&#34;UTexture2D*&#34;))
info.type = EVariableTypes::Texture2D;
else if (type == FString(&#34;TArray&#34;))
info.type = EVariableTypes::TArray;
else
{
//这里没有写完,只是当作示例
FStructProperty* StructProperty = Cast<FStructProperty>(StructMember);
if (StructProperty)
{
info.type = EVariableTypes::CustomStruct;
}
}
result.Add(info);
}
}
}
return result;
}
需要注意的是TArray<> 、TMap<>、TSet<>、Struct在Property这套系统中分别对应了Array、Map、Set、Struct等类型,并没有真正拿到其中变量的类型,这个在第三个问题取值和赋值的时候,我们一块贴一下代码
第二个问题
当我们拿到某个变量的名字和类型时,就可以根据类型匹配设计师预先制作好的UI Item,比如Float型就create 一个float对应的widget,以此类推,至于category以及其他小图标,设计师自己定义了一堆type和map来标记,这个不再赘述。
第三个问题
如何取值和赋值,先看基本类型的取值和赋值,以String为例
基本类型
void UXXXXX::SetGPPropertyString(UObject* object, const FString& paramName, FString bValue)
{
if (UClass* cl = object->GetClass())
{
if (FProperty* Property = cl->FindPropertyByName(*paramName))
{
if (FStrProperty* StrProperty = Cast<FStrProperty>(Property))
{
StrProperty ->SetPropertyValue(Property->ContainerPtrToValuePtr<void*>(object), bValue);
}
}
}
}
看起来比较简单,但是有个工程化的问题需要考虑,因为这个值要公开给蓝图,好像每个值都要定义取值和赋值函数,要写很多重复的代码,当然这里如果所有的UMG使用slate来制作,所有流程控制在c++也不至于这么复杂,我们这边由于工作流上的一些问题,是使用了C++ + UMG这套流程,正好后面要做RuntimeBP, 不可避免的要解决UMG中取值和赋值的过程,我这里的解决方案是利用union封装了一个结构体,根据type来取值就好,同理可以封装Array
// This stuct is used to have a dynamic variable depending on the Enum above this
USTRUCT(BlueprintType)
struct FNodeVarArgs
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
EVariableTypes m_VariableType;
NODE_VAR_ARG_UNION VariableData;
// Strings are messed up with TUnions so this variable is here to store string data, this also allows to use this string as a meta for other values
FString StringData = &#34;&#34;;
int GetSubtypeIndex() const
{
return VariableData.CurrentSubtypeIndex;
}
....
}
复杂类型
Map、Array、Set的获取变量类型
通常其Property内部都会记录一个变量对应的Property,比如FMapProperty中的KeyProp和ValueProp,那么再根据基本类型来处理就好
class COREUOBJECT_API FMapProperty : public FMapProperty_Super
{
DECLARE_FIELD(FMapProperty, FMapProperty_Super, CASTCLASS_FMapProperty)
// Properties representing the key type and value type of the contained pairs
FProperty* KeyProp;
FProperty* ValueProp;
FScriptMapLayout MapLayout;
EMapPropertyFlags MapFlags;
...
}Map、Array、Set的取值赋值
通常借助 FScriptXXXHelper 来操作,以Array为例
void Uxxxx::SetPropertyValuesOfArray(UObject* object, const FString& paramName, const FNodeVarArgsArray& value)
{
UClass* inClass = object->GetClass();
if (FProperty* Property = inClass->FindPropertyByName(*paramName))
{
if (UArrayProperty* ArrayProperty = Cast<UArrayProperty>(Property))
{
FProperty* innerProp = ArrayProperty->Inner;
void* StructValue = ArrayProperty->ContainerPtrToValuePtr<void>(object);
if (innerProp->IsA(UStrProperty::StaticClass()))
{
TArray<FString> ArrayOfStrings;
for (auto& arr : value.Array)
{
ArrayOfStrings.Add(arr.GetStringArg());
}
FScriptArrayHelper ArrayHelper(ArrayProperty, StructValue);
ArrayHelper.Resize(ArrayOfStrings.Num());
UStrProperty* TEMP = Cast<UStrProperty>(innerProp);
for (int i = 0; i < ArrayOfStrings.Num(); i++)
{
TEMP->SetPropertyValue(ArrayHelper.GetRawPtr(i), ArrayOfStrings);
}
}else if (innerProp->IsA(UFloatProperty::StaticClass()))
{
...
}
}
}
Struct获取变量类型
FStructProperty内部持有一个class UScriptStruct* Struct;遍历这个Struct中对应的Property即可,其实所有的复杂类型的取值、赋值、获取变量类型都应该是递归下去了,我们这里以Struct举例
//structValue: 该property对应的真正的值
//StructProperty: 对应的Property
// StructPtr:取值时,将取出的值放在这里;赋值时,就从此ptr中取值
//bIsSet: true表明取值,false表明赋值
// bNeedOutInfo: 是否需要输出变量类型
void IterateThroughStructProperty(void* structValue, UStructProperty* StructProperty, void* StructPtr,bool bIsSet/*true is set*/,bool bNeedOutInfo,TArray<FGPParameterInfo>& Info);
TArray<FGPParameterInfo> UDataVRuntimeBpLibrary::GetAllPropertiesOfStruct(UObject* structParent, const FString& structName)
{
TArray<FGPParameterInfo> result;
UClass* inClass = structParent->GetClass();
if (FProperty* Property = inClass->FindPropertyByName(*structName))
{
FStructProperty* StructProperty = Cast<FStructProperty>(Property);
void* StructValue = Property->ContainerPtrToValuePtr<void>(structParent);
IterateThroughStructProperty(StructValue, StructProperty, nullptr, false, true, result);
}
return result;
}
其中的IterateThroughStructProperty函数实现如下:
void ParseProperty(void* object, FProperty* Property, void* ValuePtr,bool bIsSet , bool bNeedOutInfo, TArray<FGPParameterInfo>& Info);
//structValue: 该property对应的真正的值
//StructProperty: 对应的Property
// StructPtr:取值时,将取出的值放在这里;赋值时,就从此ptr中取值
//bIsSet: true表明取值,false表明赋值
// bNeedOutInfo: 是否需要输出变量类型
void IterateThroughStructProperty(void* structValue, UStructProperty* StructProperty, void* StructPtr,bool bIsSet/*true is set*/,bool bNeedOutInfo,TArray<FGPParameterInfo>& Info)
{
// Walk the structs&#39; properties
UScriptStruct* Struct = StructProperty->Struct;
for (TFieldIterator<FProperty> It(Struct); It; ++It)
{
FProperty* 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<void>(StructPtr, ArrayIndex);
// Parse this property
ParseProperty(structValue,Property, ValuePtr, bIsSet,bNeedOutInfo,Info);
}
}
}
ParseProperty 可以说是上述基本类型的囊括版,在这里详细判断InProperty 有可能的所有类型,这里把enum单独列出来了,其写法稍微有所不同,如果这个InProperty的类型是Map、Array、Set、Struct将进行递归,这里我仅列出Array和Struct
void ParseProperty(void* PropertyStructValue, FProperty* InProperty, void* InOutValuePtr, bool bIsSet, bool bNeedOutInfo, TArray<FGPParameterInfo>& Info)
{
FString struct_member_name = InProperty->GetName();
// Here&#39;s how to read integer and float properties
if (UNumericProperty* NumericProperty = Cast<UNumericProperty>(InProperty))
{
if (NumericProperty->IsFloatingPoint())
{
if (bNeedOutInfo)
Info.Add({ EVariableTypes::Float, struct_member_name });
else
{
if (bIsSet)
{
FloatValue = NumericProperty->GetFloatingPointPropertyValue(InOutValuePtr);
void* valuePtrOfStruct = NumericProperty->ContainerPtrToValuePtr<void>(PropertyStructValue);
NumericProperty->SetFloatingPointPropertyValue(valuePtrOfStruct, FloatValue);
}
else
{
void* valuePtrOfStruct = NumericProperty->ContainerPtrToValuePtr<void>(PropertyStructValue);
FloatValue = NumericProperty->GetFloatingPointPropertyValue(valuePtrOfStruct);
NumericProperty->SetFloatingPointPropertyValue(InOutValuePtr, FloatValue);
}
}
}
else if (NumericProperty->IsInteger())
{
...
}
}
// How to read booleans
if (UBoolProperty* BoolProperty = Cast<UBoolProperty>(InProperty))
{
....
}
// Reading names
if (UNameProperty* NameProperty = Cast<UNameProperty>(InProperty))
{
,,,...
}
//Reading enums
if (UEnumProperty* EnumProperty = Cast<UEnumProperty>(InProperty))
{
if (bNeedOutInfo)
Info.Add({ EVariableTypes::Enum, struct_member_name }); else
{
if (bIsSet)
{
int64 EnumValue = EnumProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(InOutValuePtr);;
void* valuePtrOfStruct = EnumProperty->ContainerPtrToValuePtr<void>(PropertyStructValue);
EnumProperty->GetUnderlyingProperty()->SetIntPropertyValue(valuePtrOfStruct, EnumValue);
}
else
{
void* valuePtrOfStruct = EnumProperty->ContainerPtrToValuePtr<void>(PropertyStructValue);
int64 EnumValue = EnumProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(valuePtrOfStruct);
EnumProperty->GetUnderlyingProperty()->SetIntPropertyValue(InOutValuePtr, EnumValue);
}
}
}
// Reading strings
if (UStrProperty* StringProperty = Cast<UStrProperty>(InProperty))
{
...
}
// Reading texts
if (UTextProperty* TextProperty = Cast<UTextProperty>(InProperty))
{
...
}
// Reading an array
if (UArrayProperty* ArrayProperty = Cast<UArrayProperty>(InProperty))
{
// We need the helper to get to the items of the array
FScriptArrayHelper Helper(ArrayProperty, InOutValuePtr);
for (int32 i = 0, n = Helper.Num(); i < n; ++i)
{
ParseProperty(PropertyStructValue,ArrayProperty->Inner, Helper.GetRawPtr(i),bIsSet, bNeedOutInfo, Info);
}
}
// Reading a nested struct
if (UStructProperty* StructProperty = Cast<UStructProperty>(InProperty))
{
void* StructValue1 = StructProperty->ContainerPtrToValuePtr<void>(PropertyStructValue);
IterateThroughStructProperty(StructValue1,StructProperty, InOutValuePtr, bIsSet,bNeedOutInfo,Info);
}
}
到这里基本上就结束了,这么看下来,这块获取类型、取值和赋值对于简单类型和复杂类型并没有做特别好的封装,应该是可以写一个大一统的函数来完成所有类型信息的获取和取值赋值,这里我们仅简单记录思路和demo 代码,待后续整理。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|