ue4 关于创建动态结构体的一点思考

前言:这大半年其实没有怎么更新博客,对于自己的博客其实大部分都是一些基础教学性质的文章,真正探索性质和比较深入的内容其实很多都并没有发表出来,我想以后单独出些教学视频或资料和大家交流

这篇文章是自己写工具时遇到的一个问题,由此而写。看到这篇文章就不用再看其他文章了,因为都没有这方面的资料

介绍下目前ue4的静态Struct和DataTable的关系

先看一张excel表导入到ue4的过程:

这是一张excel的数据,其中包含了字段名和数据

在ue4中我们也要创建一个结构体

在结构体中定义好和excel一致的字段和类型

在创建DataTable时选择该结构体,才能确定每一行Row的含义和类型

可是这样有什么缺点呢?

如果一个结构体删除一个字段,增加一个字段,那手动改结构体会不会很麻烦?如果有一千张excel表格都要导入到DataTable,岂不是要先定义好一千个结构体,如果哪个结构体有问题,策划要检查该excel对应的struct,岂不是增加工作成本?

因此使用ue4的反射机制,根据策划在excel中定义好的类型,在导入成DataTable时动态生成该结构的struct并生成DataTable,将每一行的数据插入进来,自动迁出提交到svn或者git,岂不美哉?

基于ue4反射机制生成的动态struct

我们在excel中的前五行插入元数据,真正的数据从start行开始到end行结束,在前五行的第三行是表示每一列的数据类型,我们在导入到ue4中时需要解析类型并动态创建结构体,为这个结构体添加每一列的类型即可

那么怎么做?怎么做?

我们知道ue4最大的弊端就是木有文档,资料太少,只能看源码,我们来看下ue4中创建一个struct调用的是哪个模块

通过断点发现,在点击structure的一瞬间会调用到FStructureEditorUtils::CreateUserDefinedStruct方法

大概看一眼FStructureEditorUtils,该类下封装了许多的函数,比如创建结构体,通过结构体获取到所有变量的desc,通过变量的GUID获取到FProperty等等

该Struct创建后会默认加一个bool类型的变量,生成的结构体是一个UUserDefinedStruct*

第一个参数是该资源的package,第二个是struct的名字,创建一个要生成路径的package,检查这个资源是否存在,如果存在,删除,否则生成这个结构体

接着要移除到默认创建出来的bool变量,通过源码可以发现对于获取每个变量是要拿到UUserDefinedStruct的FStructVariableDescription的数组,该数组保存着每个变量的具体描述,这个描述包括了是什么类型,GUID,名称等等信息,如下图这样获取到数组第一个变量的GUID,移除该变量即可

如何动态添加每种类型到这个结构体中?

遍历excel或csv文件的每一列时,获取其类型,通过源码可知该类型是个FName

如果是数组呢?对于是单个变量还是数组还是map,是EPinContainerType这个类型

在蓝图是这样显示的

只需将ContainerType设置为Array枚举类型即可

获取到每一列的类型和变量名后,为这个struct动态添加元素

这样一个动态struct就创建完毕了。

当然,也可以直接创建一个UScriptStruct,并为这个结构体动态添加变量类型,这是另一个人给出的答案,但具体我没有具体测试过

如何生成DataTable并将结构设置为这个动态struct

这个不难,通过package生成DataTable,将这个DataTable的RowStruct指定为动态struct

如何将excel或者csv的每一行数据写入到DataTable的每一行中?

包括了2个步骤

第一步,在DataTable中插入一条空的行

具体函数在FDataTableEditorUtils中封装,包括了插入一行,移动一行,选择一行,复制一行等接口

插入一空行即可

第二步,在该空行上填充有效数据

通过阅读源码可知,DataTable的每一行的RowName对应的具体数据是存放在RowMap中,获取插入的那一行的RowName并获取到改行的每一列的数据,用uint8*来保存

这个uint8*很容易感到困惑,比如一行中有三个类型int,double,bool,那么给这一行赋值应该是

int赋值是直接更改这个指针的值

double赋值应该是这个指针+4个字节后获得的地址取值后赋值

bool赋值应该是这个指针+8个字节后获得的地址取值后赋值

通过查看源码这个接口可知,如下图所示

在UScriptStruct::CopyScriptStruct方法中就是遍历结构体中的所有Fproperty,通过地址+索引*类型宽度的方式获取该变量的地址,重新给这个地址赋值,这也印证了之前的猜想

但是自己采用的另一个方法,这个方法是看了DataTable复制一行数据的接口获得的灵感,如下图所示这个方法

在ue4中DataTable有一个复制一行的所有数据到剪切板的方法

相对的,也有一个粘贴一行数据到DataTable的空行的方法

在其中有个UScriptStruct::ExportText方法,该方法是将某一行的所有变量读到一个字符串后,存放到剪切板上,代码如下:

void UScriptStruct::ExportText(FString& ValueStr, const void* Value, const void* Defaults, UObject* OwnerObject, int32 PortFlags, UObject* ExportRootScope, bool bAllowNativeOverride) const
{
	if (bAllowNativeOverride && StructFlags & STRUCT_ExportTextItemNative)
	{
		UScriptStruct::ICppStructOps* TheCppStructOps = GetCppStructOps();
		check(TheCppStructOps); // else should not have STRUCT_ExportTextItemNative
		if (TheCppStructOps->ExportTextItem(ValueStr, Value, Defaults, OwnerObject, PortFlags, ExportRootScope))
		{
			return;
		}
	}

	if (0 != (PortFlags & PPF_ExportCpp))
	{
		return;
	}

	int32 Count = 0;

	// if this struct is configured to be serialized as a unit, it must be exported as a unit as well.
	if ((StructFlags & STRUCT_Atomic) != 0)
	{
		// change Defaults to match Value so that ExportText always exports this item
		Defaults = Value;
	}

	for (TFieldIterator<FProperty> It(this); It; ++It)
	{
		if (It->ShouldPort(PortFlags))
		{
			for (int32 Index = 0; Index < It->ArrayDim; Index++)
			{
				FString InnerValue;
				if (It->ExportText_InContainer(Index, InnerValue, Value, Defaults, OwnerObject, PPF_Delimited | PortFlags, ExportRootScope))
				{
					Count++;
					if (Count == 1)
					{
						ValueStr += TEXT("(");
					}
					else
					{
						ValueStr += TEXT(",");
					}

					const FString PropertyName = (PortFlags & PPF_ExternalEditor) != 0 ? *It->GetAuthoredName() : It->GetName();

					if (It->ArrayDim == 1)
					{
						ValueStr += FString::Printf(TEXT("%s="), *PropertyName);
					}
					else
					{
						ValueStr += FString::Printf(TEXT("%s[%i]="), *PropertyName, Index);
					}
					ValueStr += InnerValue;
				}
			}
		}
	}

	if (Count > 0)
	{
		ValueStr += TEXT(")");
	}
	else
	{
		ValueStr += TEXT("()");
	}
}

我们也可以照猫画虎,将excel或者csv的所有变量构造成这样的一个字符串,然后再导入到RowStruct中即可完成插入一行数据的任务

如何将创建好的DataTable序列化到ue4引擎中生成uasset?

总结:

由于这个工具做了有一段时间,只是提到了重要的几个源码类,但探索时要看的类要比这多很多才能将许多细节了解清楚,对于ue4引擎来说,如果要实现一个功能自己却不知道是调用引擎的哪个方法时,如果没有目的的去找很容易迷失以至于丧失耐心

不妨先看ue4编辑器是怎么做的,然后再找源码的相关接口,这样有利于我们快速实现相关功能

猜你喜欢

转载自blog.csdn.net/zhangxiaofan666/article/details/112879891