关于哈布雷游戏开发的文章很多,但其中很少有涉及“幕后”主题的文章。 这些主题之一是组织游戏的交付,实际上是长期向大量用户(一,二,三)交付游戏。 尽管事实上对于某些任务而言,这项任务看似微不足道,但我还是决定分享我在一个特定项目中从事此工作的经验。 有兴趣的人-请。关于信息披露的一点题外话。 大多数公司都非常嫉妒公众无法使用“内部厨房”。 为什么-我不知道,但是那是-那是。 在这个特别的项目-Universim中,我很幸运,是Crytivo Inc.的首席执行官。 (以前是Crytivo Games Inc.)Alex Wallet在这件事上绝对是理智的,所以我有机会与他人分享经验。关于修补程序本身的一些知识
我从事游戏开发已有很长时间了。 在某些情况下–作为游戏设计师和程序员,在某些情况下–作为系统管理员和程序员的融合(我不喜欢术语“ devops”,因为它不能准确反映我在此类项目中执行任务的本质)。
在2013年底(时间流逝的恐惧),我考虑向用户交付新版本(内部版本)。 当然,当时有许多解决方案可以解决这一任务,但是赢得了制造产品的愿望和“自行车制造”的愿望。 另外,我想更深入地学习C#-因此,我决定制作自己的修补程序。 展望未来,我将说这个项目是成功的,有十几家公司在项目中使用了该项目并将其用于项目中,有些公司要求在考虑其意愿的情况下进行版本设计。
经典的解决方案涉及创建不同版本的增量包(或差异包)。 但是,这种方法对于测试人员和开发人员均不方便-在一种情况下,为了获取游戏的最新版本,您需要遍历整个更新链。 即 玩家需要按顺序收集他(a)永远不会使用的一定数量的数据,而开发人员则需要在一些服务器上存储一堆过时的数据,某些玩家可能曾经需要这些数据。
在另一种情况下-您需要下载最新版本的补丁程序,但开发人员需要将所有这些补丁程序放在家里。 修补程序系统的某些实现需要某些软件和服务器上的某些逻辑-这也使开发人员感到头疼。 此外,游戏开发人员通常不想做与游戏本身的开发没有直接关系的任何事情。 我还要说更多-大多数不是可以配置服务器来分发内容的专家-这根本不是他们的活动范围。
考虑到所有这些,我想提出一种解决方案,该解决方案对于用户(想玩得更快,不希望使用不同版本的补丁跳舞)以及需要编写游戏而又不知道为什么和原因的开发人员来说,应尽可能地简单。不会由下一个用户更新。
知道某些数据同步协议是如何工作的(当在客户端上分析数据并且仅传输来自服务器的更改时),我决定使用相同的方法。
此外,实际上,在整个开发阶段中,各个版本之间的版本都会有所不同,许多游戏文件都会稍有变化-纹理存在,模型本身以及一些声音。
结果,将游戏目录中的每个文件视为一组数据块似乎是合乎逻辑的。 当发布下一个版本时,将分析游戏版本,构建块图,并逐块压缩游戏文件本身。 客户端分析现有块,仅下载差异。
最初,修补程序被计划为Unity3D中的模块,但是出现了一个令人不快的细节,使我重新考虑了这一点。 事实是Unity3D是一个完全独立于您的代码的应用程序(引擎)。 在引擎运行时,一大堆文件都打开了,这在您要更新它们时会产生问题。
在类似Unix的系统中,覆盖打开的文件(除非明确地将其锁定)不会出现任何问题,但是在Windows上,如果没有手鼓跳舞,这种“耳朵假意”是行不通的。 这就是为什么我将修补程序制作为单独的应用程序,除了系统库之外不加载任何东西的原因。 实际上,修补程序本身实际上是完全独立于Unity3D引擎的实用程序,但是并不能阻止将其添加到Unity3D存储中。
修补程序算法
因此,开发人员以一定的频率发布新版本。 玩家希望获得这些版本。 开发人员的目标是为玩家提供最小的成本和最小的麻烦的过程。
来自开发商
准备补丁程序时,补丁程序操作的算法如下所示:
○使用其属性和SHA512校验和创建游戏文件树
○对于每个文件:
►将内容分成块。
►保存SHA256校验和。
►压缩块并将其添加到文件块映射。
►将块地址保存在索引中。
○保存文件树及其校验和。
○保存版本数据文件。
开发人员必须将收到的文件上传到服务器。
玩家侧
在客户端上,修补程序执行以下操作:
○将自身复制到其他名称的文件中。 如有必要,这将更新修补程序可执行文件。 然后控制权转移到副本,原件完成。
○下载版本文件并与本地版本文件进行比较。
○如果比较没有发现差异-您可以玩,我们有最新版本。 如果有差异,请转到下一项。
○下载带有校验和的文件树。
○对于服务器中树中的每个文件:
►如果有文件,它将考虑其校验和(SHA512)。 如果不是,则认为它是但为空(即由实零组成),并且还认为其校验和。
►如果本地文件的总和与最新版本文件的校验和不匹配:
►创建本地块图,并将其与服务器中的块图进行比较。
►对于每个与远程块不同的本地块,它从服务器下载一个压缩块并在本地覆盖它。
○如果没有错误,请更新版本文件。
经过一定数量的测试后,我使数据块大小为1024字节的倍数,我决定使用64KB块更容易操作。 尽管代码的通用性仍然存在:
#region DQPatcher class public class DQPatcher {
如果将块缩小,那么更改本身很少时,客户端就需要较少的更改。 然而,另一个问题出现了-索引文件的大小与块大小的减小成反比地增加-即 如果我们使用8KB的块进行操作,则索引文件将比使用64KB的块大8倍。
我出于以下考虑为文件和块选择了SHA256 / 512:与(过时的)MD5 / SHA128相比,速度略有不同,但是您仍然需要读取块和文件。 与SHA256 / 512发生碰撞的可能性大大小于与MD5 / SHA128发生碰撞的可能性。 完全无聊-在这种情况下,它是如此之小,以至于可以忽略这种可能性。
此外,客户考虑以下几点:
►数据块可以以不同的版本移动,即 在本地,块编号为10,在服务器上,块编号为12,反之亦然。 考虑到这一点,以免下载额外的数据。
►一次不请求块,而是成组请求-客户端尝试合并必要块的范围,并使用Range标头从服务器请求它们。 这也最大程度地减少了服务器负载:
不用说,客户端可以随时中断,并且在随后启动时,它实际上将继续其工作,并且不会从头开始下载所有内容。
在这里,您可以观看有关愤怒的机器人示例项目的修补程序工作的视频:
关于游戏Universe补丁的组织方式
2015年9月,Alex Koshelkov与我联系并提出加入该项目-他们需要一个能够为3万名玩家(带尾巴)提供每月更新的解决方案。 存档中游戏的初始大小为600 MB。 在与我联系之前,曾尝试使用Electron制作自己的版本,但是所有问题都遇到了打开文件的问题(顺便说一下,Electron的当前版本可以做到这一点)和其他一些问题。 而且,没有开发人员了解所有这些工作原理-他们为我提供了几种自行车设计,服务器部分完全不存在-他们想在解决所有其他任务后再做。
另外,有必要解决如何防止玩家密钥泄漏的问题-事实是密钥是针对Steam平台的,尽管Steam上的游戏本身尚未公开。 分发密钥是游戏的严格要求-尽管玩家有可能与朋友共享游戏密钥,但这可以忽略不计,因为如果游戏出现在Steam上,则密钥只能被激活一次。
在修补程序的普通版本中,修补程序的数据树如下所示:
./
|-Linux
| |-1.0.0
| `-version.txt
|-macosx
| |-1.0.0
| `-version.txt
`-窗户
|-1.0.0
`-version.txt
我需要确保只有具有正确密钥的用户才能访问。
我想出了以下解决方案-对于每个密钥,我们获取其哈希(SHA1),然后将其用作访问服务器上补丁数据的路径。 在服务器上,我们将补丁程序数据传输到比docroot更高的级别,并在docroot本身中将带有补丁程序数据的符号链接添加到目录中。 符号链接具有与密钥哈希相同的名称,只是分为几个级别(以促进文件系统的操作),即 哈希0f99e50314d63c30271 ... ... ade71963e7ff将表示为
./0f/99/e5/0314d63c30271.....ade71963e7ff -----> /完整/路径/到/补丁数据/
因此,不必将密钥本身分发给将支持更新服务器的人员-足以将对玩家本身毫无用处的散列值进行转移。
要添加新键(或删除旧键)-只需添加/删除相应的符号链接。
通过这种实现方式,密钥本身的验证显然不会在任何地方执行;在客户端上收到404错误表示密钥不正确(或已被停用)。
应该注意的是,密钥访问不是全面的DRM保护-这些仅仅是(封闭)alpha和beta测试阶段的限制。 而且,借助Web服务器本身(至少在我使用的Nginx中)可以轻松地切断搜索。
在发布月份,仅第一天就交付了2.5 TB的流量,随后几天,平均每月平均分配的流量大约相同:

因此,如果您计划分发大量内容-最好预先计算将花费多少。 根据个人观察-来自欧洲托管服务商的流量最低,来自亚马逊和谷歌的流量最高(我会说“金”)。
实际上,Universim每年平均可节省大量流量-比较上述数字。 当然,如果用户根本没有游戏或游戏已经过时,那么奇迹就不会发生,他将不得不从服务器上下载大量数据-如果是从头开始的话,则比游戏在存档中的存储量还多。 但是,通过每月更新,情况会变得非常好。 在不到6个月的时间里,美国镜像提供了超过10 TB的流量,如果不使用修补程序,该价值将大大提高。
这是该项目的年度流量的样子:

关于最难忘的“耙子”的几句话,我们必须在开发“ Universim”游戏的自定义修补程序的过程中采取以下步骤:
●最大的麻烦是等待我使用防病毒软件。 好吧,他们不喜欢从互联网上下载内容,修改文件(包括可执行文件)然后尝试运行下载的应用程序。 一些防病毒软件不仅阻止访问本地文件,而且还阻止了对更新服务器本身的调用,直接进入了客户端下载的数据。 解决方案是为修补程序使用有效的数字签名-这样可以大大减少防病毒软件的偏执狂,并使用HTTPS协议而不是HTTP-可以快速消除与防病毒软件的好奇心有关的一些错误。
●进度更新。 许多用户(和客户)希望看到更新进度。 一个人必须即兴创作,因为不一定总是可以可靠地显示进度而不必做额外的工作。 是的,并且补丁程序结束的确切时间也无法显示,因为补丁程序本身不具有需要事先更新文件的数据。
●来自美国的大量用户对来自欧洲的服务器的连接速度不是很高。 将更新服务器迁移到美国解决了此问题。 对于其他大洲的用户,我们将服务器留在了德国。 顺便说一句,在某些情况下,美国的交通费用比欧洲的交通费用贵很多-几十倍。
●Apple对这种安装应用程序的方法不太满意。 官方政策-应仅从其商店中安装应用程序。 但是麻烦的是,商店中不允许处于alpha和beta测试阶段的应用程序。 更重要的是,关于从早期访问中出售原始应用程序没有什么可谈的。 因此,您必须编写有关如何在罂粟花上跳舞的说明。 由于测试人员数量的限制,未考虑使用AppAnnie的选项(当时它们仍然是独立的)。
●网络相当不可预测。 为了不使应用程序立即放弃,我必须输入一个错误计数器。 9个捕获的异常使您可以坚定地告诉用户他的网络存在问题。
●32位操作系统对每个执行线程以及整个进程的内存(内存映射文件-MMF)中显示的文件大小有限制。 修补程序的第一个版本使用MMF来加快工作速度,但是由于游戏资源文件很大,因此我不得不放弃这种方法并使用普通的文件流。 顺便说一句,没有观察到特殊的性能损失-很有可能是由于主动读取OS。
●您必须准备让用户抱怨。 无论您的产品有多好,总会有一些不满意的人。 而且,您产品的用户越多(就Universim而言,目前有5万多用户)-从数量上讲,您的投诉就会越来越多。 就百分比而言,这是一个很小的数字,但就数量而言...
尽管该项目总体上是成功的,但它仍然存在一些缺点:
●尽管起初我分别取出了所有主要逻辑,但GUI部分在MAC和Windows的实现上有所不同。 Linux版本没有引起问题-所有问题主要仅在使用不需要Mono Runtime Environment-MRE的整体构建时出现。 但是由于您需要具有分发此类可执行文件的附加许可证,因此决定放弃单片构建,而仅需要MRE。 Linux版本与Windows版本的区别仅在于对特定于* nix系统的文件属性的支持。 对于我的第二个项目(不仅仅是一个修补程序),我计划使用一种模块化方法,该方法以内核进程的形式运行,该进程在后台运行,并允许在本地接口上管理所有内容。 并且控制本身可以从基于Electron等的应用程序中执行(或仅从浏览器中执行)。 与任何小事情。 在讨论此类应用程序的发行规模之前,请看一下游戏的规模。 某些演示版本(!!!)在归档文件(!!!)中占用5或更多GB。
●在为3个平台发布游戏时,现在使用的结构无法节省空间-实际上,您需要保留3个几乎相同的数据副本,尽管已压缩。
●当前版本的修补程序不会缓存其工作-每次重新计算所有文件的所有校验和时。 如果修补程序为客户端上已经存在的那些文件缓存结果,则可能会大大减少时间。 但是有一个难题-如果文件已损坏(或丢失),但是保存了该文件的缓存条目,则修补程序将跳过它,这会引起问题。
●当前版本不知道如何与多个服务器同时使用(除非您使用DNS进行轮询)-我想切换到“类似激流”的技术,以便可以同时使用多个服务器。 毫无疑问,将客户端用作数据源会引发许多法律问题,并且从一开始就更容易拒绝。
●如果要限制对更新的访问,则必须独立实现此逻辑。 实际上,这几乎不能称为缺点,因为每个人都可以对限制有自己的愿望。 如上所示,最简单的密钥限制-没有任何服务器部分-变得相当简单。
●一次只能为一个项目创建一个修补程序。 如果您要构建类似于Steam的软件,则已经需要一个完整的内容交付系统。 这是一个完全不同层次的项目。
我计划在实施“第二代”之后将修补程序本人置于公共领域-一个游戏内容交付系统,其中不仅包括演进的修补程序,还包括遥测模块(因为开发人员需要知道玩家的行为),云保存模块和其他一些模块。
如果您有一个非营利项目,并且需要修补程序,请给我写有关您的项目的详细信息,我将免费提供给您一份副本。 这里没有链接,因为这不是“ I PR”中心。
我很乐意回答您的问题。