诚如之前所说,虚幻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自动就帮我们处理好的。有几个要注意的:

"Singleton",需要一直存在的,直接AddToRoot。

F类本身是不走垃圾回收的,但是F 类内部又有U类,这种情况下你需要注意AddReferencedObjects。把F类内部的U类给加入到GC树上。

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的情况,需要注意。

虚幻4随笔6 Object和序列化的更多相关文章

  1. 虚幻4随笔 三 从UE3到UE4

    笔者有幸参与过两个UE3项目,完全不同的使用方法,总共用了5.6年.引擎学习最好还是能参与项目,自己看的话往往容易纠结到一些细节上去,而引擎之所以是引擎,重要的恰恰是在容易被人忽视的工作流上.单从细节 ...

  2. java中流转化为Object可序列化

    一.PO实体类 public class buisPO{ /** * 业务数据 */ private Bolb buisData; public Blob getBusiData() { return ...

  3. 16 IO流(十三)——Object流 序列化与反序列化

    Object流.序列化与反序列化 Object流是将 可序列化的对象 进行序列化与反序列化的流. 可序列化的对象:使用关键字Serializable修饰,表示这个对象可以进行序列化与反序列化. 序列化 ...

  4. Error serializing object:序列化对象时出错

    序列化对象时出错 :Error serializing object. Error serializing object. Cause: java.io.NotSerializableExceptio ...

  5. 虚幻4随笔4 从project開始

     前文说到UE3開始.虚幻就使用了UnrealBuildTool(下面简称UBT)来编译和生成代码. 为什么这么做而不是使用VS是非常好理解的:由于VS跨平台会比較麻烦.像虚幻这样体量的proje ...

  6. Ignite cahce 存储object类型数据和object类型数据序列化后string存储区别

    Ignite cache在存储时 object类型的数据和 序列化该object成string类型 两者存储时间差不多. 但是这两者在读取出来的时候,string类型比object类型快很多. 以下为 ...

  7. C# 序列化与反序列化几种格式的转换

    这里介绍了几种方式之间的序列化与反序列化之间的转换 首先介绍的如何序列化,将object对象序列化常见的两种方式即string和xml对象; 第一种将object转换为string对象,这种比较简单没 ...

  8. c# Json 自定义类作为字典键时,序列化和反序列化的处理方法

    一般情况下,Newtonsoft.Json.dll 对 Dictionary<int,object>.Dictionary<string,object>等序列化与反序列化都是成 ...

  9. YbSoftwareFactory 代码生成插件【二十】:DynamicObject的序列化

    DynamicObject 是 .NET 4.0以来才支持的一个类,但该类在.NET 4.0下未被标记为[Serializable] Attribute,而在.NET 4.5下则被标记了[Serial ...

随机推荐

  1. Kuberentes-入门

    一.kubernetes架构介绍和集群规划 点击链接查看: 系统环境初始化:https://www.cnblogs.com/hwlong/p/9105742.html 二.CA证书创建和分发 点击链接 ...

  2. tp中引入js、css、img的问题

    方法一: 直接把js.css.img放到网站公共目录/Public/下. 然后直接在模板文件中使用__PUBLIC__进行替换. 方法二: 在模块配置文件config.php中配置指定的路径,如下: ...

  3. 如何在ecplise中配置maven以及ecplise访问本地仓库

    1.m2e的插件 因为使用ecplise版本比较高,所以它自带了maven的插件,但是我们希望可以使用我们自己指定的maven.配置步骤如下: ecplise--->preperences下,点 ...

  4. oracle pl sql import export

    http://blog.163.com/magicc_love/blog/static/185853662201281013345829/

  5. 骗分大法之-----分块||迷之线段树例题a

    什么是分块呢? 就是一种可以帮你骗到不少分的神奇的算法. 分块的写法有几种,我所知道的有①预处理②不预处理 不预处理的代码我看得一脸懵逼 所以我在这里就谈一下预处理的版本www 首先看一道题: 给定一 ...

  6. jquery节点获取

    jQuery.parent(expr)  找父亲节点,可以传入expr进行过滤,比如$("span").parent()或者$("span").parent(& ...

  7. break MISSING_BLOCK_LABEL_160; 看源代码出现的,源代码是反编译的

    break MISSING_BLOCK_LABEL_160; FileNotFoundException fnfe; fnfe; out.close(); throw fnfe; in.close() ...

  8. dev ChartControl 备忘

    一个chartControl 里包括以个diagram(图表) diagram里可以设置 x-axis与y-axis ,另外还可以设置SecondaryXAxis与SecondaryYAxis,在Se ...

  9. C# winIO32位,64位的使用(运行时要用管理员身份)

    下载地址: http://www.internals.com/utilities/WinIo.zip 一个按键的消息产生流程如下: 1)硬件中断/硬件端口数据 WinIO能模拟,或者修改IDT是在这一 ...

  10. Linux命令:sed

    简介 sed 是一种在线编辑器,它一次处理一行内容.处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的 ...