在选择要在游戏中使用的文件格式时,诸如PNG和FBX之类的流行文件通常最终不够好。 它们可能太昂贵而无法加载或占用太多磁盘空间。 为了最大程度地减少磁盘上的加载时间和大小,我们需要提供一个系统来将这些源资产处理为游戏中可用的内容。

我们针对此问题的解决方案包括三个步骤,现在我将更详细地介绍以下三个步骤:
- 使用第三方工具创作源资产 ,
- 配置单个来源资产的构建,
- 使用这些配置将内容构建为更优化的运行时格式。
来源资产
为了保留特定于应用程序的编辑信息,美术师通常使用工作文件,例如PSD用于Photoshop,MAX用于3DS Max。 这些文件格式通常是专有的,并且对插件的访问可能非常有限。 要将内容移动到游戏中,第一步是根据内容的类型将内容导出为更开放的中间格式,例如PNG或FBX。 这些中间格式是专门为在应用程序之间传输数据而构建的,通常有不错的SDK可供使用。
有时会构建管道以从工作文件中批量导出中间文件。 尽管我们不排除此解决方案是未来的改进,但我们目前需要美术师从工作文件中手动导出中间文件。
中间文件格式旨在在应用程序之间传输尽可能多的重要信息。 这使它们很沉重,尽管通常比特定于应用程序的工作文件要轻得多。 FBX之类的格式在描述数据方面也很通用。 而我们的游戏仅需要在运行时加载特定类型的数据,并且可以严格定义的方式进行加载。
配置资产构建
为了能够将源文件转换为更优化的运行时格式,我们需要为内容构建提供有关资产的一些信息。 我们通过将每个源文件与清单文件相关联来定义此信息。 该文件的内容取决于所讨论资产的类型。 例如,模型清单可能包含每个子网格的材料,以及是否应该计算网格顶点的切线。
清单文件也非常轻巧,因此加载速度非常快。 此属性使我们可以快速处理资产元数据并构建内容的依赖关系图。 例如,如果我们要直接访问FBX文件,则可能需要花费几秒钟来加载文件,更不用说对其进行处理了。

基于文本的格式的好处在于,我们无需附加工具即可轻松解决资产问题。 它还防止腐败。 但是,使用JSON的缺点是,它不是用于版本控制中合并的最佳格式。 但就我们而言,我们很少需要它。
清单序列化
我们使用轻量级的自动序列化系统来加载和存储清单文件。

从上图中可以看出,清单数据定义为具有字段和关联的元数据的结构。
宏用于定义反射信息。 ASSET_BEGIN_REGISTER宏仅声明元数据部分的开始,每个连续的ASSET_FIELD_ *添加有关各个字段的信息。 ASSET_FIELD_SIMPLE_KEY宏定义该类型的键。 如下所述,在清单生成期间重新应用手动修改时,此键用于在集合中定位对象实例。
反射使我们能够更改清单数据类型,而无需修改代码来加载/存储它们。 我们还可以使用反射来自动对清单数据执行一致性检查,并处理手动修改的数据,而不必逐个字段实现对它的支持。
更改清单数据结构
通常有必要更改清单数据结构包含的信息以及数据的格式。例如,我们刚刚将嵌入在模型清单中的材料移到了单独的文件中。 为此,我们必须更新所有现有清单,以确保新版本的内容构建系统能够处理它们。
为了使清单与管道的最新版本保持最新,我们将JSON数据加载到内存中,检查其版本并运行匹配的版本升级功能以将其带入下一个版本。 重复此过程,直到获得最新版本。
以通用方式访问和修改数据的能力是使用开放通用格式的主要优点之一。 只要不从构建中删除升级代码,我们就可以始终将较旧的清单版本升级到最新架构。
手动更改清单数据
内容构建系统支持在给定一组特定的源资产文件的情况下自动生成清单。 它还允许我们在建模应用程序中修改资产后重新导入它们。
为了重新生成清单以替换现有清单,我们当然必须考虑可能对清单进行的所有手动更改。 我们的解决方案是通过在属性名称后附加“:override”来标记所有手动更改的字段。
当重新生成清单时,系统还将加载现有清单,并使用它来修补新生成的清单以及找到的替代。 这可能会变得非常复杂,因为清单通常包含多个嵌入式类型,并且还可以包含这些类型的数组。 如上所述,我们通过为每个属性定义一个键属性来解决此问题。 也可以覆盖整个对象,在这种情况下,当前对象的所有值都将被复制到新对象。
建筑资产
现在,我们已经定义了如何参数化构建每个资产的参数,现在该看看我们如何使用该信息将内容实际处理为游戏中数据。
当我们使用引擎启动应用程序时,我们可以手动或自动触发内容构建。 两种方法都执行相同的代码路径,也可以随意禁用自动构建。
内容始终是为特定目标平台构建的。 所有平台都是单独构建的,这使我们可以同时为多个平台生成游戏内内容。 游戏中的所有内容都放置在项目工作目录中的临时文件夹中。 如果进行完全重建,我们可以通过仅将该文件夹的内容复制到其他开发人员来优化我们的工作流程,这样他们就不必等待重建。 这是将来可以通过服务器构建自动执行的步骤。 当前,在打包应用程序时,服务器内部版本仅用于iOS和Android内部版本。
生成过程本身会遍历所有清单,并检查是否由于源文件或清单本身的更改而需要重建生成的内容。 事实证明,即使有大量内容(尤其是在SSD驱动器上)浏览所有清单,速度也足够快,但是我们计划使用文件观察器扩展系统,以进一步限制工作集。
对于每个清单,构建系统都会生成适当的输出。 从单一纹理到整个预制件都可以。
预制件
内容构建系统中还有一个元素需要进行详尽的解释,那就是预制系统。 内容构建管道的主要作用之一是生成可以加载到引擎中的世界对象的定义。 正如我之前的一篇文章中所讨论的那样,我们由组件组成了世界对象:[ManagingAGameWorld]。
我们称这些定义为预制件,它们只是组件的集合。 内容构建将读取清单,并确定为自动生成的预制件定义哪种组件。 通常,这包括一个或多个变换以及模型组件。 如果清单中定义了动画,它也可以包括动画组件。
这些自动生成的预制件可以由引擎直接加载,但我们还支持将多个预制件合并为一个。 用例包括在储罐中添加转塔,将两个零件定义为单独的预制件。
通常,对于任何游戏对象,您都有一个自动生成的预制件和一个手工制作的预制件。 然后,后一个通过“基本”列表引用自动生成的一个,因此将其所有组件都包括在手工制作的列表中。 我们还支持对“基础”预制件中的组件进行附加修改,例如,将转塔的根部链接到坦克的基础上。
摘要
我们的自定义内容构建系统专注于清单的概念,该清单定义了将原始资产(例如FBX和PNG文件)构建为可以由引擎有效加载的形式的说明。 它还会自动构成称为“预制件”的更高级别的结构,以将资产快速整合到游戏中。 此外,预制系统支持合成,使我们能够构建更大的实体来满足游戏需求。
参考文献
- [ManagingAGameWorld]引擎内部:管理游戏世界https://medium.com/@heinapurola/engine-internals-managing-a-game-world-8d3ebd7379c2