UDN
Search public documentation:

PackagesAndNetworkingCH
English Translation
日本語訳
한국어

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

包和网络的高级详细介绍

文档概要:从网络角度来看和包相关的重要信息。

文档变更记录:最后一次由Michiel Hendriks更新,从 Two.包和网络中移植过来。最初作者是Mike Lambert (UdnStaff?)。

概述

如果您使用虚幻引擎的进行了编辑,那么您便会知道包是什么。所有的游戏内容都存储在包中:贴图、地图、代码等。包是一个“东西”的集合。包可以包含地图、贴图、代码、声音、音乐或者任何和它们相关的组合物。Unreal的目录结构适应了我们把东西比如贴图、音乐及地图等分别组织到不同地方的人类思维方法。所以一个地图制作人员可以把贴图和代码包含在他的地图中,从而不必保存地图的同时包含.u文件。但是,一般来说最好仍然遵守虚幻引擎的实例所设计的规则,以便获得理想效果。把地图放在.umap中(或者您可以指定您自己地图包扩展名)、把代码放在.u包中、把其它的资源放到.upk中。当您想提高unrealscript的编译性能时,您应该做的操作是分割包。请不要把游戏贴图和模型放到您的代码包中,而要把它们编译到包含虚构类的包中。这使得可以预先编译一次您的美术数据,然后您的代码的重新编译仅需要引用这些包(通过代码行 #obj load exec )即可。另一个优点是任何代码片段都可以作为一个较小的代码文件分配,从而避免了重新加载包含模型和贴图的整个包。

本文档是网络巨著的一部分。

ServerPackages (服务器包)

当服务器启动时,它加载了游戏所需要的所有包。它加载地图、它的相关贴图、声音及音乐包。它加载了在服务器传送命令行命令 ?game=XX 中所定义的游戏。它加载了 gamename .ini(ExampleGame.ini)中所提到的服务器包。一旦加载了这些包和类,将不会再向存储在服务器中的这些包列表中添加任何包。我们把这个列表称为serverpackages(服务器包)列表,并且这个列表将会被发送到加入到服务器中的每个客户端上。然后客户端自己下载这些包,以便可以在没有任何矛盾的情况下下载发送到那个客户端的任何actors。直到所有这些包加载完成之前都不会有任何代码执行。如果这是一个服务器,那么在加载了所有包之前它将不会接受任何客户端。这解释了在Unreal中当您加载关卡或服务器时那段较长的没有做出响应的延迟。其它额外的包可以通过使用 DynamicLoadObject() 进行加载,但是这些包仅可以在本地机器上加载。Unreal将不会通知任何连接的机器来加载这些包。关卡所需要加载的所有的包都会被自动地添加到包地图中。

这解释了您可能经历过的几个奇怪的现象。Mutators便是这样的一个实例,即使您对mutators没有兴趣,但是阅读关于它的内容是非常有用的,因为这些问题在其它很多地方也会出现。UnrealScript是通过在 GameInfo的 InitGame() 中使用 DynamicLoadObject() 函数来加载Mutators的。这个过程发生在加载完包列表之后,所以此时mutator包不会被加载到serverpackages中。Unreal中的mutators不具有这个问题,因为它们被包含在游戏代码本身中。任何仅操作服务器侧的mutators都是可以正常工作的,因为它们不需要发送那个包中任何actors到客户端。然而,如果mutator定义了需要发送到客户端的actors,那么它的actors在客户端上是不可见的。因为那个包将永远不会被添加到serverpackages列表中,客户端永远不会加载那个包,所以客户端不知道actors的默认属性,并且它是不可见的。解决问题的方法是明确地把有问题的mutator放到 gamename.ini 文件中的 [Engine.GameEngine] 部分的serverpackages数组中。这将会导致在游戏开始之前就加载mutator包,然后当 GameInfo 加载它时它已经存在于serverpackage中了,这样便可以在客户端上进行很好的工作了。

同样的原理也应用到了皮肤的包。用户的皮肤是在连接命令行选项中指定的,但直到 GameInfo 中的 Login 执行完之前将不会对其进行加载。这也是在所有 serverpackage被加载完之后进行加载的。所以,每个客户端都可以使得这个贴图复制给他们,但是它们显示它时,贴图包却不在客户端的本地内存中(因为它不在serverpackages中),所以您会看到丑陋的绿色皮肤问题。

GUIDs

每个包都有一个和它相关联的GUID,它是Globally Unique Identifier(全局唯一标识符)的简称。当保存包时每个包都会生成一个GUID。然后这个GUID可以用于辨别同一个包的不同版本或者辨别包之间的不同之处。GUID在生成128-位的ID时考虑到了时间及其它的信息,所以您可以确保不会巧合地使得两个GUID相同。可能有2128 = 2.4 x 1038 个GUIDs。我保证那数量已经足够了,所以您不必担心出现一样的GUID问题:)。

当Epic努力地确保GUIDs永远不会偶然地重复后,他们特意为您创建了一个实现这个目的的方法。在统一包的过程中,将会为新的包赋予和原始包一样的GUID,其它的有趣细节除外。在所有情况下认为它们都是一样的。我们在后面的部分来查看为什么会这样。

连接到服务器

当您连接到服务器时,过程大致如下所示:

  1. 服务器为每个包发送serverpackage列表和GUIDs。(Engine/UnConn -> UNetConnection::SendPackageMap)
  2. 服务器在[IpDrv.TcpNetDriver]中的 DownloadManagers 数组中找出客户端可以如何下载包的方法。它把这个列表发送到客户端(Engine/UnConn -> UNetConnection::SendPackageMap)。
  3. 客户端按照 [Core.System] -> Path 数组中的定义检查它的目录列表,查找和服务器发送的包的名称一样的包。它验证具有同样名称的包的GUID和服务器发送过来的包的GUID是否一样,如果不一样,将会导致 "Version Mismatch(版本不匹配)" 错误。
  4. 对于客户端在上面的步骤中没有找到的每个包,它都会检查 [Core.System]CachePathCacheExt 中定义的Cache目录,查找是否有和给定的GUID相同的包。
  5.  如果还没有找到任何包,则需要加载那个包。它检查查看那个包是否可以下载(正如在包的packageflags(包标志)中所定义的),并且使用 [IpDrv.TcpNetDriver]AllowDownloads 检查客户端是否允许下载。(在 "WELCOME" 情况下, Engine/UnPenLev -> UNetPendingLevel::NotifyReceivedText )。
  6. 客户端按照顺序检查列表,尝试每个下载管理器直到找到一个客户端上实际存在的一个包为止。(=Engine/UnConn= -> UNetConnection::ReceiveFile )如果没有找到任何包,它默认使用 Engine.ChannelDownload 。然后从服务器请求那种下载方式。(Engine/UnConn -> UNetConnection::ReceiveFile))
  7. 服务器检查这种方法是否可以,如果可以该如何下载。如果用户请求 Engine.ChannelDownload 时: (Engine/UnDownload) 。
    1.  服务器检查 [IpDrv.TcpNetDriver]AllowDownloads 布尔值是否为 true ,如果为真则不发送。
    2.  服务器发送文件到客户端。这可能会导致其它客户端的延迟,因为带宽已经用于发送文件到那个特定的客户端。文件发送的最大速度由 [IpDrv.TcpNetDriver] -> MaxClientRate 决定。
  8.  如果由于服务器不允许客户端下载包导致客户端不能下载,那么客户端将会放弃下载,并且连接服务器失败。

Conformed Packages(统一包)

统一的包用于在包的两个版本之间提供网络兼容性。它的工作方式是:假设我发行了一个版本1的产品,并且现在我想发行版本2。如果我不想让这个新版本阻止没有打补丁的用户在已经升级的服务器上玩游戏,并且我也不想让已经升级的用户在未打补丁的服务器上玩游戏,那么我必须使用版本统一。版本统一允许Unreal假设两个版本是一样的,并允许原始包的用户可以和升级版本的包的用户一同玩游戏。显然,包统一要求您具有您要统一到的原始版本,以便它知道如何告诉新的版本‘假装’为旧的版本。有两种方法来统一包:

  • 把您的原始版本的副本放到您游戏目录的 guires 目录中。它应该在您的Binaries目录附近。任何使用'ucc make'编译的版本都会自动地统一到 guires 中的版本。 *编译一个新版本的包的副本。然后使用统一命令开关来统一包。

当您编译包时,它在包中包含一个 称为 Generations 的数组,它存储了那个包统一版本的历史记录。(Core/UnObj -> UObject::SavePackage)所以当具有版本2的服务器看到具有版本1的客户端时,它会在Generations数组中查找版本1,并指导如何正确地和那个客户端进行通信。

这也是对统一包版本的原则做出了暗示。当您想统一包的版本3时,为了使它既可以统一到版本1也可以统一到版本2,您必须把版本3相对于版本2进行统一,而不是相对于版本1。版本2包含了版本1和版本2之间如何通信的信息。因为版本3需要这个信息和所有的版本相兼容,所以它必须相对于版本2进行统一。所以无论何时当您发行代码的不在内控制范围之内的公开发行的公共版本时,您最好把那个版本放到您的 guires 目录中,以便可以保证您内部编译的下一个版本(及最终的公开发行版本)和那里的所有版本相兼容。您的包的每个版本必须相对于它的前一个版本进行编译,而不是相对于第一个版本。所以当客户端是版本4而服务器短时版本2时,它会知道如何进行向后兼容赖来和没有升级的服务器端进行通信。

统一的过程实际上是由保持名称表同步组成。当版本2相对于版本1进行统一时,它保存了名称表,它会把版本1中出现的所有名称放在相同的位置。任何在版本2中出现的附加的名称都会显示在这个列表的后面。当服务器和客户端发送关于函数或变量的信息时,它们实际上是把索引发送到名称表中。所以即使客户端是版本2而服务器端是版本1,但是通过保证用于通信的名称表是一样的,便可以使得它们之间进行正常的通信。当把generation(版本代数)数组存储到磁盘中时,它存储了名称的数量及导出数据数量。这使得Unreal知道包的版本2可以使用名称表前X个元素(和版本1完全一样的元素)和版本1进行通信。

当统一本地化的包时,您应该以链的方式来统一它们,以便每种语言都可以和其它的语言协同工作。比如,如果您有’int(英语)', 'fra(法语)', 'esn(欧洲西班牙语)' 及 'deu(德语)'的包的本地化版本,现在您想使得'int(英语)'的包统一到'fra(法语)',然后把'esn(欧洲西班牙语)'包统一到 'deu(德语)'。语言的顺序没有关系,请参照 https://udn.epicgames.com/lists/showpost.php?list=unprog3&id=30760 页面。

包标记警告

您将会发现有几个包标记是非常有趣和必要的。如果您想在网络游戏中使用您的包并且允许把该包下载到客户端,那么您必须打开那个包的 AllowDownload 标记。请参照PackageFlags(包标记)参考指南。

包不匹配错误

下载具有同样名称但是具有不同GUID的包时将会导致包不匹配错误。这可以通过把那个包和那个包的前一个版本相统一来解决(因此创建了一个新的版本)。然而,如果服务器不知道两个包的校验和,那么反外挂保护将会生效。