赞
踩
前言:这大半年其实没有怎么更新博客,对于自己的博客其实大部分都是一些基础教学性质的文章,真正探索性质和比较深入的内容其实很多都并没有发表出来,我想以后单独出些教学视频或资料和大家交流
这篇文章是自己写工具时遇到的一个问题,由此而写。看到这篇文章就不用再看其他文章了,因为都没有这方面的资料
先看一张excel表导入到ue4的过程:
这是一张excel的数据,其中包含了字段名和数据
在ue4中我们也要创建一个结构体
在结构体中定义好和excel一致的字段和类型
在创建DataTable时选择该结构体,才能确定每一行Row的含义和类型
如果一个结构体删除一个字段,增加一个字段,那手动改结构体会不会很麻烦?如果有一千张excel表格都要导入到DataTable,岂不是要先定义好一千个结构体,如果哪个结构体有问题,策划要检查该excel对应的struct,岂不是增加工作成本?
因此使用ue4的反射机制,根据策划在excel中定义好的类型,在导入成DataTable时动态生成该结构的struct并生成DataTable,将每一行的数据插入进来,自动迁出提交到svn或者git,岂不美哉?
我们在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,并为这个结构体动态添加变量类型,这是另一个人给出的答案,但具体我没有具体测试过
这个不难,通过package生成DataTable,将这个DataTable的RowStruct指定为动态struct
包括了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中即可完成插入一行数据的任务
由于这个工具做了有一段时间,只是提到了重要的几个源码类,但探索时要看的类要比这多很多才能将许多细节了解清楚,对于ue4引擎来说,如果要实现一个功能自己却不知道是调用引擎的哪个方法时,如果没有目的的去找很容易迷失以至于丧失耐心
不妨先看ue4编辑器是怎么做的,然后再找源码的相关接口,这样有利于我们快速实现相关功能
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。