集え!勇士の名の下に --《勇士online》研发回忆录 3

本文是用第一人称记载的真人真事,里面涉及到了大量他人的名称等信息,所以为了避免不必要的纠纷,谢绝转载

游戏场景是整个游戏的舞台,游戏剧情的进展都要在这个舞台上演。网络游戏的场景管理主要集中在两块:一块是纯客户端方面的,对被渲染的可视数据进行组织管理,使得渲染速度尽可能地高效。另一块就是客户端和服务端共同使用的地形数据的组织和管理。

前一种纯客户端的对可视数据进行组织管理的方法有很多种,例如:binary space tree,octree,quadtree,BVH(bounding volume hierarchy),等等。方法虽多,但万变不离其宗,无一不是以尽可能快速地把不在当前view frustum中的可视数据给剔除出来,以减少要渲染的图元个数。

不同的游戏类型和视角方式所采用的可视数据组织方式也有不同。如果是室内游戏,有着很明显的墙壁分界的话,大都采用binary space tree的方式去组织。最典型的是id software的《quake》系列。quadtree和octree大部分是使用在室外场景。如果场景的高度起伏不明显,在高度处理上不需要过多逻辑判断的话,一般都会采用quadtree,反之就采用了octree。目前国内大部分的MMORPG都是采用octree及其变体去组织场景的。

因为Gamebryo引擎的scene graph本身就是采取一种“父子兄弟层次架构”的方式去组织rendered object。即每个rendered object之间,存在着父子关系,或者兄弟关系。对某一个rendered object进行一些坐标变换操作,比如平移的时候。该rendered object在scene graph中的所有作为它的“孩子”的rendered object也会随之平移。而恰好BVH也是基于这种层次架构思想的。所以Gamebryo引擎缺省的可视数据组织方式,就是采用了BVH的方式。

在2007年上半年,可能是对Gamebryo引擎的工作机制不熟悉的原因;也可能是美术部门对MMORPG,尤其是对《World of Warcraft》的偏好,他们一直希望程序能够提供一个类似于《WOW》那样子的地图编辑器。希望能够完全脱离3DS MAX或者maya。直接在编辑器里面“刷地表摆房子”。为此甚至和程序组之间产生龃龉。事后看来,只能说当时大家都处于对Gamebryo引擎能做什么都不熟悉的阶段。抛开一些主观的情感,单从纯技术的角度来说,“类似于《WOW》那样子刷地表摆房子”和《勇士online》的横版视角模式是格格不入的。因为视角的原因,为了减少数据量,横版游戏的制作都是大有取巧之处的。以《勇士online》为例,场景中的远景处,是采用一张2D图片作为背景——因为离镜头很远,看起来基本上都是静态的。近景处,在怎么移动镜头也不可见的地方,不需要建模。也就是说,很多在游戏中看来的房子,树木等等,建模建好后,在导出数据时都是掀掉一半的。而这种方式又怎能直接套用到《WOW》那种全视角的地图呢,万一,玩家把镜头转到房子后面时怎么办?岂不是穿帮了。而且横版游戏的每个关卡,或者说子场景,都不是很大,完全可以用3DS MAX制作好之后再导出的。

经过了一系列的争执,反复,误解和协商之后,最终《勇士online》还是直接使用了3DS MAX进行场景编辑。然后导出Gamebryo引擎能使用的数据格式。所谓的第一种的纯客户端的对可视数据进行组织管理,就正式确定下来了。而在客户端和服务端共同使用的地形数据的组织和管理。客户端和服务端的意见相左。

所谓地形数据的组织,简单来说,可以理解为地形的高度信息和类型的集合。即地面上某一点,或者说,某一块地表,它的高度是多少;它是一块怎样的地面,是沼泽,还是平地?地形数据的管理就涉及到在这些地形上活动的游戏对象,如何去根据地形信息去设置自己的行为表现——比如从平地走到一个陷阱,怎样掉下去?走到泥泞地时,怎样减少移动速度。客户端如何表现?服务端如何判定?等等。

作为客户端这边的意见,CQ的想法是倾向于使用一套“octree加地表高度数据”的方式去组织和管理地形数据。在这个方案中,度量场景的坐标系,是基于浮点数的三维坐标系,可以认为是一个连续的坐标系。所有游戏对象的位置坐标,攻击判定范围,都是基于这个三维坐标系的考虑。因为是三维的坐标系,所以在处理角色对象的位置信息时,还可以处理垂直方向的高度信息。这点在攻击判定时显得更为真实:假如某个角色A,在高处,或者跳起来,向另一个角色B,释放了一个飞行道具C。C在角色B的头顶上飞过。C在B的头顶上飞过时,虽然在客户端Gamebryo引擎自身的collision system能正确地算出C没击中B,但在服务端,因为是使用这个客服公用的地形数据,如果不能处理垂直方向的高度信息的话,则会认为C击中B了,因为此时的C,它的水平面上的xy坐标,和B是一样的。

引入octree的原因主要就是意图降低对象之间交互计算的次数。octree的原理就是将空间分为8的整数次方个等大小的立体区域。根据当前某玩家在场景中的位置,快速地拣选出玩家在场景中那个部分,然后就只选取该部分的其他游戏对象,比如NPC,或者monster与玩家进行交互。处于玩家不在部分的monster或NPC就不必进行交互处理。这样子就大为降低了交互次数,减低了服务端CPU计算的压力。

至于引入地表高度数据,是为了在游戏中能准确地设置玩家,NPC,monster的位置。因为在游戏中,玩家在移动的或者站立的时候,双脚必须要紧贴地面的——否则就腾空而起穿帮了。要紧贴地面的方法有二,一种就是在游戏运行时,实时地根据玩家当前的xy位置,即水平面的坐标值,然后计算出地面在该水平面坐标值下的高度值Z,然后把这个z值设置给玩家,但显然,要精确地计算出这坐标值是需要通过射线和三角形求交点的算法。在运行期去计算是很耗时的,更不用说同时会有很多个玩家或者monster在同一个场景中。所以我们采用了第二种方法,就是预先用工具算地表的高度信息存储在外部文件中,游戏运行前直接把高度信息载入到内存,在运行时某一点的地面的高度值,可以直接通过查表获取。这样子的效率,是在运行时实时计算地面高度值不可同日而语的。

CQ大约在2007年4月份左右做出了第一个基于octree的示例模型。但在和服务端沟通时,服务端主程hy提出了不同的意见。出于经验和对性能效率的考虑。hy不赞成使用三维浮点数坐标系。他更倾向于使用离散的二维网格坐标。也就是说把一个三维场景的地面,划分为若干个格子。每一个格子存储有该格子所对应的地面的高度。在服务端的游戏世界中,角色的水平面上的位置坐标值,就已经不是一个一个具体的浮点数实际坐标值,而是离散的格子编号索引值。在客户端中,某玩家在世界中某个位置(x,y,z),在服务端就变成了它在第(i,j)格,它的高度位置值是这个(i,j)格所存储的地面高度。游戏对象之间的交互,尤其是玩家和monster之间的交互,更多地是由monster所挂接的AI脚本,去控制交互与否。

两种方案各有优劣,客户端的方案的优点是精确,并且能够处理高度存在差异时的情况。服务端的方案优点是实现起来较迅速,并且在交互判定上速度较快,但不足之处就是交互判定较粗糙,对于那些水平面上坐标相等,就是高度不相等的对象,无法直接进行高度方向的判断。如果要做这方面的判定的话,就还得额外想别的办法。

在一番争论之后,在服务端的坚持下,最终还是采用了服务端方案作为对地形数据组织管理的方案。要使用这一个方案,第一个任务就是需要实现一个计算地表高度并将其存储在文件中的工具。