Game Engine Gem 1
1.1 我们该如何对待中间件
现代游戏很少有完全由内部开发人员编写的专有定制代码组成的作品。在游戏行业中竞争所需的大量精良功能,对于一个工作室来说简直就是一项艰巨的任务,用一句话概括就是 "无利可图"。如今,人们希望游戏程序员能自如地使用其他地方发明的某些工具,而只重新发明那些能为自己的项目或工作室带来切实利益的工具,因为这些工具能直接促进游戏的成功,或在某种程度上使游戏与竞争对手区别开来。考虑到选择某些中间件库是为了满足某些需求,而且通常会有几种功能相似的产品可供选择,我们不禁要问:"在比较中间件产品时,团队应该考虑哪些因素?
假设我们选择的语言是 C/C++,这在当今的中间件和游戏开发中最为常见。我们还假设,我们正在讨论的是一个现有的代码库,其中缺少一个可以由中间件填补的特定功能,而团队的愿望是在集成过程中进行“ 外科手术 ”,并在需要移除时留下尽可能小的疤痕。
1.2 集成复杂性和模块化
中间件软件包最重要的第一个特征是其集成复杂性。好的库具有高度的模块化、最小的侵入性,并且易于插入不同的代码库中。简而言之,它们只做很少的假设,与其他系统的实施细节相当分离。一个好的库应该带有合理的默认设置,或者在系统处于可测试、可工作状态之前只需要非常有限的配置。集成工程师的经验水平会影响系统的可用性,因此最小化集成是最关键的。这可以促进快速评估周期--当工程师经历了漫长的集成周期,却发现资料库并不理想时,就会留下苦涩的回忆。我的经验法则是两天。任何需要两天以上时间才能运行的东西都会浪费一周的时间来进行测试,而开发周期中没有足够的一周时间来试用替代品。
1.3 内存管理
控制台游戏机开发人员都知道,谨慎的内存管理对稳定的产品至关重要。为支持虚拟内存的 PC 系统设计的中间件,甚至是那些下一代游戏机可以满足的高要求技术,可能并不适合过去较为有限的内存预算。重要的是,不仅要知道预期的内存预算,还要知道由谁负责管理内存分配。
理想情况下,每个中间件库都有自己的内存管理方案,只需使用两个参数进行初始化:内存块指针及其大小。中间件库的作者最了解分配的大小、分配的流失率,以及最适合防止分配池碎片化的内存管理算法。此外,这种方法的优势在于,当超出内存预算时,会明确通知开发人员,而不是从主堆中收集大量内存,因为在主堆中追踪内存消耗可能会很繁琐,而且会产生问题。此外,这种方法往往能突出显示库资产泄漏的集成失误,因为当堆耗尽时,这种方法很快就会失效。可调整大小的堆有时是可取的,特别是在某些游戏关卡需要转移内存权限以强调不同系统的情况下,不过这种功能相当少见。
如果中间件库没有管理完全隔离的堆,那么一个很好的后备方案是:你需要为 alloc() 和 free() 提供一对缺失的函数;你可以用自己的函数覆盖弱声明的函数;最后,你可以从源代码编译库,并提供一个用于分配的 #define 宏。这些方法都不会产生每次分配的函数调用开销,因为它们是由编译器或链接器解决的。在这种方法中,你可以选择将分配转发到主堆(切忌!),或者使用你选择的分配策略声明一个专用于子系统的特殊堆。 有些中间件只提供了注册分配回调的方法。不必要地浪费 CPU 周期是一种常见的弊病,这也是我对其他可靠产品的一个小抱怨。
最糟糕的情况是,程序库中充斥着对 new/malloc 和 delete/free 的直接调用。这样就无法简单地封装系统资源,也无法简单地衡量或限制资源消耗。
1.4 海量存储器输入/输出访问
访问光学介质的速度非常慢,即使是在硬盘驱动器上,寻道也会影响加载时间。虽然某些类型的中间件需要访问文件系统,特别是流媒体音乐系统或在后台加载游戏资产的异步流媒体系统,但大多数中间件并不需要直接访问物理介质 API。
除了这些例外情况,中间件一般确实需要访问某些资产,但绝不能直接从底层系统 API 请求这些资产。 一个好的中间件库能为开发者提供明确的钩子,使其能轻松重载文件和数据请求,这样就可以通过任何自定义文件系统(如 WAD 或 pack 文件)来传输这些请求,开发者可以选择在其中存储数据或将文件请求重定向到不同的硬件。
最灵活的库根本不尝试将数据作为文件或数据流处理、 相反,它们只处理大容量内存缓冲区,并将资源获取牢牢掌握在开发人员手中。 这种方法简化了错误处理和加载时间的优化,而且可以更快地部署到新硬件上,并获得可靠的结果。
最糟糕的中间件库(我经常在开放源代码中看到这种情况)假定 POSIX 文件系统或类似系统总是可用的,并直接依赖于 C 运行库调用,如 FILE 和 fopen()。
另一种解决文件系统抽象问题的错误尝试是,通过为用户提供的读写器类提供抽象接口,对文件流进行侵入式扩展。该接口的派生实例通过虚拟函数一次一个字节地抓取数据,甚至以固定大小的小块抓取数据。这与现实世界的数据和性能特征映射非常不匹配,应不惜一切代价避免使用。
1.5 日志
在中间件库中,有时会出现无法满足预期的情况。好的中间件库会以统一的方式处理警告,并有办法将它们集成到现有的日志系统中。更好的库会有某种琐碎的冗余设置,范围从嘈杂到绝对安静,最好是完全编译出字符串和错误检查。嘈杂的冗余设置会提供大量有关输入数据的详细信息,这让开发人员有一种信任感,相信该库已被正确集成,其文件正在被正确读取。但是,如果不能完全编译出这些信息,那么在最终发布版本中,内存和 CPU 性能的代价将是不可接受的。有些中间件带有发布库和调试库,原因就在于此。
架构最好的中间件产品都有一个简单的方法来挂接日志输出回调,以便将日志集成到游戏现有的报告系统中。小心任何轻率调用 printf() 的库,因为该函数相对昂贵,甚至可能没有标准的 因为该函数相对昂贵,甚至可能没有连接标准输出管,无法向用户提供提示。提醒用户的方法。
1.6 错误处理
最好的中间件根本不会有错误条件,因为它们总是能正常工作。与此同时,在地球上,错误是不可避免的,如何处理错误是决定某些软件能否在项目环境中运行的一个重要因素。关于错误是致命的、可恢复的还是介于两者之间的灰色地带,有不同的观点。如果中间件库不给你任何选择的余地,无法覆盖错误的处理方式,往往会被降级。
控制台开发人员都知道,给游戏打补丁往往是不可能的,有些错误几乎无法重现或追踪。有时需要采取 "英雄措施 "来保证游戏不会崩溃,即使某些子系统出现了完全彻底的故障。每款软件都应具备通过处理程序转发严重错误的能力,在这种情况下,你可以在挂起或至少重启之前关闭声音系统、清除屏幕并打印 "你赢了!"。避免使用 exit()、abort(),甚至是赤裸裸的 assert() 调用。
1.7 稳定性和性能一致性
中间件应该是稳定的。毕竟,开发人员不自己编写的主要原因是编写和调试所需的时间。不能报告(并试图恢复)常见琐碎错误的库是脆弱的,往往会受到开发人员的诅咒。最好的中间件永远不会崩溃,会修复垃圾输入或忽略它们 输入或忽略它们,并为程序员留下大量的调试线索。 程序员追踪问题的线索。顶级工具和引擎可以 能承受严重的数据损坏和文件丢失,而不会直接崩溃。 最终会阻碍整个团队处理问题的进度。
在性能方面,每个项目都有不同的期望值,因此您需要自己判断什么是绝对可接受的。不过,每个您认为可用的中间件库都应该具有一致的内存和 CPU 性能。稳定的帧到帧内存占用对于按时发布游戏至关重要。偶尔出现的帧速率峰值也很难跟踪,而且会严重降低游戏体验。好的中间件在任何情况下都能通过剖析器检测到。
我最近参与的一个项目出现了单帧内存峰值 2 MB 和 100 毫秒的情况。经过追踪,这是因为关卡脚本的编写方式发生了细微变化。 如果虚拟机更稳定,就会限制执行指令的数量,或将执行限制在一个时间片内。一个更稳定的程序库会更密切地关注内存使用情况。轶事证据有时就是你所能得到的一切,直到你在最糟糕的时候与个人经验发生冲突。到处问问吧。
1.8 自定义剖析工具
每当考虑纳入一个软件包时,能在项目接近尾声时减少不确定性的工具都会给我留下深刻印象。这些工具包括任何自带监控 API 的系统,或者更好的是某种剖析器,它能在游戏接近尾声时,内存紧张、CPU 时间稀缺、无人知晓其走向的情况下,减少程序员数天的潜在时间。如果能立即拿出一个工具来检测游戏的某个部分,就能迅速缩小搜索范围。获取内容的可视性通常很难,因此中间件提供的任何帮助都是巨大的。
1.9 客户支持
商业中间件通常提供集成专家、电话支持、直接电子邮件支持以及论坛或邮件列表。有时,这正是一个团队向前迈进所需要的,尤其是当他们遇到重大障碍时。使用中间件的全部原因就是为了降低风险和不确定性,而客户支持正是这样做的。 没有预期支持的中间件是无用的。我曾放弃过许多伟大而有前途的技术,原因仅仅是作者无法回答我的几个问题。回答几个问题。
注意论坛上的快速响应时间,以及专人在电话或电子邮件中回答问题。 电话或电子邮件中回答问题的专业人员,并准备好在他们要求时将集成代码发送给他们。当他们提出要求时,准备好将您的集成代码发送给他们。(这也是保持代码整洁的更好理由)。
1.10 对维护者的要求
程序员工作繁忙,成本高昂。如果中间件需要花费大量精力和注意力才能与你的构建系统很好地集成,那么它很快就会被淘汰。理想情况下,你只需在构建过程中添加一个库,#include 一个头文件,然后调用几个函数。嗒哒!就集成好了。
有时,中间件更具侵入性,需要设置各种 #define 宏来配置它。或者,中间件需要直接集成到项目中,并与游戏一起编译。此外,有些中间件还具有外部依赖性,必须存在这些依赖性才能编译。更糟糕的是,它可能需要在编译过程中引入一个新工具,而该工具可能无法与编译系统很好地配合。整套系统显然更可取。我寻找的中间件带有 GCC 和 Microsoft Visual Studio 项目文件,但项目配置极为基本。这证明我所关心的编译器可以处理这些代码,而且在使用所提供的项目文件进行初始 在使用提供的项目文件完成初始构建后,我就可以扔掉项目文件,按照自己的方式集成它们。
1.11 源代码的可用性
最终,您可能需要调试到计划集成的中间件产品的源代码。如果该产品是专有的闭源产品,没有价格合理的源代码选项,则应寻找替代产品。虽然程序库中的某些 "行业机密 "部分可能是二进制的,但供应商知道源代码是可望而不可及的,因此通常会在获得许可后提供源代码。那些不提供源代码的供应商通常会声称,他们的客户支持可以避免这种需求。虽然这可能是事实,但一旦出现客户支持无法解决但源代码可以解决的问题,我们就会开始寻找替代品。同样,使用中间件的一个重要原因是,中间件是经过验证的、稳定的,因此源代码实际上并不是必需的。但如果需要,可用性仍然很重要。
1.12 源代码的质量
拥有源代码并不总是意味着你有能力进行有意义的修改。最优秀的中间件都有出色的文档,每当有新代码时,这些文档就会从源代码中自动生成。它将有一个熟悉的、一致的括号方案,对函数和变量有某种命名约定,最好是在一个简短的命名空间内。
花点时间仔细阅读重要的头文件。检查语言关键字或常用函数(如 new、min 和 max)的 #define 宏,或者任何超出头文件范围、可能在其他地方引起问题的宏。使用存档检查工具,如 dumpbin(Windows)或 nm(Linux),验证中间件库定义的唯一导出符号是否在一致的命名空间中,以避免与其他库或自己的代码发生冲突。
有问题的中间件会充满 #pragma 语句、禁用错误、降低警告级别等。请仔细检查。头文件中的这些语句会损害代码的质量,并可能导致某些文件停止编译。
1.13 平台可移植性
某些类型的中间件可能与平台无关,但即使你有源代码,也可能潜伏着字节排序(endianness)问题。检查文件和网络流处理是否存在字节序交换代码,或者是否存在按平台将数据设置为本地格式的不同资产构建工具。
如今,多线程在游戏中非常普遍。一些中间件库早在这一转变之前就已存在。确定线程锁定是如何处理的,代码是否是线程安全的,以及线程控制是否可以通过不同平台的接口轻松覆盖。 请注意,任何不涉及线程的库,就其本质而言,在线程环境中使用可能是不安全的。要考虑使库线程安全的时间因素,或者至少将其使用限制在一个特定的线程上。
还要考虑您的游戏是否会在某个时候移植到能力较弱的硬件平台上。如果有可能,请确定该中间件产品有哪些独特功能,如果该库不适用低端平台,那么移植到其他库就会特别困难。
1.14 许可要求
我不是律师,这不是法律建议。请咨询您自己的律师来回答具体问题,并亲自阅读每份许可证。还要注意的是,大多数出版商甚至一些硬件制造商都对允许在你开发的产品中使用哪些开源许可有严格的规定。在使用开放源代码许可之前,请先向法律部门咨询。以下是我的外行观点。
据我所知,所有开放源码许可证都是在分发软件的过程中产生的。这意味着,只要你不向公司外部分发软件,你就可以在内部使用的应用程序中使用任何你喜欢的软件。你可以在面向内部的服务器技术或工具中随意使用任何开放源代码,但不要计划分发这些程序。
公共域代码是完全无害的。一旦你将代码复制到硬盘上,你就拥有了它,你对它所做的每一项修改都归你所有。 当然,你也可以把许可证改成其他的,把它放回公共领域,或者保密。它是你的。
MIT、Zlib 等许可证似乎或多或少不对如何使用其软件承担任何责任。除了信用条款和在代码中保留某些声明外,它们不要求任何其他条件。这不是一种限制零售使用的许可证。
LGPL 许可证有一些规定,要求分发库的源代码(可能还包括您自己应用程序的部分内容)。在零售产品中使用前请仔细阅读。
GPL 许可证和许多其他类似许可证要求,如果将它们集成到产品中,则在发布时必须发布完整的代码。
通常情况下,商业中间件许可证会要求署名、闪屏、介绍影片等。 屏幕、介绍影片等。请注意遵守这些要求,并在适当的地方注明出处。 中间件作者帮助实现了您的游戏。
1.15 费用
每个游戏对中间件的预算都不同。价格从 100 美元到 750,000 美元不等,这取决于您需要什么样的产品。优秀的中间件需要专业人员对细节的关注,而在专业使用库时,良好的支持也很重要(但通常费用较高,这取决于你的需求水平)。
在寻找收费的软件库时,要考虑其费用是按座位、按游戏、按平台计算的,还是有维护许可证的,如按年计算。许多中间件公司对数字下载游戏和零售游戏有特殊的收费标准。此外,你也可以打电话给你所选择的中间件商店的客户经理,试着说服他们给你折扣。说服他们提供折扣。如果这是你与他们合作的第一部作品,他们的灵活性可能会让你大吃一惊。 他们很清楚,中间件往往会变得根深蒂固,而且移除的成本很高。 他们很清楚,中间件往往会变得根深蒂固,而且移除的成本很高,所以让你迷上他们的产品对他们来说是件好事。 产品。如果你有这样的筹码,试着达成多个项目的交易。
有些库(如 Zlib)已经发展到在网上论坛和示例代码中提供大量支持的地步,错误数量接近零。估算一下实现上述各种缺失功能需要多少人工周,给这些人工周定价,然后与中间件库的成本进行比较。如果两者相差无几,那就选择商业中间件,因为它是成熟的、经过测试的、受支持的,而且最重要的是,你可以把程序员解放出来,让他们开发实际的游戏功能,而不是给别人的宠物项目打补丁。
References
[1] Kyle Wilson."Opinion: Defining Good Middleware". Gamasutra.com.news