IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 【UE4/5】利用反射系统Runtime自动生成UI -> 正文阅读

[游戏开发]【UE4/5】利用反射系统Runtime自动生成UI

使用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("Category") && (*map)["Category"] == category)
#endif
			{
				FGPParameterInfo info;
				info.struct_member_name = StructMember->GetName();
				FString type = StructMember->GetCPPType();
				if (type == FString("float"))
				{
					info.type = EVariableTypes::Float;
				}
				else if (type == FString("int32"))
					info.type = EVariableTypes::Int;
				else if (type == FString("FString"))
					info.type = EVariableTypes::String;
				else if (type == FString("FLinearColor"))
					info.type = EVariableTypes::LinearColor;
				else if (type == FString("bool"))
					info.type = EVariableTypes::Bool;
				else if (type == FString("UTexture2D*"))
					info.type = EVariableTypes::Texture2D;
				else if (type == FString("TArray"))
					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 = "";

	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[i]);
				}
			}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' 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'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 代码,待后续整理。

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-09-12 13:27:58  更:2021-09-12 13:28:54 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/17 11:50:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码