游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

为游戏内容制作一个简单的补丁系统

发布时间:2013-02-18 15:43:57 Tags:,,,,

作者:Colt McAnlis

90年代初,电脑游戏兴起后,游戏开发商始终需要通过各种办法来快速修复漏洞、升级和更新内容—-于是,“游戏补丁”系统便应运而生。这个更新游戏的方法已经从PC延伸到游戏机,现在又进入手机游戏开发领域。

为你的游戏内容构造一个补丁系统可能需要费一番功夫,但一旦你获得这种系统,它就会成为你的开发工作室的强大工具。

为什么要有自己的补丁系统?

对于现代游戏开发商,最常见的游戏出售渠道就是数字推广服务,如Google Play、Steam、XBLA和Chrome Web Store等。除了向他们的用户营销游戏,这些推广服务通常替开发商承担了大部分将游戏内容传达给用户的工作。

然而,对于需要经常更新的游戏来说,这种推广服务进行的内容宿主进程便存在一些问题。例如,有些服务供应商在制作补丁时可能需要大量开支,或延迟新版本的发布。对于多人游戏,延迟可能导致客户端版本与服务器版本不同步,进而导致客户端无法直接升级。

解决这个难题的办法之一是让玩家不借助推广服务也能获得你的内容。通常来说,利用你自己的技术构建补丁系统,会使作为开发者的你拥有更多控制权,同时往往有助于你的公司和产品更加灵活地选择不同的平台。

直达用户

除了解决上述问题,补丁系统还给你直达用户的机会。现在,基本上所有平台都与网络直接关联,让忠实玩家保持愉悦便意味着我们需要时常倾听玩家社区的声音、解决他们的问题,并为他们提供新内容。补丁系统让你可以通过新内容、新闻和相关的通告向现有的玩家基础营销游戏。

你的玩家就等在那里,多补丁多快乐!

补丁系统概述

补丁系统通常都有三个组件:

1、一个构建服务器,用于生成技能组合和补丁(这个服务器归开发者所有)

2、一个内容服务器,用于分配技能组合和补丁

3、客户端,用于发现本地游戏和服务器版本的差异、检索资源和升级本地版本。

The components of a patching system(from gamasutra)

The components of a patching system(from gamasutra)

这三个组件就是补丁系统的三大支柱。如果你能更深入细节,你便可以创造出更多有趣的版本,但这些细节必须是针对特定的游戏,显然这一内容不属于本文的论述范围。

简单的补丁发现文件系统

《雷神之锤3》的源代码中包含了一个成功的补丁系统的基本案例。这个简单的系统允许补丁将新内容添加到文件系统中。新存档包含所有不同于原补丁/版本的资源。换句话说,这个系统用新存档覆盖旧内容。当游戏从硬盘中读取资源时,文件系统就会扫描存档,然后从资源中提取最新的版本。

在这个简单的系统中,安装原版游戏的电脑为各个进程补丁保留一个存档文件,各个存档都包含升级后的资源。相反地,安装新版本的电脑中就不会有过多的存档文件,而是只包含安装之时的游戏世界的状态的存档。

《雷神之锤3》中的简单系统为我们提供了一个解决更复杂问题的模型。你的补丁系统会随着时间越变越复杂。我们的补丁系统案例以《雷神之锤3》的模型为基础,将遵循以下三条原则:

1、大多数内容是已存档的。

2、新存档的内容优先于旧存档的内容。

3、我们彻底忽略了二进制补丁,反而将被完全替换掉的零散内容包括进来。

再说一次,假设随着资源更新,你将得到为数不多的存档中的绝大部分,而新内容会以额外存档的形式交付,并完全取代旧内容。

因此我们假设磁盘中有一系列存档。在加载时间,我们打开存档并将它们的文件列表并入全局目录。当游戏读取信息时,我们查阅目录以决定哪些是最新版本的资源,从哪个存档中读取信息。

升级建造系统

建造系统有点像祭神的典礼—-各家公司往往有自己的风格,以及严格的把关过程。我不打算描述建造系统的概念(或者告诉你怎么编写一个);相反地,我将假设你已经有了这么一个系统。

为一个建造系统生成补丁,你的建造系统必须产生一系列不同于之前版本的文件(比如,介于版本299和300,可能升级了27个纹理,删除了3个模型,更新了渲染DLL)。一旦你有能力完成这些东西,你就必须将这些信息整合到补丁解释中,描述如下。

在我们的补丁系统案例中,任何已经改变或添加的内容都要作为一个新补丁添加到存档中。很容易就能找出新文件:只要比较两个文件夹中的文件名称列表,旧列表中不存在的名称就代表新内容。但发现旧文件被修改就要困难一些了。例如,简单地调出文件的最后修改时间可能不太管用,这是取决于你的建造系统连接内容的方式。傻瓜式检查办法就是,比较两个版本文件夹中的所有二进制数据。

你的建造系统计算这些文件组差异的能力很大程度上取决于build系统的语言和工具。例如,如果你的建造系统是基于C++,一份二进制数据与一个40GB的游戏版本相比,可能会是一个非常艰难的过程。相反地,如果你的建造系统是基于Python(你可以将其简单地称作目录比较),你便能看到两个目录不同文件间的delta数据了。

图2是一个构建delta的案例。蓝色表示修改后的文件,红色表示删掉的文件,绿色表示新文件。在我们的补丁系统案例中,新的和修改后的文件都包含在存档中,见图右半部分。

Determining build deltas(from gamasutra)

Determining build deltas(from gamasutra)

创造一个补丁定义

你的建造系统在计算两个版本之间的差别后,下一步就是将数据合并成客户端能够读取的形式。补丁定义的作用就是列出版本中的重要变化,从而方便将客户端升级到最新的版本。

各个定义补丁都需要一些信息小部件来引导客户端升级。以下列举了补丁定义中应该具备的一些信息:

版本号—-什么版本?最好是整数,以便于查找。

目标范围—-如果你的游戏是全球发布,那么你的某些补丁/内容可能无法在某些地区发布。

添加的文件—-这个列表应该包括新存档和必须添加到本地版本中的零散文件。

移除的文件—-有时候,在建造过程中,旧版本会被完全替换掉,以免旧版本中的数据危及某些安全/私人信息。将文件从磁盘中完全移除有时候是很重要的。

二进制升级的文件—-对于需要直接就地修补的文件,可以显示一系列元组、修补的内容和使用的补丁文件。

补丁的价值—-玩家在游戏前是否需要这个补丁?或能否在后台运行?

决定要下载的文件

每完成一个版本的定义补丁,下一步工作就是让客户端读取信息。过程通常如下:

1、客户端查询补丁服务器,发送本地版本和其他元数据。

2、补丁服务器将某些文件信息形式回复给客户端。

3、客户端处理信息,开始索取新补丁以升级本地版本。

决定下载什么文件主要有两种方式(但有许多变体)—-客户端或服务器。你的选择很大程度上取决于你能使用到的引擎资源。

就服务器技术方面,最简单的途径就是保证在服务器上的基于文档的清单文件列出所有补丁以及各自的版本等。当客户端发送请求时,服务器就会将清单文件发送给客户端,然后由客户端处理升级本地版本所需的文件。

虽然执行起来不难,但这个方法有很大限制。显然,它需要客户端有一些高级的逻辑,以正确解析清单文件和生成请求列表。如果转换的内容比较大(如改变清单文件格式),客户端就很可能需要经过特殊处理,如在升级内容以前,客户端可能需要自己的补丁。

一个更复杂但更灵活的解决方案是,将所有版本数据放在补丁服务器上,列出数据库条目。客户端提供一些关于本地信息(容易解析URL)状态的简单元数据给服务器。之后,服务器计算客户端升级自身所需的步骤,并转换一个“升级脚本”给客户端用于直接执行。

这种基于服务器的方式的主要优点在于,升级本地客户端版本所需的计算工作全部由服务器完成。这也就是说,随着升级逻辑的变化,客户端只需要简单地作出相应的回应。

这还使得客户端在过程中储存更少的数据(游戏邦注:如客户端可能只需要储存它的范围和版本号)。服务器可以储存剩下的信息,并给客户端提供更先进的功能,如将多次补丁整合成一个升级请求。

执行补丁

在客户端升级本地版本形成一个清析的路径图以后,下一步就是升级数据。对于下载新存档的简单系统而言,升级内容也很容易—-下载二进制数字后写入磁盘便可。

最后,你会发现必须升级游戏客户端本身。如果游戏正在运行,这就不好办了。为了解决这个问题,大多数PC游戏会分配一个用于查看补丁和升级本地信息的独立应用,包括可执行的代码。一般来说,这些应用容易保持独立,可以在创造补丁后启动游戏本身。

对于嵌入式应用,执行补丁就更麻烦一点了。例如,在游戏机中,基本数据是存在DVD中的,所以补丁必须写入硬盘驱动中,然后查看内容。我不太了解手机平台的补丁要求。幸好,大多数平台包含API,可以处理补丁执行的过程,因此会更简单一些。

决定删除哪些文件

客户端积累的补丁会越来越多,其中有一些可能已经不再有用了。如果新版本的补丁已经包含旧版本中的全部文件,那么旧版本中的文件就不需要了。为了节约客户端所在机器的磁盘空间,最好能识别出这些无用的文件并将其删除。

一份不再使用的存档会被新存档完全替代。为了测试这个效果,你必须调整建造系统,这样目标补丁存档才能询问新版本,以决定是否需要删除;基于你的建造系统,添加过程可能很容易,也可能非常耗时耗力,所以你要保证在执行补丁以前你的系统是成熟的。

注意

二进制的文件补丁

如果你搜索“游戏内容的补丁”,你会搜到许多描述如何在二进制水平上微调文件的非磁盘内容的文章。一般来说,这种做法就是计算文件中的差别,然后只向客户端发送差异,从而减少转换工作并加快补丁过程。

不幸的是,大多数关于补丁生成的研究是围绕如何修补可执行文件展开的。极少研究专注于修补二进制资源,如纹理、模型和声音。

因此,本文描述的补丁系统并不强调传统概念中的“补丁”,而更关注使我们能够将特定资源分配给客户端的过程,这样我们才能更容易地将客户端升级到最新版本。随着现代压缩技术的进步,这个过程可以使转换的工作量大大减少。不过,如果你仍然需要创造一些更传统的补丁,不妨参考以下建议:

作为新手,我建议你看一看XDelta,这是一种相当简单明了的命令行工具:你只需要运行简单的命令就能制作补丁,然后执行另一个命令来执行补丁。这个应用已经开放源代码,所以你可以将它放到客户端中可执行文件的自定义部分中。

XDelta的压缩效果可能不好,但就补丁而言,它还是相当实用的。值得一提的是,XDelta不会给存档文件产生非常小的delta,这主要是因为它使用的前向搜索模型与存档文件中罗列的连续文件数据匹配得不好。

你很快便会发现,在两份存档文件中运行XDelta并不能为你节省开支。但是这么做却能为你的各份资源生成补丁文件,并在存档/压缩后将其传达给客户端。

如果你使用你的补丁系统去处理二进制内容,我不得不提醒你,这将是一个非常棘手的问题。虽然容易想到一个简单的解决方案,但你很快就会发现每一个文件都需要多个补丁,所以看似简单的方法通常都不管用。你还会发现,两份文件之间的delta并不能呈线性—-可能包含缺失部分、插入部分和替换部分,这很难追踪到。所以,在你这么做以前,请确保你已经有一套可靠的管理方案。

文件处理和限制系统

新操作系统对执行内容施加了某些限制,大多数情况下应用都需要在执行以前进行注册。一旦应用完成了注册,系统就可以检测到应用的任何变更,无论变更是偶然产生的还是恶意编码。

如何注册你的资源和正确分配给某个平台上的客户端,这需要你自己练习。各个OS似乎都有各自的政策,你的行动取决于游戏的发布系统。

结论

执行补丁系统是需要一定努力的,但这种努力是值得的。补丁系统能使你更加灵活地控制游戏,为玩家提供更好的体验,为新内容提供更多营销机会。

源代码

我的Github帐号上有一些非常简单的源代码,可以用来执行描述如下的补丁系统。(注意,是用Python写的)。代码包括一个模拟建造系统,一个内容服务器和一个客户端。你可以根据以下命令使用这个系统:

1、python build/gen_补丁.py
2、python server/httpd.py
3、python client/client.py

命令(1)从两个版本中生成一个补丁

命令(2)启动分配补丁的服务器

命令(3)计算本地版本的差异(假设客户端已经有最初的版本),然后从内容服务器中提取delta 补丁。

本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

In-depth: A simple system to patch your game content

By Colt McAnlis

Since the rise of PC games in the early ’90s, game developers have needed ways to quickly issue fixes, updated builds, and new content to existing users – hence the rise of ‘game patch’ systems. Over time, this method of updating games has made its ways from PCs to consoles, and is now trickling into mobile development.

It may take some effort to build and use a patching system for your game content, but once you’ve got such a system up and running, it’s a very powerful tool for your development studio.

Why have your own patching system?

For modern game developers, the most popular avenue to sell games is through one of many digital distribution services like Google Play, Steam, XBLA, and the Chrome Web Store. Besides marketing games to their users, these distribution services generally handle the lion’s share of transferring game content to customers on developers’ behalf.

For games that need to update frequently, however, the content hosting process from such distribution services can be problematic. For example, some of the services can introduce significant costs in patch creation, or delays in issuing updated builds to users. For multiplayer games, delays can let client builds get out of sync with server builds, with no way of triggering an update directly.

One opportunity to work around such hurdles is in embracing the ability to make your content available outside of distribution services. In general, building your game technology on top of your own patching system gives you a great deal of control as a developer, and generally provides options that give your company and products a great deal of flexibility to move between platforms.

Reach your users directly

In addition to solving the problems listed above, a patching system gives you the opportunity to reach your users directly. Nowadays every game platform is constantly connected to the interwebs, and keeping a long tail of customers happy means constantly listening to the community, fixing their issues, and furnishing new content to them. A patching system lets you market to existing users with new content, as well as news, updates, and notices relating to your game.

So, be your name Buxum or Bixby or Bray, your mountain of users is waiting, patch them to happiness, and be on your way!

Patching system overview

Patching systems generally have 3 components:

A build server that generates builds and patches (this server resides with the developer)

A content server from which to distribute builds and patches

A user client that can detect differences between the local and server versions of a game, retrieve assets, and update the local version

Figure 1: The components of a patching system

At its core, these are the three pillars of a patching system. You can create more fancy versions once you start getting into details, but such details tend to be game-specific and are beyond the scope of this article.

A simple patch-aware file system

The Quake 3 source code contains an elementary example of a successful patching system. This simple system allows a patch to append new archives to the file system. The new archives contain all the assets that differ from previous patches/builds. Another way to describe this system is that new archives are overlaid on top of existing archives. When an asset is to be read in from disk, the file system traverses the archives and selects the newest version of the asset.

In this simple system, over time a user who installed the original game would have multiple archive files from each progressive patch, each archive containing an updated set of assets. In contrast, a user who acquired the game much later in its life span would not have a plethora of archive files on their disk, but rather a collapsed archive representing the proper state of the world as of the time of their installation.

The Quake 3 model is hard to beat for simplicity, and offers a good starting point to address more complex topics as your patching system gets more sophisticated. The example patching system that we will implement is thus based on the Quake 3 model, and has the following rules:

The majority of the content is archived.

Content in newer archives take precedence over content in older archives.Archived content is generally not patched, but rather replaced entirely.

We ignore binary patching altogether and instead include loose assets that are replaced entirely.

To restate, we assume that as far as assets go, you’ll have the lion’s share in a small number of archives, and that new content will be shipped out in the form of additional archives, the contents of which will wholly replace older content.

We thus assume that there will be a series of archive files on disk. At load time, we open the archives and merge their file lists into a global dictionary. When it’s time to read an asset, we consult the dictionary to determine what the newest version of the asset is, and which archive to pull the asset from.

Updating your build system for patching

Build systems are a bit like sacred rituals – each company tends to have its own flavor and guards its process heavily. I’m not going to cover the concepts of a build system (or tell you how to write one); rather I assume that you’ve got that under wraps.

To generate a patch for a build, your build system needs to generate a list of the files that are different from the prior build (for example, between build 299 and 300, 27 textures may have been updated, 3 models may have been deleted, and the rendering DLL may have been updated). Once you have the ability to generate this type of delta list, you need to combine the information into a patch definition, which is described below.

For our example patching system, any content that has changed or that has been added is included in the archive for a new patch. Finding new files is generally easy: Simply compare the file name listings between two folders to find what didn’t exist before. Finding existing files that have been modified can be trickier. For instance, simply testing the last-modified time of files may not work because of how your build system touches content. The fool-proof approach is to use a brute-force comparison between all the binary data in two build folders.

The ease with which your build system can compute these types of file set differences depends greatly on the language and tools of the build system. For example, if your build system is driven by C++, a binary data compare of a 40GB build would be a gnarly and less-than-ideal process. In contrast, if your build system is driven by Python, you can simply call dircmp, which gives you all the proper delta data between files in two directories.

Figure 2 below shows an example of build deltas. Blue indicates modified files, red indicates deleted files, and green indicates new files. In our example patching system, the new and modified files are included in archives, shown on the right.

Figure 2: Determining build deltas

Creating a patch definition

Once your build system can calculate what’s changed between two builds, the next step is to merge that data into some form that allows the client to consume it. Patch definitions are used to list key changes in builds over time, such that we can minimalistically update the client to the latest build.

We need a few pieces of information in each patch definition to help guide a client to the required actions to update itself. Here are a few examples of the type of information that should go into a patch definition:

build number – What build is this? A simple integer is easiest to track.

target region – If you distribute your game internationally, there may be restrictions on the types of patches/content you can ship to a specific region.

files to add – This list should include new archives, as well as specific loose files that need to be added to the local build.

files to remove – At times, in the course of builds, you’ll succeed in completely replacing an old build, or there may be some security/privacy risk with old data existing on the client disk. Having the ability to remove files from disk in these situations is useful.

files to binary update – For files that need direct, in-place binary patching, this can present a list of tuples, the content to be patched, and the patch file to use.

patch importance – Is this patch required before the user can play? Or can it be streamed in the background?

Determining what files to download

Once you can create per-build patch definitions, the next step is to allow the client to consume this information. The process is generally as follows:

The client queries the patch server, sending the local version and other metadata.

The patch server responds with some form of file information.

The client processes the information and begins requesting new patch data to update the local copy.

There are two primary ways (with lots of variants) to make the determination of what files to download – at the client level or at the server level. Your choice of implementation is highly dependent on the engineering resources that are available to you.

The simplest approach in terms of server technology is to keep a text-based manifest file on the server that lists all the patches, their versions etc. This entire manifest file is passed to the client upon request, and the client is responsible for building up the series of file requests to update the local copy.

While simple to implement, this approach quickly runs into limitations. Significantly, this approach requires some advanced logic built into the client to parse the manifest file and generate the request list properly. If a large content-shift occurs (for example, you change the manifest file format), the client will likely need special processing to handle the changes, and may require a patch of the client itself before it’s able to update the content.

A much more complex but scalable solution is to keep all the version data on the patch server, listed as entries in a database. The client provides some simple metadata about the state of the local data (easily encodable in a URL) to the server. The server then computes the proper series of actions the client needs to perform in order to update itself, and transfers an ‘update script’ to the client for direct execution.

The main benefit of this server-based approach is that the computation of what the client needs to do in order to update the local state is all handled on the server. Thus, as the update logic changes, the client can remain neutral to those issues and simply react accordingly.

This also allows the client to generally store less data needed for the update process (for instance, the client may only need to store its region and build number). The server can store the rest of the information needed to complete the update process, as well as provide the client with more advanced functionality, like grouping multiple patches into a single request update action.

Applying patches

Once the client has a clear roadmap of what’s required to update the local build, the next step is to actually update the data. For our simple system of downloading new archives, updating content is easy – we download the bits and write them to disk. Done. Let’s get tacos.

Eventually you will encounter a situation where you need to update the game client itself. This can be tricky if the game is running. To solve this problem, most PC games distribute a separate application that checks for patches and updates the local state, including the executable code. Typically these applications are easiest to generate as standalone applications that can patch and then launch the game itself.

For embedded environments, applying patches is a bit trickier. For example, on consoles the base data is shipped on DVD, so you generally have to write patches to the hard drive and check for content there first. Mobile platforms have a whole separate set of requirements that I won’t get into. Thankfully, most of those platforms contain APIs to help out with this process of applying patches, which makes things a bit easier.

Determining what files to delete

Over time a client can accumulate many patches, and in some cases, it may have lots of data that is no longer needed. For example, if all of the files in a given patch have been replaced by newer versions in subsequent patches, then the files in the original patch will never be used and are not needed. To keep the user’s machine from consuming disk space unnecessarily, it’s helpful to identify such instances and allow a patch to delete files from disk.

An archive that’s no longer needed is one whose assets are entirely replaced by newer archives. To test for this, your build system needs to be modified such that a target patch-archive has the ability to query newer builds to determine if it’s relevant any more; depending on how your build system is set up, adding this processing can be easy, or months of man-work, so make sure you take full stock of your system before trudging down the path of enlightenment.

Notes

Binary-level file patches

If you do a search for ‘patching game content’, your results will include numerous articles that describe how to minimally modify the on-disk contents of a file at a binary level. Typically this is done by computing the difference for a file and shipping only the difference to the client, resulting in a smaller transfer and a faster patching process.

Unfortunately most of the research on patch generation revolves around how to patch executable files (see e.g. Courgette (source), bsdiff, and DCA). Very little (if any) research has been focused on patching binary assets like textures, models, and sounds.

Consequently the patching system described in this article focuses less on the traditional notion of a ‘patch’ and more on a process that allows us to distribute specific assets to clients so that we can easily update the clients to the latest build. With modern compression technologies this process can result in the transfer of fairly small files to users, and the mental overhead of maintaining this type of system is significantly lower. If however you’re one of the brave souls who needs to do the more traditional version of patching, here are some quick notes.

As a starter, I suggest taking a look at XDelta, which is a fairly straightforward command-line tool: You can run a simple command to create a patch, and another command to apply a patch to a file. The app is open-source so that you can build it into a custom part of your client executable.

I haven’t seen XDelta produce amazing compression results, but it does the job fairly well in terms of patching. It’s also worth noting that XDelta doesn’t produce very small deltas for archive files, mainly because the predictive look-ahead model that it uses does not fare well with the contiguous file data laid out in archive files.

You’ll quickly find that simply running XDelta on two archive files won’t produce the net savings that you’re looking to gain. As such, it may be more beneficial to generate patch files for each of your assets, and archive/compress those for transfer to the client.

If you’d like to roll your own patching system for arbitrary binary content, be warned that this is a tricky problem. While it’s easy to come up with a naive solution, you’ll soon realize that there can be multiple patch points to a single file, which usually throws simple solutions out the window. You’ll also realize that the delta between two files may not be linear – it may include deletions, insertions, and replacements, which is difficult to track. Make sure you’ve got manager approval before going down this route ;)

File processing and restricted systems

Newer operating systems place restrictions on what content can be executed, mostly requiring that applications be signed before they can be executed. Once an application is signed, the system can detect any change to the application, whether the change is introduced accidentally or by malicious code.

How to sign your assets and properly distribute/patch them to the client on a given platform is left as an exercise to you. Each OS seems to have its own policies on that process, and your actions will depend on the systems to which you ship your game.

Conclusion

It takes some effort to implement a patching system, but the benefits are well worth it. A patching system gives you increased flexibility and control over your game, lets you provide a better user experience, and gives you the opportunity to market new content to your users.

Source code

My Github account has some very simple source code that implements the patching system described above. (Be warned, it uses Python.) The code includes a mock build system, a content server, and a client. You can use this system with the following commands:

python build/gen_patch.py

python server/httpd.py

python client/client.py

Command (1) generates a patch from two builds.

Command (2) starts a server from which to distribute patches.

Command (3) calculates differences in the local version (assuming the client already has the initial build) and pulls down a delta patch from the content server.(source:gamasutra)


上一篇:

下一篇: