跳转到主要内容

论架构

第 1 章“虚拟世界导论”描述了虚拟世界服务器是如何按四个不同的功能层次(驱动程序、mudlib、世界模型、实例化)构建起来的。这是设计者最关心的划分方式。不过,至少从抽象层面上理解系统的其余部分是如何拼合在一起的,仍是必要的。下面要讨论的就是这些。

总体架构

实际的架构因开发者而异,但它们几乎都可以看成同一种通用做法的变体,这种做法的着眼点是可靠性、可扩展性与可维护性。图 2.1 展示了这种总体架构。

图 2.1
图 2.1 总体架构

下面说说它是怎么运作的。

系统的核心是用户数据库。这是一套功能强大的软件,独占一台速度很快、存储空间很大的机器。它保存着所有注册到这个虚拟世界的玩家的记录。任何人都必须先登录,才能访问内容;而登录就意味着要到用户数据库里去核对。整个系统里负责计费、客户服务管理与打补丁的其他部分(图 2.1 中没有画出)同样可以访问用户数据库。这是一套非常重要的工业级系统,因此通常是买现成的,而不是自己内部开发。

玩家接入虚拟世界有若干种选择。主要的一种是用 PC 上的客户端连进来(或者用家用主机、Macintosh 或 Linux 机器——这些客户端呈现的界面都相同,所以虚拟世界既不知道、也不在乎它们跑在什么操作系统之下)。玩家也可以用浏览器连上服务器,不过这么做的远不如用专门定制客户端的多;虽然确实有一些文字虚拟世界拥有不错的基于浏览器的(Java)客户端,但还没有人鲁莽到为一款成熟的 3D 图形虚拟世界做同样的事。同样,手机离能真正拿来玩还差得远。不过,浏览器和手机都可以用来从虚拟世界获取信息(例如新闻、虚拟天气),或者修改常设指令(例如训练安排、待售商品的定价)。手机与电子邮件可以用来向玩家通报正在发生的事件,但通常不提供参与其中的机会。

当有连接从外部接入时,就由前端(front end)来处理。前端既能与自己的目标平台(客户端、浏览器/电子邮件、手机)通信,也能与用户数据库通信。图 2.1 把各个前端画成彼此独立的实体,因为它们各自是独立的进程;不过在实际部署中,它们可以合并到一台机器上,也可以拆到好几台机器上(例如按现实世界的地理位置来分)。

虚拟世界的每一个化身都托管在服务器集群上,集群又称 shard(分片)1。

shard 的架构稍后会详细讲述,不过眼下不妨就把一个 shard 简单地看成一个服务器实体。前端在准许某个玩家访问内容之后,要么向与该内容相关的服务器发起查询(浏览器或手机的连接属于这种情形),要么把控制权直接交给服务器本身(客户端连接属于这种情形)。服务器为支持虚拟世界,并不需要访问用户数据库,因为服务器打交道的是角色,而不是玩家。但客户支持人员打交道的是玩家,而不是角色,所以他们确实需要在虚拟世界内部就能查看用户数据库。这件事有时是靠一个单独的数据库工具来做的:在照常使用普通客户端的同时,他们可以调用这个工具;但并非总是如此。尤其是,如果客户端把玩家的 PC 整个占用 2,那就得有一个特殊的管理客户端,把数据库工具内建其中。

如果是这样,那么要么客户端必须能保持两条连接(一条连服务器,一条连用户数据库),要么——更省事的办法是——服务器本身在需要时发出数据库请求,再把结果送回客户端。


  1. 这个叫法源于《网络创世纪》(Ultima Online)里的一段虚构设定,为的是解释一个据说只有一份的世界怎么会有多份副本。这就好像一面映照世界的镜子碎成了无数细小的碎片,每一片这样的碎片都映出原本的世界,只是映法略有不同。 ↩︎

  2. 这里取其“客户端运行期间不许其他进程运行”之义,以此给黑客添麻烦。但这样做也会给非黑客添麻烦,所以客户端也许退而求其次,只求独占屏幕。玩家对此举一般仍然不领情。 ↩︎

服务器架构

服务器集群承载着虚拟世界的各个实例。集群之间可能存在由程序设定的差异(例如,在某个集群里可以攻击其他玩家,在另一个集群里则不行),但这些差异在形式上通常很小,效果上则未必。一个集群里有多少台服务器,因实现而异,但通常也就六台上下。集群的数量同样不等:一款产品越受欢迎,增设的集群就越多(《无尽的任务》——EverQuest——在 2000 年达到 40 个服务器集群,高峰时段平均每个集群同时在线约 1,500 名玩家)。

图 2.2 展示了一个服务器集群通常是怎么配置的。

图 2.2
图 2.2 服务器集群架构

各个客户端分别连接到各自的子服务器。理想情况下,每台子服务器完成的工作量都与其他子服务器相同(实践中,这意味着它处理的玩家数量相同)。子服务器可以访问一个共享的角色数据库 1,其中存放着本集群中玩家角色的持久数据 2。它可能是某个用于管理玩家访问权限的全系统账户数据库的一部分,也可能不是。出于可维护性的考虑,大型虚拟世界通常会为环境数据单独维护一个数据库,这个数据库又可能划分成模板数据库、脚本语言数据库和实例化数据库(第 1 章描述过这种划分)。

此外,这些世界/环境数据库是共享的(如图所示),还是各台子服务器本地各有一份,取决于负载均衡的实现方式。以《无尽的任务》的分区系统为例,它用不着那么大的环境数据库,由各台子服务器各自管理自己那份就行;各台子服务器之间只有角色数据库需要共享,供角色在分区之间移动时使用。当然,这样做要在别的方面付出代价,这一点我们稍后就会谈到。

服务器集群在硬件上就是一堆 PC 或者更强悍的硬件(通常运行着某种 Unix 变体),用局域网连在一起。

图 2.2 画出了服务器各组成部分之间通常的通信路径;但事实上,只要有需要 3,任何一台子服务器都能与任何其他子服务器通信。

因此,在子服务器之间转交玩家,既可以采取正式的做法(经由数据库),也可以采取非正式的做法(靠机器之间直接协商)。各台子服务器也可能与用户数据库有联系,如图 2.1 所示(图 2.2 里没有画这条线,免得这张图被虚线画死)。

我要指出的是,学术界正在试验别的架构,尤其是科幻小说里最受青睐的那种分布式架构(例如《虚拟偶像》和《异域》都用了它)。不过,由于会带来安全与责任问题,这类架构以及涉及组播(multicast)的架构,都不太可能在学术界之外得到应用。


  1. 这在《MUD1》里叫作 persona file(角色档案)。 ↩︎

  2. 对于那些角色可以在不同化身之间迁移的虚拟世界,可能需要一个由所有虚拟世界共用的单一角色数据库(就像用户数据库那样)。另一方面,如果转移必须手工完成,开发者就有理由对这项服务收费(《无尽的任务》就是这么做的;到 2002 年年中,他们靠这项服务赚了 100 多万美元)。 ↩︎

  3. 这里“需要”的确切定义取决于具体的虚拟世界。 ↩︎

负载均衡

幸运的是,负载均衡这门学问虽然玄奥,却并不是设计师需要直接操心的事。不过,技术专家最终采用什么方案、会带来什么后果,设计师还是得心里有数——尤其是因为碰上这件事,多半会有人来征询他们的意见。

理想情况下,虚拟世界应该跑在一台性能极强的计算机上。文字世界已经是这样了 1。图形世界可能也会走上同一条路 2,但就目前而言,服务器端新添的算力仍然有很多地方可花:

  • 增加每个化身里同时存在的玩家数量(100,000 名玩家分布在 50 台服务器上是一回事,100,000 名玩家挤在 1 台服务器上又是另一回事)。

  • 提高虚拟世界运行时的细节精细度(例如在雪地上留下脚印,脚印会随着雪融化或新雪落下而渐渐消失)。

  • 改进虚拟世界居民的人工智能(质量与数量两方面)。

因此,在相当长一段时间里,虚拟世界很可能会把抛给它们的额外算力统统吸走。便宜的机器仍然会成集群使用,因为用 8 台算力为 X 的计算机,总比用 1 台算力为 8X 的计算机更省钱(不过,随着新机器不断加入,管理开销会拉低整体效率,所以没人会用 64 台算力为 X/8 的计算机) 3。

那么,既然分布式服务器注定会长期存在,这对设计师有什么影响?

好吧,虚拟世界太大了,装不进一台计算机,所以必须划分到好几台计算机上。

算是吧……

如果虚拟世界是惰性的,也就是说,里面什么都不会发生,那它根本不需要任何计算机,只要待在一个磁盘组里就行了。只有当里面有事发生时,它才需要计算机。所以,问题就在于确保活动分散到各台子服务器(sub-server)上,让它们都能应付自己手上的工作;换句话说,计算负载必须以均衡的方式分担(“负载均衡”这个说法就是这么来的)。

虚拟世界里最大的活动来源是玩家群体。玩家只要身处虚拟世界,就无时无刻不在与它交互。仅仅是从一个地点移动到另一个地点,就得通知所有(在虚拟世界里)能看见你这么做的其他角色。没错,有些活动比其他活动更耗资源,但它们往往并不专属于某种玩法。问题在于正在执行的动作数量本身,而不在于单个动作的效率。因此,虚拟世界里的负载均衡,通常就意味着确保集群里的每台子服务器都连上大致相同数量的玩家。

最直白的做法,是把每个新接入的玩家分配给当前玩家数最少的那台子服务器。然而事实证明,要在不引入巨大开销的前提下做到这一点,相当困难。为了反映某个动作而更新实例化数据库时,必须锁住相关记录,以免其他子服务器同时改动它们(例如两个角色同时试图捡起同一个物件)。子服务器需要锁住它在一次动作中可能用到的所有记录,执行检查以确保该动作可行,发出必要的更新请求,然后解锁这些记录。这里的加锁/解锁操作非常多。要是有办法把实例化数据库里的某些记录长期占住,又不必交出对它们的控制权,那就太方便了。有没有哪几类记录可以轻松做到这一点呢?

从统计上看,玩家执行的大多数动作都与移动有关 4。

玩家想把角色从 A 移到 B,子服务器就得锁住地点 B,检查它是不是空的;如果是空的,就把玩家移进地点 B,然后解锁。地点 A 也必须锁住,这样,那些以为角色还在地点 A、想对它做点什么的人(例如把角色传送到地点 C),就不会在不经意间把事情搞砸。许多其他常用命令(尤其是 get/drop 以及跟通信有关的命令)同样是基于地点的。

出于这些原因,服务器通常按(虚拟的)地理界线来划分对虚拟世界的职责。子服务器可以长时间锁住实例化数据库里的地点记录;事实上,只要系统编写得正确,它根本不必给这些记录加锁——仅仅因为别的子服务器都不这么做,它就已经获得了隐式的控制权。

把到目前为止的论证归纳一下:我们希望把玩家尽量均匀地分散到各台子服务器上,可是直白的做法会给数据库访问带来太多开销;效率高得多的做法是按地理位置来划分。问题在于:用这种划分模型,能不能得到负载均衡?

答案是:能。至于具体怎么实现,则取决于虚拟世界本身。

基本上有两种做法:固定负载均衡与动态负载均衡。前者以《无尽的任务》(EverQuest,EQ)为代表,把一块预先划定的地理位置(也就是一个分区,zone)交给一台子服务器负责 5;后者以《阿瑟龙的召唤》(Asheron’s Call)为代表,在子服务器之间转移对各地理位置的职责。

固定负载均衡:

  • 更容易实现。

  • 可以拆分实例化数据库,并让它留在子服务器本地,从而提高效率。

  • 允许客户端提前判断需要哪些纹理贴图,并预载入显卡内存。

动态负载均衡:

  • 地形无缝(能一直望到地平线 6)。

  • 边界不是实体边界(追着你的怪物不会卡在分区边缘)。

  • 对负载的均衡效果更好。

这一切对设计意味着什么?

这是技术反过来给设计设限的一个例子。采用固定负载均衡时,可以把各个分区做得更有各自的个性:它们之间那些“实体”屏障,让相邻分区之间的差别可以非常悬殊。玩家从一个分区走到另一个分区,可以指望在另一头看到不一样的东西。而在无缝的世界里,这种反差必须有更充分的道理,否则就会显得格格不入。

怪物之类的内容无法越过分区边界,这意味着玩家在这种世界里的战术,会不同于在另一种世界里的战术——在后一种世界里,他们要是把某个生物惹得火冒三丈,那东西随时可能穷追不舍。

维多利亚时代伦敦的警察(在法律上)不能越过辖区边界:开膛手杰克(Jack the Ripper)在一个警区杀了人,再跑进另一个警区,警察就不许追过去;要是他们能追,他就得重新考虑自己的逃跑策略了。

分区式世界在“快闪人群”(flash crowd,指人们看到有意思的消息就瞬间一齐出现在相近的地方;这个词出自拉里·尼文 1973 年的一篇短篇小说 7)上有些麻烦。大多数时候,每台子服务器处理的玩家数量都差不多。但有时候会出点事,让所有人都想往同一片地方凑 8。也许是一头气派的龙罕见地刷了出来,也许是婚礼、公会集会之类的社交活动。不管是什么,总之突然之间,想进某台服务器的人就超过了它能承受的数量。遇到这种情况,设计师必须决定怎么办。人们想进去时,就直接给他们显示一条“分区已满”的消息?还是放他们进去,让客户服务部门去应付随之而来的延迟投诉?又或者,把虚拟世界的虚构设定安排成:所有人都想挤进同一个分区,本身就是说不通的?

《卡米洛特的黑暗时代》(Dark Age of Camelot,DAoC)把它的玩家群体划分成三个“界域”(realm)。一个界域的成员不能进入另一个界域的地盘。不过,他们可以进入两个界域之间的无人地带,界域对界域的战争就发生在这里。由此带来的结果是:除非 DAoC 的玩家群体分布严重失衡,否则任何一个界域里通常只有三分之一的在线玩家。这对负载均衡来说再好不过。而且,界域是分区的集合,它们本身并不是分区。每个界域由 13 个户外分区(64K × 64K 的方格)、5 个地下城加 1 座城市组成。一台子服务器没有任何理由只能处理来自一个界域的分区;如果(出于某种原因)75% 的玩家都在同一个界域里,那么负责该界域的那些子服务器虽然从这个界域分到的负载很重,但它们同时还管着其他(更稀疏的)界域,从那些界域分到的负载自然就更少。

《无尽的任务》的玩家能注意到子服务器是如何按分区分担责任的,而 DAoC 的玩家注意不到。为什么注意不到?这要归因于后一款游戏的世界设计。


  1. 直到我把《MUD2》那台 33MHz 的服务器换成一台气派的 50MHz 新服务器,处理器速度才终于不再是《MUD2》的问题。 ↩︎

  2. 《子午线 59》(Meridian 59)采用单服务器架构,因此上线时每个化身最多只能容纳 200 名玩家。《魔剑》(Shadowbane)也是单服务器架构,但跑在性能略强一些的硬件上。 ↩︎

  3. 其实还没有人试过。虽然这种并行处理架构在许多商业计算应用里经常出问题,但对虚拟世界的某些划分方式来说,它也许没问题。 ↩︎

  4. 《MUD2》里这个比例超过 50%;图形化的虚拟世界还要更高——90% 甚至更多——因为在图形世界里,玩家要去任何地方都得走更多步。随着点击式界面的出现,这一点正在改变:你想去什么地方,点一下那里就行,不必再朝那个方向指过去。 ↩︎

  5. 应当指出,一台子服务器可以同时处理不止一个分区。 ↩︎

  6. 采用固定负载均衡的世界,如果像《网络创世纪》(Ultima Online)那样由许多地块拼成,也可以让跨服务器的边界看不出来。不过,交互跨越服务器边界时,可能会出现怪事(例如把箭射过边界)。在采用动态负载均衡的《阿瑟龙的召唤》里,互相发生交互的角色会被移到同一台服务器上,以减少跨服务器通信造成的混乱。 ↩︎

  7. 拉里·尼文,《快闪人群》(Flash Crowd)。拉里·尼文,《马的飞行》(The Flight of the Horse)。纽约:巴兰坦出版社,1973。 ↩︎

  8. 如果人群聚得特别集中,无缝世界也会遇到同样的问题。这种情况比分区式世界少见,但也正因为少见,一旦发生就更难处理。 ↩︎

别的事情也会发生

虽然支撑虚拟世界的硬件上,负载主要来自玩家,但玩家并不是唯一的来源。不管有没有玩家在场,都会有事情发生。这些事里有些是机械式的:虚拟世界的太阳照常升起、落下,天气照常来去,全都不管周围有没有玩家。对于一个细节极其丰富的世界来说,这些加起来可能意味着相当可观的工作量(一阵微风吹动树上的一片片叶子,其中一片飘落下来,掉进小溪,溪水懒洋洋地把它送进河流,再由河流送进大海)。不过,眼下要做出这么复杂的虚拟世界,还差得远呢。

更麻烦的,是虚拟世界里那些虚拟生物。它们通常被称为 mobiles 1(活动体,简称 mob),代表着居住在虚拟世界里的怪物和非玩家角色。第 4 章“世界设计”会更详细地讨论它们,但眼下让我们操心的是:它们的行为必须显得可信。这需要人工智能技术,而这类技术吞噬起计算资源来,别的东西都比不上。哪怕只是简单的寻路,也是 CPU 周期填不满的无底洞。要是能有一座虚拟城市,住着 10 万个虚拟居民,每个居民都在实时决定自己的虚拟人生怎么过,那该多好。不过,要等到这一天,我们恐怕还得等上一阵子。

如果设计者想要的 mobiles 数量超出了服务器能承受的范围,他们就必须拿出管理这些 mobiles 的办法。经典的做法是:暂停处理那些行为不会被玩家直接看到的 mobiles。造成 AI 负载的,不是一台子服务器上有多少 mobiles,而是同一时刻处于活动状态的有多少。

设想一座村庄里的一群哥布林。附近没有玩家时,让这些哥布林做任何事都没有意义。当然,子服务器在负载不太重的时候,可以让它们稍微走动走动,但玩家有优先权。只有当一群冒险者出现时,才轮到激活这些哥布林:让它们表现得聪明一点,让玩家们好好费一番功夫。玩家一走,死掉的哥布林就可以重新生成,等着下一批冒险者。

如果设计者想要的 mobiles 超过了程序员所说的上限,他们可能会忍不住采用这种“就近激活”的做法。不过,他们必须意识到:自己做的每一个决定都有后果。

在这个例子里,后果落在因果关系上。如果一棵树在沙漠里倒下,它会发出声响吗?可要是采用就近激活,这棵树压根儿不会倒下。

再设想第二支哥布林劫掠队。它从营地出发,杀掉村民的几只羊,然后带着战利品回到营地。村民们很生气,出钱雇玩家去杀掉这些哥布林。

这是够玩一个晚上的任务。

而如果哥布林一直待在自己的营地里,直到某个玩家偶然撞见它们,这个够玩一个晚上的任务就永远不会出现。

当然,绕过这个问题的办法立刻就能想出很多,但这不是重点:重要的是,设计者首先得意识到可能存在的问题。你提出的解决办法,自身会不会也带来问题?要是我不问,你会想到这一点吗?

虚拟世界设计,关乎后果。


  1. 出自《MUD1》的“mobile objects”(活动体)。我之所以这么叫,是因为那些以受控却不可预测的方式移动的生物,就像挂在天花板上的那种“mobiles”(活动雕塑)一样。呃,我当时有点赶时间…… ↩︎

客户端/服务器模型

服务器体现着虚拟世界,客户端则把它转换成玩家能够理解的形式。由于客户端是玩家瞭望虚拟世界的窗口,设计者对客户端的外观与感觉有很多话要说。不过,这些多半属于品味与惯例的问题,因此这里不予讨论。显然,如果你提出的施法系统复杂到无法用鼠标和键盘来实现,那确实会造成差别 1,可要是这种事还需要别人告诉你,那你根本就不该当设计者。

然而,虚拟世界的实现有一些工程上的考量,会直接影响到虚拟世界本身。这些考量是绕不开的,设计者必须意识到它们。

举例来说:理论上,同一个虚拟世界可以用不同的皮肤来呈现,于是一个玩家看到的可能是正在发射能量武器的防御机器人,另一个玩家看到的却是挥舞魔杖的巫师。玩家会觉得这很新奇,但它对设计者构成了重大挑战。很少有哪两种题材能对应到这个程度,所以不得不做出妥协。

其实,这个例子是个障眼法,因为单是支撑一种题材所需的美术量就已经相当可观,更不用说两种了。收益不值这个代价。在这方面,文字世界当然比图形世界更容易换皮,但想要这么做的理由也更少 2。在实践中,给图形客户端换皮,意味着改变客户端界面的外观与布局,而不是改变它所显示的虚拟世界的样子。挂上不同的窗帘,透过窗户看到的景色并不会变。

事实证明,与客户端/服务器关系有关的问题里,只有两个会对虚拟世界的设计产生切实影响,但这两个都非常重要:同步与安全。


  1. 嗯,只对施法者如此。理论上并没有理由说,不同的角色职业不能拥有为自己大部分时间都在做的事量身定制的界面。 ↩︎

  2. 不过,确实存在一些转换方案,可以在虚拟世界里主题各异的各部分之间(甚至不同虚拟世界之间)迁移功能等价的物件。 ↩︎

同步

信息在客户端和服务器之间传递需要时间。服务器执行命令也需要时间。在这段时间里,客户端得一直把虚拟世界显示下去。因此,屏幕上显示的东西未必是服务器所认定的当前世界状态的真实呈现:两者并没有同步。在真实世界里,太阳也许在 19 秒前就毫无缘由地爆炸了,而你还要再过八分钟才会知道。在虚拟世界里,你朝某人射出一箭,而对方可能四秒前就已经退出了。

因服务器负载而产生的延迟,可以通过购置更快的硬件、优化代码来解决。不过,对于通信造成的延迟,能做的就不多了。即便连接状况完美,光在玻璃中传播的速度 1 也意味着:一个身在悉尼(Sydney)的人,玩着一个服务器设在圣迭戈(San Diego)的虚拟世界 2,就算用直连电缆,每个方向也会有超过 0.06 秒的延迟。而通信还得经过路由器,走的又不是直线,这样一来,延迟就变成差不多三分之一秒了。再把模拟调制解调器算进去,又能多出三分之一秒。延迟总会发生。

设计师必须为此做出应对:设计里不要有任何对时间过于敏感的东西。在普通的电脑游戏里,可以让玩家掐着时间从风车的叶片、巨大的蒸汽活塞或钟摆似的镰刀之间穿过去;但在虚拟世界里,玩家在自己的客户端上看到一个缺口时,服务器上却没法保证真有这个缺口。正因如此,永远不能要求玩家做出反射动作(虽然可以要求他们的角色这么做),也就是说,虚拟世界里的 twitch(瞬时反应)少得可怜,甚至根本没有。

定时动作是可以做的,但成功的判定窗口至少要持续四秒,否则有些玩家会以为自己赶上了,实际上却错过了。不过,互联网可靠性的提高,还是让人尝试把第一人称射击游戏里常见的那种响应时间带进虚拟世界:《星际online》(Planetside)3 就是其中最著名的先驱。

撇开精确计时的问题不谈,虚拟世界面对延迟其实相当稳健。玩家在什么确切时刻发起一个动作,很少要紧;玩家们约好“同时”做一件事的时候,心里清楚那是个模糊的概念。只要设计师不去刻意做任何需要速度的设计,就很少会出问题。

玩家忍受的延迟,虽然大部分在整条连接上相当恒定,但并非全都如此。有时候延迟会是一阵一阵的:一切正常运转,接着突然毫无明显原因地停住。这种延迟其实源于带宽问题,服务提供商因此启用了某种资源分配方案。你的宽带连接有多好都没用:如果 ISP 不把数据包发给你,你就是收不到。这种延迟能持续好几秒,随后所有被缓冲下来的数据包会一次性发出,服务恢复正常。

客户端要靠服务器定期发来的数据包,更新自己的本地状态。大多数时候,这些数据包都到得很及时。可是,只要有个和你用同一台路由器的人开始下载海量色情内容,数据包就免不了被拖慢。那么,遇到这种情况,客户端该怎么办?

最简单的解决办法是什么也不做。就那么坐着等,等数据包到了,再按顺序更新。问题在于:在更新到达之前,对玩家来说虚拟世界实际上已经冻住了。玩家可以试着做点什么,但在数据包重新流动起来之前得不到任何回应。同样的情况在文字世界里未必总是问题,但在图形化世界里,看起来就非常令人不安。

因此,大多数图形客户端使用一种预测模型:让物体沿着它们最后一次得到信息时的方向继续移动。如果一个角色正朝东跑,那么等更新包终于到达时,客户端多半还在让它朝东跑;画面因此是对的,而且从头到尾都没冻结过。预测模型在预测正确时效果很好(幸运的是,大多数时候都是如此)。可一旦预测错了,问题就来了。如果那个朝东跑的角色其实已经停下、转向了北面,那么它的实际位置,就可能和那个被拖延的客户端所显示的位置差出一大截。

要纠正失败的预测,有两种做法。传统的做法是采用新信息,把预测直接忘掉。这会产生一种叫 warping(跃迁)的效果,它最早出现在《空中勇士》(Air Warrior)里。玩家本来正咬着敌机的尾巴,那架飞机却突然从视线里消失,又在不远处瞬间重新出现,仿佛做了一次超空间跳跃。玩家甚至找到了诱发它的办法,这样就能安排好那架飞机重新出现的位置,取得战术上的优势。

更现代的客户端则是给物体显示出来的位置做渐进式平移,让它平滑地移到正确的位置上(虽然会慢一点,而且看上去仍然很可疑)。

对设计师来说,这意味着他们不仅不能用相对时间(“三秒后”),也不能用相对空间(“正前方”)。客户端可能以为某个坐标在玩家稍微偏左一点,而服务器知道它在稍微偏右一点。因此,需要坐标的命令必须使用绝对坐标,而不是相对坐标。还是那句话:只要设计师意识到这个问题,不去要求玩家在迷宫之类的地方执行复杂指令,虚拟世界通常就足够结实,应付得来;绝对坐标上有点小误差没关系。不过,他们首先得意识到这一点。

不过,有一种特殊情况:有时候玩家想对远处的其他玩家做点什么,比如朝他们射一箭。这种情况下,不仅弓箭手的位置可能与服务器的权威版本不一致,目标的位置也可能不一致。客户端知道玩家想朝某个角色射箭,却并不确知那个角色的坐标。设计师必须决定:这条命令究竟是“射击某个目标”,还是“射击某个位置”(但愿那个位置上正好有目标)。如果箭的飞行是定时的(如果它模拟的是真实的箭,那当然应该如此),出错的余地还会更大。

唉!

图形化虚拟世界呈现出来的是连续的空间。角色实际占据的也许是整数坐标,但它们有动画,会在这些坐标之间平滑地移动,于是给人一种印象:它们有时就处在坐标“之间”的空间里。

文字世界也可以是连续的,不过它们通常是毗连的。地点可以代表一个区域,而不是一个点,好几个角色可以同时处在这样一个地点 4 而不引起任何异常。把水平方向的移动限制在八个罗盘方位上,意味着在这类虚拟世界里使用相对方向完全可行。果不其然,有些文字世界就允许玩家在移动时既使用绝对方向(北、东北、东等),也使用相对方向(前方、右前方、右等),房间描述采用哪种格式(绝对还是相对)也由玩家自己决定。图形游戏可以接收客户端发来的相对坐标,但发送出去时必须写成绝对坐标。

经验不足的客户端作者常犯的一个错误,就是把这一切都不当回事。客户端和服务器对什么东西在什么位置、朝哪个方向,看法略有出入,那又怎样?分歧大不到哪儿去。好吧,偶尔你确实会看到有人从实心的风车叶片里穿过去,因为他那边的客户端上有个空隙,可这实在算不上什么拦路虎。就让客户端来决定箭有没有射中吧,别把负担压在服务器身上。也许会有几次本该脱靶的判成了命中、本该命中的判成了脱靶,但时间一长总会扯平。

这就顺理成章地把话题引到了安全问题上。


  1. 大约每秒 197,000 公里。 ↩︎

  2. 在海平面上距悉尼大约 12,083 公里。 ↩︎

  3. http://planetside.station.sony.com/ ↩︎

  4. 这种地点叫作房间。 ↩︎

安全

你的客户端软件一定会被人破解。对某些虚拟世界来说(比如那些完全没有游戏成分的),这未必有什么关系。对其余的虚拟世界,关系可就大了。

至少,这意味着客户端发给服务器的每一个数据包,都必须检查一遍,看它是否讲得通。硬件层与传输层的纠错机制本应保证,到达服务器的内容就是客户端发出去的内容——那又何必浪费时间,去检查根本不会出现的胡话?可事实是:正在跟服务器说话的,未必是一个货真价实的客户端。它可能只是在跟一段代码说话——那是某个玩家自己写的,用来冒充客户端,或者往客户端的数据包里插数据。要是服务器收到了讲不通的数据,却没有办法处理,那会怎么样?是不是已经有人学会了让服务器崩溃的本事?

这还只是说,那台服务器是你的。有时候,一群玩家会自己写一台服务器,然后说服客户端改去跟它说话。这样一来,他们就能设计和游玩自己的世界,而不玩你的世界——而且这一切分文不花 1!

虚拟世界的程序员没有义务让黑客的日子好过——他们可以使用加密、可以独占屏幕、可以把身份校验做得极不频繁,让数据包嗅探软件有可能漏掉它们——但他们的代码终究会被人逆向工程,人们终会弄清它到底是怎么回事。然后,他们就会把它改掉 2。

重要:凡是与虚拟世界里会发生什么有关的决定,都绝不能下放给客户端。任何决定都不行。就是任何决定都不行。

《空中勇士》(Air Warrior)最初的客户端会自己做必要的计算,判定一枪是打中了还是没打中。当时认为,玩家把准星精确对准目标、扣下扳机,却因为客户端显示目标在某个位置、而服务器认定它不在那儿,结果打了个空,这是不公平的。这种考虑非常值得称道——直到有人给客户端写了一个外挂:只要你一开火,它就发一个数据包,声称你打中了离你准星最近的那架飞机——不管它是不是真的在准星里。Kesmai 公司不得不推出一个补丁来修它。

程序员绝不该把关乎世界运转的代码放进客户端。一旦这么做了,就可能意味着重大、极大规模的欺诈。设计者只在一种情况下才需要为此操心:损害长时间没被发现,他们不得不拿出策略来修补它(比如,一群玩家给自己凭空发钱,再把这笔钱花出去,数额大到引发通货膨胀)。

设计者确实还得操心自动化游玩这类事。凡是要求反复、反复、反复地做类似动作的事,通常都容易自动化——很多情况下甚至用不着破解客户端,市面上现成的工具就能代劳。《网络创世纪》(Ultima Online)的制作系统枯燥到了这种地步:玩家发现有一种给打字员用的宏软件能让他们免于食指重复性劳损时,简直喜出望外。计算机下达命令的速度通常也比玩家快:有人为《MUD1》写了自己的客户端,把命令以极快的速度塞进线路,结果开玩 30 秒后,那个自动角色就站在那里,怀里抱满了凶悍的武器和其他有用装备,而其他人手里基本上还空空如也。

所以,设计者应当避免提出任何要求玩家把某个动作一遍又一遍、又一遍地做下去、中途不得喘息的设计(这也可能包括移动)。如果你确实想让某个动作花掉大量时间,那就让角色趁玩家离线时,把这件事当作后台任务来做。如果速度偶尔也很重要(比如出于戏剧性的原因),那就坚持让服务器程序员在处理任何单个玩家的命令之间加入延迟 3。如果需要某种程度的搜寻或探索(比如一道谜题),那就放上会移动的障碍或陷阱,好让程序没法轻易用蛮力法找出解法。

人们想自动化的,通常都是乏味的任务。如果你设计的某种东西,你料想许多玩家都会想把它自动化,那就不妨想一想:它本身会不会就没什么意思。举例来说,玩家如果自己动手画地图,对虚拟世界的理解会深刻得多,所以设计者也许想鼓励他们这么做。然而,画地图的实际操作枯燥得让人头脑发木,以至于(针对文字世界)出现了自动绘图程序:它们会试遍每个房间的每一条出口,直到生成一张完整的地图,然后就可以挂到网站上。如果你原本指望靠不在客户端里内置地图工具来激发探索的氛围,那这招没有奏效。

狡猾的玩家还可以用别的办法操纵客户端,让自己占上风。设想一个角色原地转 360° 的情形。客户端必须与服务器保持紧密而快速的联络,才能确保随着扫视的推进,手边随时都有显示世界所需的信息。否则,玩家看到的可能就是半屏空白,或者有角色从画面正中突然冒出来——而一瞬间之前它们还不在那儿。不过,正如前文所说,客户端通常不能指望与服务器之间有一条快到飞起的连接。

为了解决这个问题,《无尽的任务》(EverQuest)的服务器被配置成向客户端发送超出其严格所需的信息:玩家想转身时,附近物体的位置早已在手,显示起来就不会有延迟。为此,客户端会被告知附近一切物体与玩家的位置——不只是那个角色“看得见”的那些——外加一些别的顺手小信息,比如它们身上带着哪些东西、还有多少生命值。世界的地形数据也一直常驻在玩家的 PC 上。

这是一个高效的做法:任何人都可以毫无顾忌地旋转镜头或以别的方式改变视角,而不会碰上画面错位。然而,不少玩家很快就意识到:既然信息就在那儿,那么不管用不用得上,都可以把它显示出来。他们写了程序,把附近每个人、每样东西都以雷达读数的形式呈现出来。mobiles(活动体)再也没法从背后偷袭他们;他们也不用再猜哪一只身上带着战利品。其中一个这样的程序 ShowEQ 实在太好用,Verant 不得不禁止使用它(这又成了它众多公关灾难中的一起 4)。

被破解的甚至未必是客户端。《网络创世纪》用 PC 显卡来控制亮度,这意味着爱钻空子的玩家可以绕开客户端(把伽马校正调高),在本来应该一片漆黑的时候换来满眼白昼。打埋伏再合适不过了!

只要有可能,设计者就应当坚决要求:发给客户端的任何数据项,都不得携带超出玩家角色有权知道范围的信息。不过,客户端编程本来就是一门谈不上精确的科学,很可能总有些额外信息必须提前发过去,哪怕只是 CD-ROM 上的纹理贴图。设计者应当事先被告知这一点,以便相应调整自己的设计。如果你明知玩家反正会弄清附近所有物体都在哪儿,那就干脆正式地把这些信息交给他们,同时确保它派不上多大用场。如果你想让他们时刻提着心,那就让新的 mobiles 凭空瞬移出来。

好,那么,你的程序员向你保证:客户端里没有本该放在服务器里的东西,只有少数几处你可以绕着设计过去。这么一来,安全问题这就算解决了!

唉,你明知道并没有。你自己的设计本身就可能存在安全问题。

举例来说,假设你(已经认定固定价格不是好主意)让经济系统根据当地的供需来决定商品价格。再假设,它允许人们按单件的价格批量买卖。正常情况下,这不会出问题:如果有 300 把剑在售,那么你按 20 UOC 5 买一把,还是按每把 20 UOC 买 10 把,都没有区别。剑剩得越少,价格就涨得越高,这正是你想要的效果——买下 10 把之后,新价格可能是 22 UOC。你会把回购价定得远低于出售价,这样,谁要是想把刚花 200 UOC 买来的 10 把剑卖回去,拿到的可能只有 110 UOC,而不是 220 UOC。

到目前为止,一切正常。

可是,碰上高成本、低产量的商品,又会怎样?也许本地的钻石矿每周只挖出五颗钻石。五颗全在时,钻石市场的价格是 1,000 UOC;只剩一颗时,价格飙到 4,000 UOC;一颗都没有时,就是供给枯竭了。在这种情形下,一颗钻石能按多少钱卖回钻石市场?即便按买入价打五折,也至少是 2,000 UOC。于是,如果有人以每颗 1,000 UOC 一次买下全部五颗,再以每颗 2,000 UOC 一次全部卖回,他们每做一笔就赚 5,000 UOC。你的设计送了他们一台引擎,想造多少钱就造多少钱!呃啊!

虚拟世界允许某件事、而设计者巴不得它不被允许,这种情况就叫 exploit(可被利用的漏洞)。exploit——设计层面的 bug——能在一夜之间毁掉一个虚拟世界 6。

撇开 exploit 不谈,玩家还有很多办法把设计者的一番好意扭到别处去。身份盗窃——在现实生活里冒充别人——相当容易,但基本上不是设计者的责任 7。角色盗窃——在虚拟世界里冒充别人的角色——则不该容易,因为这是设计者的责任。尤其是,如果两个玩家可以用相同的名字给角色命名,那么之后其中一个成功冒充了另一个,设计者就有一部分责任。这个“名字问题”在第 3 章里有相当深入的讨论。

所以,你的设计一定会出 bug。玩家会找到它们,并从里面把能捞的好处统统捞走。就算他们立刻就上报了,他们也会觉得,凭这份诚实,你欠他们一份人情。那你该怎么办?

exploit 无法杜绝,但你可以采取措施,把它们的数量和影响压到最低。检测与恢复至关重要。只要可能,就把一切都记录下来。在《MUD2》里,玩家经常抱怨线路噪声 8 切断了他们与游戏的连接,害得他们的角色一命呜呼。实行一套“全部记录”的策略(玩家的输入/输出记录,加上服务器的所有决定),一下子就把问题解决了——十次里有九次,玩家会被揭穿:直到巨龙把他们烧成灰、野狼咬掉他们的脑袋、或者别的什么的那一刻,他们都还活动得好好的;因此,他们那些慷慨激昂的复活请求,等同于作弊。

对于占用大量带宽的虚拟世界来说——比如图形化的游戏世界——这种程度的记录并不总是做得到。有两种做法,但哪一种都不太令人满意:一是存下事件(据此可以还原出确切的情境),二是存下客户端通信(据此可以得到玩家实际看到的画面)。这两种做法都会产生海量、且难以检索的数据。在事态发展的过程中,客服管理员也许能窥探 9 玩家,这可以部分缓解问题;但如果他们赶到现场时已经太晚,那么除了日志,就只剩在场的中立证人这一个办法了(这种证人想必一抓一大把)。

检测出可能的 bug 与 exploit 很重要,但这本身还不够。当事情真的不幸出了岔子时,纠正后果的能力应当永远都在手边(哪怕这意味着从定时备份里把角色数据库整个重装一遍)。发现事情出错的能力不完整,只是不方便;恢复受损数据的能力不完整,则是失职 10。

设计虚拟世界的满足感,有一部分就在于看着自己的造物不断演化,看着里面发生一些你未曾预料、却又完全说得通的事。不过,随之而来的一个不太走运的后果是:发生的事里,总有一些你会巴不得它没发生,还不得不动手去修。你备下的应急预案越多越好,但你永远也不可能把一切都覆盖到。

不过,既然你要的就是不可预测,那它真来了,你也别抱怨。


  1. 例子包括《无尽的任务》的 EQemu(http://www.eqemu.net/)和 EthernalQuest(http://www.ethernalquest.com),以及《网络创世纪》的 Sphere(http://www.sphereserver.com/)、UOX(http://www.uox3dev.net/)和 Epsilon(http://www.epsilon.escend.net/)。更多例子见 The Smithys Anvil(http://www.smithysanvil.com/)。 ↩︎

  2. 开发者所说的 arms race(军备竞赛),指的就是这个。 ↩︎

  3. 其实更时髦的做法不是强制延迟,而是让服务器——甚至其他客户端——来做抽查,看有没有人在作弊。不用说,如果客户端有权举报谁在作弊,它们迟早会被破解,用来诬告无辜的玩家。 ↩︎

  4. Verant 修改了最终用户许可协议,赋予自己搜查你家 PC 的权利,去寻找在他们看来可能妨碍《无尽的任务》正常运行的软件。随之而来的全面反抗,帮他们重新考虑了这个做法。 ↩︎

  5. 货币单位(Units of Currency,UOC)。 ↩︎

  6. 当然,任何类型的模拟软件都会出现这种问题,只不过在虚拟世界里为害最烈。我在一款单机贸易游戏(Ascaron 的《贵族》,The Patrician)里发现过一个与钻石例子类似的 exploit,靠它,我在苦战五年之后终于通关。到了《贵族 2》(The Patrician II),他们把这个问题修掉了。 ↩︎

  7. 2000 年,我在《无尽的任务》里花了好几个月建立声望,尽管那段时间我一次都没有玩过这款游戏。有人假扮成我,而别人居然信了。 ↩︎

  8. 早年间,调制解调器没有硬件纠错功能。电话线噼啪作响,数据也跟着噼啪作响。 ↩︎

  9. 马修·隆巴德(Matthew Lombard)与特雷莎·迪顿(Theresa Ditton),《一切的核心:临场感的概念》(At the Heart of It All: The Concept of Presence)。洛杉矶,《Journal of Computer-Mediated Communication》,南加利福尼亚大学(University of Southern California),1997 年 9 月。http://www.ascusc.org/jcmc/vol3/issue2/lombard.html。关于虚拟世界的专门描述,见约翰·托厄尔(John Towell)与伊丽莎白·托厄尔(Elizabeth Towell),《基于文本的网络化虚拟环境即“MUDs”中的临场感》(Presence in Text-Based Networked Virtual Environments or “MUDs.”)。《Presence: Teleoperators and Virtual Environments》第 6 卷第 5 期。马萨诸塞州剑桥,MIT Press,1997 年 10 月。http://www.fragment.nl/mirror/various/Towell_et_al.1997.Presence_in_MUDs.htm。 ↩︎

  10. 而一旦玩家意识到你手里没有准确的事件快照,那才是真正开始出问题的时候。 ↩︎