今年 4 月,战争机器人迎来了 10 周年纪念!时至今日,我们仍在继续开发和支持它,不仅为玩家发布新功能,还在技术上对其进行改进。
在本文中,我们将讨论我们多年来在该大型项目中的技术开发经验。但首先,以下是该项目的快照:
为了保持此类项目的功能性并确保进一步的高质量开发,仅完成当前产品任务是不够的;改善其技术状况、简化开发和自动化流程(包括与创建新内容相关的流程)也是关键。此外,我们必须不断适应不断变化的用户设备市场。
本文基于对 Pixonic(MY.GAMES)开发部主管 Pavel Zinov 和 War Robots 首席开发人员 Dmitry Chetverikov 的采访。
让我们回到 2014 年:War Robots 项目最初由一个小团队创建,所有技术解决方案都符合快速开发和向市场推出新功能的模式。当时,该公司没有大量资源来托管专用服务器,因此,War Robots 以基于 P2P 逻辑的网络多人游戏进入市场。
Photon Cloud 用于在客户端之间传输数据:每个玩家命令(无论是移动、射击还是任何其他命令)都使用单独的 RPC 发送到 Photon Cloud 服务器。由于没有专用服务器,游戏有一个主客户端,负责所有玩家比赛状态的可靠性。同时,其余玩家在本地客户端上完全处理整个游戏的状态。
举个例子,没有对移动进行验证——本地客户端根据自己的意愿移动机器人,这个状态被发送给主客户端,主客户端无条件地相信这个状态,并简单地将其转发给比赛中的其他客户端。每个客户端独立保存战斗日志,并在比赛结束时将其发送到服务器,然后服务器处理所有玩家的日志,给予奖励,并将比赛结果发送给所有玩家。
我们使用了一个特殊的 Profile Server 来存储有关玩家资料的信息。它由许多带有 Cassandra 数据库的服务器组成。每个服务器都是 Tomcat 上的一个简单应用服务器,客户端通过 HTTP 与其交互。
游戏玩法所采用的方法存在许多缺点。团队当然知道这些缺点,但由于开发速度和最终产品上市速度太快,不得不做出一些妥协。
这些缺点中,首先是主客户端的连接质量。如果客户端网络不好,那么比赛中所有玩家都会遇到延迟。而且,如果主客户端不是在性能非常强大的智能手机上运行,那么由于负载过高,游戏也会遇到数据传输延迟。所以,在这种情况下,除了主客户端,其他玩家也会受到影响。
第二个缺点是,这种架构很容易受到作弊者的攻击。由于最终状态是从主客户端传输的,因此该客户端可以随意更改许多比赛参数,例如,计算比赛中被占领的区域数量。
第三,Photon Cloud 上的匹配功能存在问题:Photon Cloud 匹配室中最老的玩家成为主客户端,如果在匹配过程中他们发生了什么事情(例如,断开连接),那么组队就会受到负面影响。相关趣闻:为了防止在将组队送去比赛后 Photon Cloud 中的匹配关闭,有一段时间,我们甚至在办公室安装了一台简单的 PC,这台 PC 始终支持匹配,这意味着 Photon Cloud 的匹配不会失败。
在某个时候,问题和支持请求的数量达到了临界点,我们开始认真考虑改进游戏的技术架构——这个新架构被称为基础设施 2.0。
这种架构背后的主要思想是创建专用的游戏服务器。当时,客户团队缺乏具有丰富服务器开发经验的程序员。然而,该公司同时在开发一个基于服务器的高负载分析项目,我们称之为 AppMetr。该团队创建了一款每天处理数十亿条消息的产品,他们在服务器解决方案的开发、配置和正确架构方面拥有丰富的专业知识。因此,该团队的一些成员加入了基础设施 2.0 的工作。
2015 年,在相当短的时间内,我们创建了一个 .NET 服务器,该服务器包含在运行于 Windows Server 上的 Photon Server SDK 框架中。我们决定放弃 Photon Cloud,仅在客户端项目中保留 Photon Server SDK 网络框架,并创建了一个主服务器,用于连接客户端和相应的服务器以进行匹配。部分逻辑从客户端移至专用游戏服务器。特别是,引入了基本伤害验证,并将比赛结果计算放在服务器上。
在成功创建基础设施 2.0 之后,我们意识到需要继续朝着解耦服务职责的方向发展:其结果是创建了微服务架构。在此架构上创建的第一个微服务是氏族。
从那时起,出现了一个单独的通信服务,它负责在微服务之间传输数据。客户端被教导与游戏服务器上负责元机制的“机库”建立连接,并使用 Photon Cloud 上的 UDP 发出 API 请求(与服务交互的单一入口点)。服务数量逐渐增加,因此,我们重构了匹配微服务。借助此,创建了新闻功能 Inbox。
当我们创建部落时,很明显玩家希望在游戏中相互交流——他们需要某种聊天方式。这最初是基于 Photon Chat 创建的,但一旦最后一个客户端断开连接,整个聊天记录就会被删除。因此,我们将此解决方案转换为基于 Cassandra 数据库的单独微服务。
新的微服务架构使我们能够方便地管理大量彼此独立的服务,这意味着可以水平扩展整个系统并避免停机。
在我们的游戏中,更新无需暂停服务器。服务器配置文件与多个版本的客户端兼容,至于游戏服务器,我们始终为新旧版本的客户端准备一个服务器池,在应用商店发布新版本后,这个池逐渐转移,游戏服务器随着时间的推移从旧版本中消失。当前基础设施的总体轮廓称为基础设施 4.0,如下所示:
除了改变架构之外,我们还面临着一个难题:我们的服务器应该位于何处,因为它们应该覆盖来自世界各地的玩家。最初,许多微服务都位于 Amazon AWS 中,原因有很多,特别是因为该系统在扩展方面提供了灵活性,因为这在游戏流量激增时非常重要(例如当它在商店中展示和提升 UA 时)。此外,当时很难在亚洲找到一个能够提供良好网络质量和与外界连接的优质托管服务提供商。
Amazon AWS 的唯一缺点是成本高昂。因此,随着时间的推移,我们的许多服务器都转移到了我们自己的硬件上——我们在世界各地的数据中心租用的服务器。尽管如此,Amazon AWS 仍然是架构的重要组成部分,因为它允许开发不断变化的代码(特别是联盟、氏族、聊天和新闻服务的代码),而稳定性测试没有充分涵盖这些代码。但是,一旦我们意识到微服务是稳定的,我们就将其转移到我们自己的设施中。目前,我们所有的微服务都在我们的硬件服务器上运行。
2016年,游戏的渲染做出了重要的改变:出现了为战斗中的所有机甲使用单一纹理图集的Ubershaders概念,这大大减少了绘制调用的数量并提高了游戏性能。
显然,我们的游戏在成千上万种不同的设备上运行,我们希望为每位玩家提供尽可能最佳的游戏条件。因此,我们创建了 Quality Manager,它基本上是一个文件,可以分析玩家的设备并启用(或禁用)某些游戏或渲染功能。此外,此功能允许我们将这些功能管理到特定的设备型号,以便我们能够快速解决玩家遇到的问题。
还可以从服务器下载 Quality Manager 设置,并根据当前性能动态选择设备上的质量。逻辑非常简单:Quality Manager 的整个主体被划分为多个块,每个块负责性能的某些方面。(例如,阴影或抗锯齿的质量。)如果用户的性能受到影响,系统会尝试更改块内的值并选择一个可提高性能的选项。稍后我们将回到 Quality Manager 的演变,但在这个阶段,它的实现速度非常快,并提供了所需的控制级别。
随着图形开发的继续,质量管理器的工作也必不可少。游戏现在具有级联动态阴影,完全由我们的图形程序员实现。
渐渐地,项目代码中出现了相当多的实体,所以我们决定开始管理它们的生命周期,并限制对不同功能的访问。这对于分离机库和战斗代码尤其重要,因为当游戏环境发生变化时,许多资源不会被清除。我们开始探索各种 IoC 依赖管理容器。特别是,我们研究了 StrangeIoC 解决方案。当时,这个解决方案对我们来说似乎相当麻烦,所以我们在项目中实现了自己的简单 DI。我们还引入了机库和战斗环境。
此外,我们开始致力于项目的质量控制;FirebaseCrashlytics 被集成到游戏中,以识别生产环境中的崩溃、ANR 和异常。
使用我们内部的分析解决方案 AppMetr,我们创建了必要的仪表板,用于定期监控客户状态并比较发布期间所做的更改。此外,为了提高项目质量并提高对代码更改的信心,我们开始研究自动测试。
资产数量的增加和内容中独特的错误让我们开始考虑组织和统一资产。对于机甲来说尤其如此,因为它们的构造对于每个机甲来说都是独一无二的;为此,我们在 Unity 中创建了工具,并利用这些工具制作了带有主要必要组件的机甲假人。这意味着游戏设计部门可以轻松编辑它们——这就是我们开始统一机甲工作的方式。
项目不断发展,团队开始考虑在其他平台上发布它。另外,对于当时的一些移动平台来说,游戏支持尽可能多的平台原生功能非常重要,因为这样可以让游戏脱颖而出。因此,我们开始为 Apple TV 开发 War Robots 的实验版本,并为 Apple Watch 开发配套应用。
此外,2016 年,我们在一个对我们来说很陌生的平台上发布了这款游戏——亚马逊应用商店。就技术特性而言,这个平台是独一无二的,因为它拥有统一的设备线,就像苹果一样,但这个线的功能处于低端安卓的水平。考虑到这一点,在这个平台上发布时,我们做了大量工作来优化内存使用和性能,例如,我们使用了地图集、纹理压缩。亚马逊支付系统(用于登录和成就的 Game Center 的类似物)被集成到客户端中,因此重新设计了玩家登录的流程,并推出了第一批成就。
值得一提的是,客户团队同时首次为 Unity 编辑器开发了一套工具,这使得在引擎中拍摄游戏内视频成为可能。这让我们的营销部门能够更轻松地处理资产、控制战斗并利用摄像头制作出观众非常喜欢的视频。
由于没有 Unity,尤其是服务器上没有物理引擎,大多数比赛仍需在玩家设备上模拟。因此,作弊问题仍然存在。我们的支持团队会定期收到用户的反馈,其中有作弊者的视频:他们会在方便的时候加速、在地图上飞来飞去、从某个角落杀死其他玩家、永生不死等等。
理想情况下,我们会将所有机制转移到服务器上;然而,除了游戏在不断开发中并且已经积累了大量遗留代码之外,使用权威服务器的方法也可能存在其他缺点。例如,增加基础设施的负载并需要更好的互联网连接。考虑到我们的大多数用户都在 3G/4G 网络上玩游戏,这种方法虽然显而易见,但并不是解决问题的有效方法。作为在团队中打击作弊者的替代方法,我们想出了一个新主意——创建“法定人数”。
Quorum 是一种机制,它允许您在确认造成的伤害时比较不同玩家的多个模拟;受到伤害是游戏的主要功能之一,游戏的其他状态实际上取决于此。例如,如果您消灭对手,他们将无法占领信标。
这个决定的思路是这样的:每个玩家仍然在模拟整个世界(包括射击其他玩家)并将结果发送到服务器。服务器分析所有玩家的结果,并决定玩家最终是否受伤以及受伤程度。我们的比赛涉及 12 人,因此服务器认为已经造成了伤害,只要在 7 名玩家的本地模拟中记录这一事实就足够了。然后,这些结果将被发送到服务器进行进一步验证。从示意图上看,该算法可以表示如下:
这种方案让我们能够显著减少作弊者的数量,这些作弊者在战斗中让自己无法被杀死。下图显示了针对这些用户的投诉数量,以及在服务器上启用伤害计算功能和启用伤害仲裁的阶段:
这种伤害处理机制解决了长期以来的作弊问题,因为尽管服务器缺乏物理原理(因此无法考虑表面地形和机器人在空间中的实际位置等因素),但所有客户端的模拟结果及其共识可以明确地了解谁在什么条件下对谁造成了伤害。
随着时间的推移,除了动态阴影之外,我们还使用 Unity 后期处理堆栈和批量渲染为游戏添加了后期效果。下图显示了应用后期处理效果后图形效果的改进示例:
为了更好地了解调试版本中发生的情况,我们创建了一个日志系统,并在内部基础设施中部署了一个本地服务,该服务可以收集内部测试人员的日志,并更轻松地找到错误的原因。QA 仍使用此工具进行游戏测试,其工作结果附加到 Jira 中的票据上。
我们还在项目中添加了一个自写的包管理器。最初,我们想通过各种包和外部插件(项目已经积累了足够多的包和插件)来改进工作。然而,当时 Unity 的功能还不够,所以我们的内部平台团队开发了自己的解决方案,包括包版本控制、使用 NPM 存储以及只需在 GitHub 上添加链接即可将包连接到项目的功能。由于我们仍然想使用原生引擎解决方案,我们咨询了 Unity 的同事,当 Unity 在 2018 年发布包管理器时,我们的许多想法都被纳入了最终解决方案中。
我们继续扩大游戏适用的平台数量。最终,出现了支持键盘和鼠标的平台 Facebook Gameroom。这是 Facebook (Meta) 的解决方案,它允许用户在 PC 上运行应用程序;本质上,它是 Steam 商店的模拟。当然,Facebook 的受众非常多样化,Facebook Gameroom 在大量设备上推出,主要是非游戏设备。因此,我们决定在游戏中保留移动图形,以免给主要受众的 PC 带来负担。从技术角度来看,游戏所需的唯一重大变化是集成 SDK、支付系统以及使用原生 Unity 输入系统支持键盘和鼠标。
从技术上讲,它与 Steam 的游戏构建没有太大区别,但设备质量明显较低,因为该平台的定位是休闲玩家的平台,他们拥有的设备相当简单,只能运行浏览器,而 Facebook 希望通过这个平台进入一个相当大的市场。这些设备的资源有限,尤其是该平台存在持久内存和性能问题。
后来,该平台关闭了,我们将玩家转移到 Steam,因为这样才有可能。为此,我们开发了一个特殊的系统,其中包含用于在平台之间转移账户的代码。
2017 年的另一件大事是 iPhone X 的发布,这是第一款带有刘海的智能手机。当时,Unity 不支持带有刘海的设备,因此我们必须基于 UnityEngine.Screen.safeArea 参数创建自定义解决方案,这会强制 UI 缩放并避开手机屏幕上的不同安全区。
此外,2017 年,我们决定尝试其他东西——在 Steam 上发布我们游戏的 VR 版本。开发持续了大约六个月,包括在当时可用的所有头盔上进行测试:Oculus、HTC Vive 和 Windows Mixed Reality。这项工作是使用 Oculus API 和 Steam API 进行的。此外,还准备了 PS VR 耳机的演示版本,以及对 MacOS 的支持。
从技术角度来看,需要保持稳定的 FPS:PS VR 为 60 FPS,HTC Vive 为 90 FPS。为了实现这一点,除了标准的 Frustum 和 Occlusion 之外,还使用了自编的 Zone Culling,以及重新投影(根据前几帧生成一些帧)。
为了解决晕动症的问题,我们采用了一种有趣的创意和技术解决方案:我们的玩家成为机器人飞行员,坐在驾驶舱里。机舱是一个静态元素,所以大脑会将其视为世界上的某种常数,因此,空间中的运动效果很好。
由于 Cinemachine 在 Unity 的该版本中尚未提供,因此游戏还有一个场景需要开发几个单独的触发器、脚本和自制时间线。
对于每种武器,瞄准、锁定、跟踪和自动瞄准的逻辑都是从头编写的。
该版本在 Steam 上发布后,受到了玩家的一致好评。然而,在 Kickstarter 众筹结束后,我们决定不再继续开发该项目,因为开发一款成熟的 VR PvP 射击游戏充满了技术风险,而且市场对此类项目的准备也不够充分。
在我们致力于改进游戏的技术组件以及创建新机制和开发现有机制和平台时,我们发现了游戏中的第一个严重错误,这在推出新促销活动时对收入产生了负面影响。问题是“价格”按钮被错误地本地化为“奖品”。大多数玩家对此感到困惑,用真实货币购买,然后要求退款。
技术问题在于,当时,我们所有的本地化都内置在游戏客户端中。当这个问题出现时,我们意识到我们不能再推迟开发允许我们更新本地化的解决方案。因此,我们创建了一个基于 CDN 和配套工具(例如 TeamCity 中的项目)的解决方案,它可以自动将本地化文件上传到 CDN。
这使得我们可以从我们使用的服务(POEditor)下载本地化内容并将原始数据上传到 CDN。
之后,配置文件服务器开始为客户端的每个版本设置链接,与本地化数据相对应。当应用程序启动时,配置文件服务器开始将此链接发送给客户端,并下载和缓存这些数据。
时间快进到 2018 年。随着项目的发展,从服务器传输到客户端的数据量也在增加。在连接到服务器时,需要下载大量有关余额、当前设置等的数据。
当前数据以 XML 格式呈现,并由标准 Unity 序列化器序列化。
除了在启动客户端时以及与配置文件服务器和游戏服务器通信时传输相当大量的数据之外,玩家设备还花费大量内存来存储当前余额并对其进行序列化/反序列化。
显然,为了提高应用程序的性能和启动时间,有必要进行研发以寻找替代协议。
我们的测试数据的研究结果显示如下表:
协议 | 尺寸 | 分配 | 时间 |
---|---|---|---|
XML | 2.2 兆字节 | 18.4 MB | 518.4 毫秒 |
MessagePack(无合约) | 1.7 兆 | 2 英里 | 32.35 毫秒 |
MessagePack(str 键) | 1.2 兆字节 | 1.9 MB | 25.8 毫秒 |
MessagePack(int 键) | 0.53 MB | 1.9 | 16.5 |
扁平缓冲区 | 0.5 MB | 216 乙 | 0 毫秒 / 12 毫秒 / 450 KB |
因此,我们选择了 MessagePack 格式,因为迁移比切换到具有类似输出结果的 FlatBuffers 更便宜,尤其是在使用带有整数键的 MessagePack 时。
对于 FlatBuffers(以及协议缓冲区),需要用单独的语言描述消息格式,生成 C# 和 Java 代码,并在应用程序中使用生成的代码。由于我们不想承担重构客户端和服务器的额外成本,因此我们改用了 MessagePack。
过渡几乎是无缝的,在第一个版本中,我们支持回滚到 XML 的功能,直到我们确信新系统没有问题。新解决方案涵盖了我们的所有任务 - 它显著减少了客户端加载时间,并提高了向服务器发出请求时的性能。
我们项目中的每个功能或新技术解决方案都是在“标志”下发布的。该标志存储在玩家的个人资料中,并在游戏开始时与余额一起发送到客户端。通常,当发布新功能(尤其是技术功能)时,多个客户端版本会同时包含旧功能和新功能。新功能的激活严格按照上述标志的状态进行,这使我们能够实时监控和调整特定解决方案的技术成功。
渐渐地,是时候更新项目中的依赖注入功能了。当前的功能已无法处理大量依赖项,并且在某些情况下引入了难以打破和重构的不明显的连接。(对于以某种方式与 UI 相关的创新尤其如此。)
在选择新解决方案时,选择很简单:著名的 Zenject,它已在我们的其他项目中得到验证。该项目已经开发了很长时间,得到了积极的支持,正在添加新功能,并且团队中的许多开发人员都以某种方式熟悉它。
我们开始一点一点地使用 Zenject 重写 War Robots。所有新模块都是用它开发的,旧模块则逐渐被重构。自从使用 Zenject 以来,我们在之前讨论过的上下文(战斗和机库)中获得了更清晰的加载服务顺序,这使开发人员能够更轻松地深入项目开发,并更自信地在这些上下文中开发新功能。
在相对较新的 Unity 版本中,可以通过 async/await 使用异步代码。我们的代码已经使用了异步代码,但整个代码库中没有统一的标准,而且由于 Unity 脚本后端开始支持 async/await,我们决定进行研发并标准化我们的异步代码方法。
另一个动机是从代码中删除回调地狱——即当存在一系列异步调用的顺序链时,每个调用都会等待下一个调用的结果。
当时有几种流行的解决方案,例如 RSG 或 UniRx;我们将它们全部进行了比较并将它们编译成一个表:
| RSG.承诺 | 第三方物流 | TPL 带 await | 优利达 | 带异步的 UniTask |
---|---|---|---|---|---|
完成时间,s | 0.15843 | 0.1305305 | 0.1165172 | 0.1330536 | 0,1208553 |
第一帧时间/自身,毫秒 | 105.25/82.63 | 13.51/11.86 | 21.89/18.40 | 28.80/24.89 | 19.27/15.94 |
第一帧分配 | 40.8 兆 | 2.1 兆字节 | 5.0 兆 | 8.5 兆字节 | 5.4 兆 |
第二帧时间/自身,毫秒 | 55.39/23.48 | 0.38/0.04 | 0.28/0.02 | 0.32/0.03 | 0.69/0.01 |
第二帧分配 | 3.1 兆 | 10.2 千字节 | 10.3 千字节 | 10.3 千字节 | 10.4 千字节 |
最终,我们决定使用大多数开发人员熟悉的原生 async/await 作为处理异步 C# 代码的标准。我们决定不使用 UniRx.Async,因为该插件的优势并不包括对第三方解决方案的依赖。
我们选择遵循最低要求功能的路径。我们放弃了使用 RSG.Promise 或 holders,因为首先,需要培训新开发人员使用这些通常不熟悉的工具,其次,需要为使用 RSG.Promise 或 holders 中的异步任务的第三方代码创建一个包装器。选择 async/await 还有助于标准化公司项目之间的开发方法。这种转变解决了我们的问题——我们最终获得了一个使用异步代码的清晰流程,并从项目中消除了难以支持的回调地狱。
UI 逐渐得到改进。由于我们团队的新功能功能由客户端程序员开发,布局和界面开发由 UI/UX 部门进行,因此我们需要一个解决方案,使我们能够并行处理同一功能 - 以便布局设计师可以在程序员编写逻辑的同时创建和测试界面。
这个问题的解决方案是将界面转换为 MVVM 模型。该模型不仅允许界面设计人员在不涉及程序员的情况下设计界面,而且还允许他们在尚未连接任何实际数据的情况下查看界面对某些数据的反应。在对现成的解决方案进行了一些研究以及对我们自己的解决方案 Pixonic ReactiveBindings 进行快速原型设计之后,我们编制了一个比较表,结果如下:
| CPU 时间 | 内存分配 | 内存使用情况 |
---|---|---|---|
Peppermint 数据绑定 | 367 毫秒 | 73 千字节 | 17.5 兆 |
DisplayFab | 223 毫秒 | 147 千字节 | 8.5 兆字节 |
Unity Weld | 267 毫秒 | 90 千字节 | 15.5 兆 |
Pixonic ReactiveBindings | 152 毫秒 | 23 千字节 | 3 兆字节 |
一旦新系统证明了其自身价值(主要是在简化新界面的制作方面),我们就开始将其用于所有新项目以及项目中出现的所有新功能。
到 2019 年,苹果和三星已经发布了几款图形性能相当强大的设备,因此我们公司提出了对 War Robots 进行重大升级的想法。我们希望利用所有这些新设备的性能,并更新游戏的视觉效果。
除了更新的图像之外,我们对更新的产品还有几个要求:我们希望支持 60 FPS 的游戏模式,以及针对不同设备的不同质量的资源。
当前的质量经理也需要返工,因为块内的质量数量在不断增加,这些质量在不断切换,降低了生产率,并且产生了大量排列,每个排列都必须进行测试,这给每次发布都带来了额外的成本。
重新设计客户端时,我们首先着手的是重构射击。最初的实现依赖于帧渲染速度,因为游戏实体和它在客户端中的视觉表现是同一个对象。我们决定将这些实体分开,这样射击的游戏实体就不再依赖于它的视觉表现,从而实现了不同帧速率的设备之间的更公平的对战。
游戏涉及大量的射击,但处理这些数据的算法却非常简单 - 移动、判断敌人是否被击中等。实体组件系统概念非常适合组织游戏中射弹运动的逻辑,因此我们决定坚持使用它。
此外,在我们所有的新项目中,我们都已经在使用 ECS 来处理逻辑,现在是时候将这种模式应用到我们的主要项目中以实现统一了。在完成这项工作后,我们意识到了一项关键要求——确保能够以 60 FPS 的速度运行图像。然而,并不是所有的战斗代码都转移到了 ECS,因此这种方法的潜力无法充分发挥。最终,我们没有像我们希望的那样提高性能,但我们也没有降低性能,这仍然是如此强大的范式和架构转变的一个重要指标。
与此同时,我们开始开发 PC 版本,看看使用新的图形堆栈可以实现什么级别的图形。它开始利用 Unity SRP,当时它仍处于预览版本中,并且不断变化。Steam 版本的图像效果非常令人印象深刻:
此外,上图所示的部分图形功能可以移植到功能强大的移动设备上。这尤其适用于 Apple 设备,这些设备历来具有良好的性能特征、出色的驱动程序以及紧密的硬件和软件组合;这使得它们能够生成非常高质量的图像,而无需我们采取任何临时解决方案。
我们很快意识到,仅仅改变图形堆栈并不能改变图像。我们还发现,除了功能强大的设备外,我们还需要针对功能较弱的设备的内容。因此,我们开始计划开发针对不同质量级别的内容。
这意味着机甲、枪械、地图、特效及其纹理必须按质量分开,即不同质量对应不同的实体。也就是说,高清质量下的物理模型、纹理和机甲纹理集与其它质量下的机甲不同。
此外,游戏在地图上投入了大量资源,这些资源也需要重新制作才能符合新的质量标准。很明显,当前的质量经理无法满足我们的需求,因为它无法控制资产质量。
因此,首先,我们必须决定游戏中可以使用哪些品质。考虑到我们当前品质经理的经验,我们意识到在新版本中,我们需要几个固定品质,用户可以根据给定设备的最大可用品质在设置中独立切换。
新的质量系统确定用户拥有的设备,并根据设备型号(对于 iOS)和 GPU 型号(对于 Android),使我们能够为特定播放器设置最高可用质量。在这种情况下,此质量以及所有以前的质量均可用。
此外,每种品质都有一个设置,可将最大 FPS 在 30 和 60 之间切换。最初,我们计划有大约五种品质(ULD、LD、MD、HD、UHD)。然而,在开发过程中,很明显,开发这么多品质需要很长时间,而且不会让我们减轻 QA 的负担。考虑到这一点,我们最终在游戏中采用了以下品质:HD、LD 和 ULD。(作为参考,目前使用这些品质的受众分布如下:HD - 7%、LD - 72%、ULD - 21%。)
当我们意识到需要实现更多品质时,我们就开始研究如何根据这些品质对资产进行分类。为了简化这项工作,我们同意采用以下算法:艺术家将创建最高品质 (HD) 资产,然后使用脚本和其他自动化工具,将这些资产的一部分简化并用作其他品质的资产。
在开发该自动化系统的过程中,我们开发了以下解决方案:
我们做了大量工作来重构现有的机甲和地图资源。原始机甲在设计时并未考虑不同的品质,因此存储在单个预制件中。重构后,从中提取了基本部件,主要包含其逻辑,并使用预制件变体创建了具有特定品质纹理的变体。此外,我们还添加了可以自动将机甲划分为不同品质的工具,同时考虑到根据品质划分纹理存储文件夹的层次结构。
为了将特定资源交付给特定设备,必须将游戏中的所有内容分成包和包。主包包含运行游戏所需的资源、用于所有容量的资源以及每个平台的优质包:Android_LD、Android_HD、Android_ULD、iOS_LD、iOS_HD、iOS_ULD 等。
将资源分成包得益于我们平台团队创建的 ResourceSystem 工具。该系统允许我们将资源收集到单独的包中,然后将它们嵌入到客户端或单独上传到外部资源(例如 CDN)。
为了交付内容,我们使用了平台团队创建的新系统,即所谓的 DeliverySystem。此系统允许您接收由 ResourceSystem 创建的清单并从指定来源下载资源。此来源可以是整体构建、单个 APK 文件或远程 CDN。
最初我们计划利用Google Play(Play Asset Delivery)和AppStore(On-Demand Resources)的能力来存储资源包,但在Android平台上,客户端自动更新存在很多问题,并且存储资源的数量也有限制。
此外,我们的内部测试表明,带有 CDN 的内容传送系统效果最好、最稳定,因此我们放弃将资源存储在商店中,开始将其存储在我们的云中。
当重制版的主要工作完成后,就到了发布的时候了。然而,我们很快意识到,尽管市场上有许多流行的游戏需要用户下载 5-10 GB 的数据,但最初计划的 3 GB 大小下载体验不佳。我们的理论是,当我们将重制版游戏发布到市场时,用户会习惯这一新现实,但不幸的是。
也就是说,玩家们并不习惯手游中这种大文件。我们需要尽快找到解决这个问题的办法。我们决定在几次迭代之后发布一个没有高清画质的版本,但我们还是在之后将它交付给了我们的用户。
在重制版开发的同时,我们积极致力于实现项目测试的自动化。QA 团队在确保项目状态能够自动跟踪方面做得非常出色。目前,我们拥有运行游戏的虚拟机服务器。使用自编的测试框架,我们可以用脚本按下项目中的任何按钮——在设备上直接测试时,相同的脚本可用于运行各种场景。
我们正在继续开发这个系统:我们已经编写了数百个持续运行的测试来检查稳定性、性能和正确的逻辑执行。结果显示在特殊的仪表板上,我们的 QA 专家和开发人员可以在这里详细检查最有问题的区域(包括测试运行的真实屏幕截图)。
在将当前系统投入运行之前,回归测试花费了大量时间(旧版项目大约需要一周时间),而且在数量和内容量方面也比当前版本少得多。但由于采用了自动测试,当前版本的游戏仅测试了两个晚上;这可以进一步改进,但目前我们受到连接到系统的设备数量的限制。
在重制版即将完成之际,Apple 团队联系了我们,并让我们有机会参加新产品发布会,以使用我们的新图形展示新款 Apple A14 Bionic 芯片(2020 年秋季与新款 iPad 一起发布)的功能。在这个小项目的工作过程中,我们创建了一个完全可运行的高清版本,能够以 120 FPS 的速度在 Apple 芯片上运行。此外,还增加了一些图形改进,以展示新芯片的强大功能。
经过激烈竞争和艰难的筛选,我们的游戏被列入了苹果秋季发布会!整个团队聚集在一起观看并庆祝这次活动,这真的很酷!
甚至在 2021 年推出游戏重制版之前,就出现了一项新任务。许多用户抱怨网络问题,其根源是我们当时的解决方案,即用于与 Photon Server SDK 配合使用的 Photon 传输层。另一个问题是存在大量不标准的 RPC,这些 RPC 以随机顺序发送到服务器。
此外,还存在世界同步问题以及比赛开始时消息队列溢出问题,这可能会导致严重的延迟。
为了解决这个问题,我们决定将网络匹配堆栈转向更传统的模型,该模型与服务器的通信通过数据包和接收游戏状态进行,而不是通过 RPC 调用。
新的在线比赛架构被称为 WorldState,旨在将 Photon 从游戏玩法中移除。
除了基于LightNetLib库将传输协议从Photon替换为UDP之外,新的架构还涉及简化客户端-服务器通信系统。
通过开发此功能,我们降低了服务器端的成本(从 Windows 切换到 Linux 并放弃了 Photon Server SDK 许可证),最终采用了对用户端设备要求更低的协议,减少了服务器和客户端之间状态不同步的问题数量,并创造了开发新 PvE 内容的机会。
一夜之间改变整个游戏代码是不可能的,因此 WorldState 的工作被分为几个阶段。
第一阶段是彻底重新设计客户端和服务器之间的通信协议,并将机甲的移动转移到新的轨道上。这使我们能够为游戏创建一个新模式:PvE。逐渐地,机制开始转移到服务器,特别是最新的机制(对机甲的伤害和致命伤害)。将旧代码逐步转移到 WorldState 机制的工作仍在继续,今年我们还将进行几次更新。
2022 年,我们推出了一个对我们来说很新的平台:Facebook Cloud。该平台背后的概念很有趣:在云端的模拟器上运行游戏,并将它们传输到智能手机和 PC 上的浏览器,而终端玩家无需拥有功能强大的 PC 或智能手机来运行游戏;只需要稳定的互联网连接。
从开发者的角度来看,两种类型的版本可以作为平台使用的主要版本:Android 版本和 Windows 版本。我们选择了第一种方式,因为我们对这个平台有更丰富的经验。
为了在 Facebook Cloud 上发布我们的游戏,我们需要进行一些修改,例如重新进行游戏中的授权并添加光标控制。我们还需要准备一个包含所有内置资源的版本,因为该平台不支持 CDN,我们需要配置我们的集成,但这些集成并不总是能在模拟器上正确运行。
在图形方面也做了很多工作以确保图形堆栈的功能,因为 Facebook 模拟器不是真正的 Android 设备,在驱动程序实现和资源管理方面有自己的特点。
然而,我们发现该平台的用户遇到了很多问题——连接不稳定、模拟器运行不稳定,Facebook 决定在 2024 年初关闭其平台。
程序员方面经常发生的事情是,定期处理项目的技术风险,定期监控技术指标,不断处理内存和优化资源,在合作伙伴 SDK 的第三方解决方案中寻找问题,广告集成等等,这些在这么短的文章中无法详细讨论。
此外,我们将继续研究和实践修复严重崩溃和 ANR 问题。当一个项目在如此多完全不同的设备上运行时,这是不可避免的。
另外,我们想指出那些致力于寻找问题原因的人员的专业精神。这些技术问题通常性质复杂,需要将来自多个分析系统的数据叠加在一起,并进行一些非平凡的实验才能找到原因。通常,大多数复杂问题都没有可以测试的一致可重现的案例,因此通常使用经验数据和我们的专业经验来解决这些问题。
应该说几句我们用来查找项目问题的工具和资源。
这只是该项目自诞生以来经历的技术改进的一小部分。很难列出所有已完成的工作,因为该项目在不断发展,技术经理们也在不断制定和实施计划,以改善产品的性能,无论是对最终用户还是对工作室内部使用该产品的人员,包括开发人员自己和其他部门。
我们对于产品在未来十年还会有很多的改进,我们希望能够在下一篇文章中分享这些。
祝 War Robots 生日快乐,感谢让这一切成为可能的庞大技术专家团队!