虚幻4随笔6 Object和序列化

诚如以前所说,虚幻4主要的一些特性都是由UObject穿针引线在一块儿的,想把虚幻玩到比较深的程度,UObject是早晚要面对、回避不得的问题,因此,准备在其它主题以前,先把UObject好好弄一下。UObject主要完成了哪些工做呢?私觉得: 编程

 

反射系统

 

UObject体系构建了整个虚幻反射系统的核心,每一个UObject都来自于一个UClass,这个Class能够是Unreal Header Tool(之后统一遵循官网命名:UHT)生成的,也能够是来自于Blueprint生成的(UBlueprintGeneratedClass)。反射能够说是如今主流引擎的构建基础,对国内多数人而言,可能更熟悉的是Unity透过Mono构建出来的反射,它的重要性不言而喻。 网络

反射很大一坨的东西,具体就不说了,它最大的做用,至关于在运行时动态生成代码,能够省掉不少手写代码的工做量。不然像UE这样复杂的界面,所有Hardcode,100人是绝对不够的,改一次所需的时间也是没法接受的。有了反射以后,剩下的不少就是很好理解的一条路就顺下来了:属性编辑器自动生成、自动消息包收发、自动序列化、自动生成BP节点、BP和C++的自动接口交互、自动浅拷贝深拷贝、甚至按照设定规则来进行拷贝……不胜枚举。 多线程

共通性都是同样:Get Class,Get Property,或者Get Function,分析Property和Function的属性,而后,设值、获取值、Invoke函数…… 异步

 

垃圾回收和生命期管理

 

UObject构建了虚幻的垃圾回收(GC)系统。GC这东西众说纷纭,但博主本人持乐观态度。最近的公司业务就遇到这么个事儿:脚本里须要发动态包,因而就须要在脚本里手动生成一个动态包,并挂接在包上面。为了完成这个目的,我就必须在脚本中制做一个生成动态包的节点,而后问题来了,咱们必须还得要一个回收动态包的节点,不然这个动态包就没法回收……因而最后放弃了,回到了包里加各类Reserved的老路上去……有了GC,多数状况下都不用管这事儿……你让一个策划去理解回收这种事情,就是在给他们添麻烦,那本不是他们业务内的范畴。 编辑器

GC的存在价值,并不是是让事情变得简单这么简单,更多时候它能让你节省下不少编程心力,把精力花在真正该关注的地方。真GC出问题时去查错所带来的成本,未必比忘写delete带来的成本要大,说不定反而更小。 函数

虚幻的垃圾回收系统,基本上就是从Root开始,不断遍历全部的Property,标记其为使用中。最后再遍历一遍,确认哪些Object没有标使用中就给它删掉。基本上,你不须要管这个过程,由于反射的做用,因此相关的信息都是UE自动就帮咱们处理好的。有几个要注意的: spa

"Singleton",须要一直存在的,直接AddToRoot。 线程

F类自己是不走垃圾回收的,可是F 类内部又有U类,这种状况下你须要注意AddReferencedObjects。把F类内部的U类给加入到GC树上。 code

Classes里的类,标UPROPERTY的UObject属性会被自动加入到当前类的GC列表里,但不标的会不会,没有具体跟,反正习惯随手写个就好了。 对象

TArray和TMap里面的UObject会被自动加入GC列表,可是若是写的是std::vector和std::map,则应该是不会的,须要手动用AddReferencedObjects加进去。

 

资源管理

 

UObject最后一个做用是构建了虚幻的资源管理体系。包括资源的搜索、资源之间的引用管理,下面详细展开。

首先要先说一下虚幻的Object命名,因为资源也都是UObject,因此其命名与UObject是同一个标准。按照如今的要求,是[类名']路径名/路径名/Asset名.[包内路径.]Object本名:[属性名]['](通常是Object所在类名+一个数字后缀)。好比:

Brush'/Script/Engine.Default__Brush'

BillboardComponent'/Script/Engine.Default__TextRenderActor:Sprite'

/Engine/TemplateResources/MI_Template_BaseGray_03_Metal.MI_Template_BaseGray_03_Metal

这个名称解析跟UE3和UDK略有不一样,UE3因为基于upk来对包进行管理,而又限制Content文件夹下的Upk包不能重名,因此不须要前面的路径名。UE4基于Asset,Content不一样子目录下能够有同名UAsset,因此路径名就是不可或缺的了。除此以外,Asset跟UPK没有太多不一样,咱们后面说包,也是指的UAsset,虽然看起来这个不像包。

理论上,全部的UObject均可以交由StaticLoadObject来加载(事实上也确实是这么作的),可是不少类是有基于LoadObject的特殊实现的,好比UClass(Blueprint Class),就必须用StaticLoadClass,而地图必须使用LoadMap,LevelStreaming这样地图相关的加载流程。这些变种的主要区别是会针对相应的状况作一些特殊的处理和操做。可是核心都绕不开StaticLoadObject。因此搞明白这个StaticLoadObject,实际上就搞明白了虚幻主要的资源组织结构。

主要的流程以下:

解析路径,找到对应包(UAsset或者UPK),若是还没加载则加载包。

判断Object是否已经加载,若是已经加载则直接返回。

对资源包的加载,会把整个资源包的全部Object所有预加载的(建立并调用PreLoad,对于资源等须要PostLoad的调用PostLoad)。同时,加载包时建立ULinkerLoad,这个LinkerLoad会自动分析每一个包与其余包之间的关联,经过Imports来记录本包对其它包的引用,经过Exports记录本包内的Object。

加载后,看看目标对象是否是个Redirector,若是是Redirector,则说明"曾经有个包在这里,可是被移动到新地方去了",就重定位到必要的地方。

说了这么多,其实你明白原理就很简单:虚幻全部对象都是按照一个包含了路径、包内路径、对象类名的惟一名称来命名的。而虚幻全部的包都是会记录对其它包的引用的。

因此一旦一个包的路径、对象的包内路径、以及对象类名自己发生变化,均可能会致使旧有资源的丢失和重定向。

固然,相关也都有一些机制能够帮助你过后修正(好比Redirector,Engine.ini里的Redirector config),可是,那都是补救措施,不能100%保证成功补救。好的状况,从新定位一下资源引用什么的就能够解决,可是最糟糕的状况下,有可能会致使数据丢失(其中最容易发生的就是由于BP类名修改,致使子BP类没法找到父BP类而致使子类没法正常使用只能删了重来……)

因此,在作UE4的包路径转移、资源名修改以前,必定要作好备份工做。最好是把全部原型迭代完毕后,统一进行相似操做,并常常存档或发SVN、GIT。

 

相关的注意事项:

 

Redirector须要提醒一点,虚幻里进行资源文件从一个文件夹到另外一个文件夹的移动,必定要在编辑器中进行。由于虚幻资源之间的引用关系是经过前面说的Object命名来保证的,而路径名又是Object的一部分,名称不对等很容易发生问题。而编辑器移动资源后,有时候会发现移动前所在的路径下多了一个1KB字节左右小尾巴,这个小尾巴就是Redirector,一样不要手动删除,而是要在资源查看器里,经过对Redirector(须要Filter开启)的Fix up命令来进行删除。

先把Redirector打开

而后Fixup,或者

右键直接文件夹,Fix up Redirectors in Folder。

 

因为包是"Link Load"的,加载过程当中会分析引用,因此若是包比较碎,这里就会因为作了更多的文件访问而致使速度变慢。单若是单个包内数据量较大,则也会致使加载单个包时速度变慢的状况,归根到底,就是权衡啊权衡。(通常说来,10个1k > 1个10k)

 

Transient对象不会被存盘,Transient包(GetTransientPackage)是一个特殊的包,全部临时对象都应该建立在这个包里面。

 

异步实际并不是真正异步。LoadObject加载过程当中有一系列的全局变量,并且这些全局变量维护时没有任何锁,因此也没法真正作到异步加载。因此虽然您看到接口上有LoadPackageAsync,但那个的实现是在主线程每帧区分时间片来实现的。不过话说回来,多线程读包真的有必要吗?机械硬盘的访问速度自己是最大的限制因素,读包过程当中的多线程,CPU其实帮不上任何忙。

 

真正大量磁盘或网络数据的异步加载能够参考Texture Streaming(为什么只有Texture作了Streaming就是由于这玩意儿如今是游戏最吃资源的了,十个模型的资源量不见得比得上一张贴图啊),先把Object和少许基本信息看成占位符加载进来,Object的实际数据则放到其余线程里慢慢加载。若是您有相似需求,能够考虑这个方案。不过感受是不必,好比咱们游戏经常使用的角色异步加载什么的,其实走主线程时间片彻底够了。

 

编辑器中的资源会被标记为Standalone,在无引用时仍然存在,其它还有一系列不会被GC的状况,须要注意。

相关文章
相关标签/搜索