2018年3月15日

针对分块下载优化《战争破坏者(Battle Breakers)》

作者 Ben Zeigler

在发布大中型游戏时,一种常见的做法是将游戏内容分隔成多个数据块(Chunk),以减少初始下载数据量,加快补丁安装,并优化加载时间。对于主机和PC平台而言,使用少量的大数据块通常是最佳选择,而对于移动游戏,使用大量小数据块的效果往往更好。在这个过程的开始,我们需要将内容分隔成多个独立的数据块,虚幻引擎4.19提供了多种支持这种设置的工具,而且还具备分析和优化功能。在这篇帖子中,我们将探讨在制作《战争破坏者》时使用的工具和技术,目的是确保尽可能减少其初始下载数据量,并支持按需下载额外的内容。

SHARE_BattleBreakersOptimizations.jpg

数据块设置

数据块是一组可以独立部署和下载的资源(游戏内容)。默认情况下,一个项目只有一个数据块,即所谓的Chunk 0,所有的资源都包含在这个数据块中。将资源分配到不同的数据块之后,虚幻引擎4的分段系统会使用每个数据块分别创建一个独立的.pak文件,然后便可以根据游戏的目标平台将这些文件集成到该平台相应的部署系统中。我们为《战争破坏者》创建的是使用HttpChunkInstaller插件下载的文件。过去的方法是使用烘培程序回调来创建数据块,但是从4.17开始,资源管理器就成了最简单也最灵活的方法。要充分利用这些功能,必须对游戏进行设置,以创建打包文档中所述的数据块。

《战争破坏者》(在代码中称作“World Explorers”)已经开发了一段时间,所以第一步是将它转换为可以使用资源管理器的格式。为此,我们重新审视并挑出了应该定为主资源(Primary Asset)的游戏内容类型,并在资源管理器设置中进行了相关设置。《战争破坏者》中有28种不同的主资源类型(Primary Asset Type),但最令人感兴趣的内容是地图和角色。《战争破坏者》有几百个具备专用美术资源的角色,但大多数玩家只能接触到其中的一部分。此外,设计师们希望将所有的新手任务/教程内容都加入初始下载包中,以避免影响玩家体验的流畅性。所以,《战争破坏者》所需的分块模式应该包括三种不同类型的数据块:装载新手任务内容的Chunk 0,装载其他地图和共用Gameplay内容的一组更高编号的数据块(手动定义,在玩家完成新手任务后下载),以及一组自动生成的数据块(包含其他角色的内容,按需下载)。以下是DefaultGame.ini中的一些示例设置:
 
[/Script/Engine.AssetManagerSettings]
+PrimaryAssetTypesToScan=(PrimaryAssetType="Character",AssetBaseClass=/Script/WorldExplorers.WExpCharacterDefinition,bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Characters/Classes"), (Path="/Game/World/Map")),Rules=(Priority=1,CookRule=AlwaysCook))
+PrimaryAssetTypesToScan=(PrimaryAssetType="WExpChunkDefinition",AssetBaseClass=/Script/WorldExplorers.WExpChunkDefinition,bHasBlueprintClasses=False,bIsEditorOnly=False,Directories=((Path="/Game/Chunks")))
为了实施数据块的手动设置,我们为《战争破坏者》创建了UPrimaryAssetLabel(以下简称“Label”)这个特定子类,它可以辨别数据块是用于新手任务、自动下载还是按需下载的内容。每个Label都有一个在规则(Rules)部分手动定义的数据块标识(ChunkId)和优先级(Priority),并指定了一个可以由美工和设计师修改的编辑器集(4.18中的新功能)。该集中的任何资源都会被拉取到相应的数据块,除非它是由更高优先级的主资源/Label来管理的。为了创建自动生成的角色数据块,我们给《战争破坏者》的专属UAssetManager子类添加了代码,以便调用每个角色的SetPrimaryAssetRules,从而将其内容分配到从Chunk 1000开始编号的数据块。我们将这些数据块分配信息保存/加载到一个简单的.csv文件中,以确保它们长期保持不变。最后我们还需要专门在虚拟函数ShouldSetManager中做一些与游戏相关的改动,以便调用它来确定哪些资源是由特定的主资源标识(Primary Asset ID)来管理的。对于如何将内容分配到数据块,每个游戏都需要有自己的规则,这具体取决于该游戏的特定需求。资源管理器是专为支持这一功能而设计的工具,它可以根据需要划分子类和定制。

BattleBreakers_Screen1.jpg

《战争破坏者》使用的另一个工具是ChunkDependencyInfo,它能指定数据块之间的父/子关系。为了对此进行设置,我们向DefaultEngine.ini中添加了如下几代码行:
 
[/Script/UnrealEd.ChunkDependencyInfo]
+DependencyArray=(ChunkID=100,ParentChunkID=11)
+DependencyArray=(ChunkID=101,ParentChunkID=11)

这些行指定了Chunk 100和Chunk 101是Chunk 11的子项。这意味着包含于Chunk 11的任何资源都不会包含于Chunk 100或Chunk 101。默认情况下,所有数据块都是Chunk 0的子项,这意味着系统不会浪费资源将初始启动数据块中的任何内容复制到其他数据块。通过正确设置数据块层次结构,我们最大程度地减少了游戏在玩家设备上占用的磁盘空间,并避免了内容的重复。

分析数据块分配

我们对这些初步设置的数据块进行了检查,看看它们能否带来我们想要的结果。实际结果不是很理想。因此,为了帮助追踪这些问题,我们向4.19添加了一些新的跟踪工具,它们可以为我们显示各个资源分配到的目标数据块,以及这样分配的原因。第一种工具是“资源审计”(Asset Audit)窗口,可以从编辑器中的“窗口-->开发人员工具-->资源审计”(Windows --> Developer Tools --> Asset Audit)菜单进行访问。在打开“资源审计”(Asset Audit)窗口之后,单击“添加数据块”(Add Chunks)按钮,窗口中便会显示游戏中存在的所有数据块的摘要:

ChunkAssetAudit_01.jpg

要检查单个数据块,右键单击它,然后使用“磁盘空间占用量分布图”(Size Map)或“引用查看器”(Reference Viewer)查看它所包含的内容。磁盘空间占用量分布图是一种全面视图,显示了内容是如何根据其所关联的顶级资源(或主资源标识(Primary Asset ID))进行分组的,并根据其磁盘空间占用量划分框的大小。而引用查看器(Reference Viewer)显示的则是单个资源之间的父子关系以及一些过滤器和选项。例如,我们可以使用引用查看器(Reference Viewer)查看Chunk_7,以了解其85MB的内容构成:
  
Chunk7.png

为此,我们查看了Chunk_7的引用视图,并发现Chunk_Onboarding是其中一个引用,于是我们双击它来使引用查看器(Reference Viewer)聚焦在它上面。绿色框表示主资源(Primary Asset),灰色框则表示实际存在于磁盘上的资源。从主资源到磁盘资源的引用称为管理引用(Management Reference),要查看这类引用,可能需要启用窗口左上方的“显示管理引用”(Show Management Reference)选项。在这种情况下,白色线条表示这些是显式管理引用,即资源显式地包含于主资源,在本例中,这是通过上述(编辑器)集实现的。如果我们禁用“搜索广度限制”(Search Breadth Limit)选项并向下滚动,我们会看到粉色线条,它们表示隐式管理引用。这些资源会被自动添加到管理集,因为它们是从显式管理资源递归引用的。

检查数据块分配的另一种方法是,在内容浏览器(Content Browser)中找到您感兴趣的资源,右键单击,选择“引用查看器”(Reference Viewer),然后手动启用“显示管理引用”(Show Management References)。这样就可以查看是由哪个主资源(如果有的话)管理该资源:

AssetReference.png

在本示例中,我们可以看到,该资源受WExpCharacterDisplay主资源的隐式管理。如果我们双击这个主资源,我们会看到它被分配到Chunk 1167。这是由左上方的“显示”(Display)资源的硬性引用导致的。如果我们双击该资源,我们会看到它是由主资源显式管理的。

检查数据块分配的另一种方法是,向“资源审计”(Asset Audit)窗口中添加多个真实资源,您可以通过在内容浏览器(Content Browser)选择它们来进行添加,也可以使用“资源审计”(Asset Audit)窗口中的批量添加选项之一。在本示例中,我们将所有的Texture2D资源添加到了“资源审计”(Asset Audit)窗口,然后按磁盘空间占用量排序。为了便于查看,我们还关闭了一些列的显示:
  
TextureAuditBB_01.png

在本示例中,磁盘空间占用量较大的纹理恰好大都在Chunk 2中。你可以右键单击某一个纹理,然后转至引用查看器(Reference Viewer)来研究将其分配到这个数据块的原因。“资源审计”(Asset Audit)窗口还能提供一些其他的有用信息。“内存”(Memory)占用量是根据资源的磁盘空间占用量(ResourceSize)来估算的,“总使用量”(Total Usage)是指引用该纹理的主资源的数量,框的大小反映了“主资源优先级”(Primary Asset Priority)。

优化磁盘空间占用量

我们使用了分析工具来确保各个数据块的设置符合我们的预期,但是其中几个数据块还是太大了。为了解决这个问题,我们使用了同样的工具来追踪所有磁盘空间占用量的去向。事实上,即使没有对游戏进行分块设置,也可以使用这些工具和技术,而且它们的功能在4.19中得到了显著的增强。首先,我们需要查看烘焙后的实际磁盘空间占用量,因此我们将回到“资源审计”(Asset Audit)窗口中的数据块视图。

如上面的截图所示,该窗口默认显示“编辑器”(Editor)磁盘空间占用量,这与实际烘焙过的构建中资源的磁盘空间占用量可能有很大差异。但是,如果已在本地完成了构建的烘焙,这个下拉列表就会显示已烘焙内容的目标平台数据,这样便可以知道在发布后部署该游戏时的实际磁盘空间占用量。选择“自定义”(Custom)还可以从硬盘加载特定的DevelopmentAssetRegistry.bin文件。如果将构建过程设置为对该文件进行归档,就可以用它来获取生产构建中的实际磁盘空间占用量数值。

选定所需的平台后,右键单击它并选择“磁盘空间占用量分布图”(Size Map),即可获得关于所有磁盘空间占用量的分布概览。以下是《战争破坏者》中Chunk_0的示例(在我们优化之前):

ChunkSizeMap.png
这个视图显示了该数据块中的所有资源,资源框的大小反映了其磁盘空间占用量。每种颜色的框代表不同的顶级资源或主资源标识(Primary Asset ID)。用鼠标滚轮放大或双击即可更深入地查看某个特定的资源组,而缩小或单击左上方箭头则会返回到前一个视图。右键单击某个资源可以将其放到引用查看器(Reference Viewer)或“资源审计”(Asset Audit)视图中进行更细致的检查。磁盘空间占用量分布图(Size Map)使用关于数据块和主资源管理引用的信息来过滤其内容,因此只有那些实际存在于该数据块中的资源才会显示出来。

由于磁盘空间占用量分布图(Size Map)中的资源框大小反映了其磁盘空间占用量,我们可以快速直观地找出一些优化途径。磁盘空间占用量较大的泛光纹理和一些VR专用资源对于手游来说是不必要的,所以我们将它们全部从构建中删除了。在其他情况下,我们会通过修改编辑器集,在数据块之间移动特定的资源。这些技术同样也可以用来优化内存,因为磁盘空间占用量通常能够很好地反映内存的占用量。实际上,在烘焙阶段是无法计算某一特定平台的运行时内存使用量的,因此,如果你从磁盘空间占用量分布图(Size Map)切换到内存占用量分布图,它会提供基于资源的磁盘空间占用量(Resource Size)的估算值,所以没有那么精确。

通过使用这些工具和专为游戏所做的优化,我们成功地为《战争破坏者》创建了数据块设置,从而提高了用户的下载速度,同时又能方便设计师根据情况的变化灵活地更新。这些工具可以用来帮助组织和优化任何游戏。