Main

September 27, 2014

随机地形生成

玩过 矮人要塞 Dwarf Fortress 的同学都会惊叹于它的随机地形生成系统。如果你对 ascii art 无感,那么可以 google 一下 stonesense 的图片。

矮人要塞是 3d 的地形系统。在游戏 wiki 上有介绍地形生成系统的参数细节

大体上是这样的: 首先有一张高度图(Elevation),决定了每个坐标的高度。然后给出一张降雨图 (Rainfall),来影响当地的植被和河流。通常降雨图是根据高度图(海岸和季风影响)计算出来的。这里有一篇文章简单描述了这个过程。

温度图(Temperature)一般根据纬度以及当地的海拔计算出来,也会影响当地的动植物。

地质的排水情况(Drainage)可以影响当地能否形成湖泊、湿地和河流。同样影响了当地的土质(进一步影响植被)。

另外还有,火山的分布情况(Volcanism),以及野生物分布图(Savagery) 会改变当地的矿产和动物分布。


August 02, 2014

Unity3D asset bundle 格式简析

Unity3D 的 asset bundle 的格式并没有公开。但为了做更好的差异更新,我们还是希望了解其打包格式。这样可以制作专门的差异比较合并工具,会比直接做二进制差异比较效果好的多。因为可以把 asset bundle 内的数据拆分为独立单元,只对变更的单元做差异比较即可。

网上能查到的资料并不是官方给出的,最为流行的是一个叫做 disunity 的开源工具。它是用 java 编写的,只有源代码,而没有给出格式说明(而后者比代码重要的多)。通过阅读 disunity 的代码,我整理出如下记录:


March 04, 2014

谈谈陌陌争霸在数据库方面踩过的坑( Redis 篇)

注:陌陌争霸的数据库部分我没有参与具体设计,只是参与了一些讨论和提出一些意见。在出现问题的时候,也都是由肥龙、晓靖、Aply 同学判断研究解决的。所以我对 Redis 的判断大多也从他们的讨论中听来,加上自己的一些猜测,并没有去仔细阅读 Redis 文档和阅读 Redis 代码。虽然我们最终都解决了问题,但本文中说描述的技术细节还是很有可能与事实相悖,请阅读的同学自行甄别。

在陌陌争霸之前,我们并没有大规模使用过 Redis 。只是直觉上感觉 Redis 很适合我们的架构:我们这个游戏不依赖数据库帮我们处理任何数据,总的数据量虽然较大,但增长速度有限。由于单台服务机处理能力有限,而游戏又不能分服,玩家在任何时间地点登陆,都只会看到一个世界。所以我们需要有一个数据中心独立于游戏系统。而这个数据中心只负责数据中转和数据落地就可以了。Redis 看起来就是最佳选择,游戏系统对它只有按玩家 ID 索引出玩家的数据这一个需求。

我们将数据中心分为 32 个库,按玩家 ID 分开。不同的玩家之间数据是完全独立的。在设计时,我坚决反对了从一个单点访问数据中心的做法,坚持每个游戏服务器节点都要多每个数据仓库直接连接。因为在这里制造一个单点毫无必要。

根据我们事前对游戏数据量的估算,前期我们只需要把 32 个数据仓库部署到 4 台物理机上即可,每台机器上启动 8 个 Redis 进程。一开始我们使用 64G 内存的机器,后来增加到了 96G 内存。实测每个 Redis 服务会占到 4~5 G 内存,看起来是绰绰有余的。

由于我们仅仅是从文档上了解的 Redis 数据落地机制,不清楚会踏上什么坑,为了保险起见,还配备了 4 台物理机做为从机,对主机进行数据同步备份。

Redis 支持两种 BGSAVE 的策略,一种是快照方式,在发起落地指令时,fork 出一个进程把整个内存 dump 到硬盘上;另一种唤作 AOF 方式,把所有对数据库的写操作记录下来。我们的游戏不适合用 AOF 方式,因为我们的写入操作实在的太频繁了,且数据量巨大。

March 03, 2014

谈谈陌陌争霸在数据库方面踩过的坑(芒果篇)

我们公司开始用 mongodb 并不是因为开始的技术选型,而是我们代理的第一款游戏《 狂刃 》的开发商选择了它。这款游戏在我们代理协议签订后,就进入了接近一年的共同开发期。期间发现了很多和数据库相关的问题,迫使我们熟悉了 mongodb 。在那个期间,我们搭建的运营平台自然也选择了 mongodb 作为数据库,这样维护人员就可以专心一种数据库了。

经过一些简单的了解,我发现国内很多游戏开发者都不约而同的采用了 mongodb ,这是为什么呢?我的看法是这样的:

游戏的需求多变,很难在一开始就把数据结构设计清楚。而游戏领域的许多程序员的技术背景又和其他领域不同。在设计游戏服务器前,他们更多的是在设计游戏的客户端:画面、键盘鼠标交互、UI 才是他们花精力最多的地方。对该怎么使用数据库没有太多了解。这个时候,出现了 mongodb 这样的 NOSQL 数据库。mongodb 是基于文档的,不需要你设计数据表,和动态语言更容易结合。看起来很美好,你只需要把随便一个结构的数据对象往数据库里一塞,然后就祈祷数据库系统会为你搞定其它的事情。如果数据库干的不错,性能不够,那是数据库的责任,和我无关。看到那些评测数据又表明 mongodb 的性能非常棒,似乎没有什么可担心的了。

其实无论什么系统,在对性能有要求的环境下,完全当黑盒用都是不行的。

游戏更是如此。上篇我就谈过,我们绝对不可能把游戏里数据的变化全部扔到数据库中去做。传统数据库并非为游戏设计的。

比如,你把一群玩家的坐标同步到数据库,能够把具体某个玩家附近玩家列表查询出来么?mongodb 倒是提供了 geo 类型,可以用 near 或 within 指令查询得到附近用户。可他能满足 10Hz 的更新频率么?

我们可以把玩家的 buf 公式一一送入数据库,然后修改一些属性值,就可以查询到通过 buf 运算得到的结果么?

这类问题有很多,即使你能找到方法让数据库为你工作,那么性能也是堪忧的。当我们能在特定的数据库服务内一一去解决她们,最终数据库就是一个游戏服务器了。

March 01, 2014

谈谈陌陌争霸在数据库方面踩过的坑(前篇)

陌陌争霸 这个项目一开始不叫这个名字,它在 2013 年中的时候,还只是一个我们公司 用来试水移动游戏的试验项目。最开始的目标很明确,COC 是打动我的第一款基于移动平台网络游戏,让我看到了和传统 MMO 不同的网络游戏设计方向。我觉得只需要把其中最核心的部分剥离出来,我们很快可以做出一个简单的却不同于以往 MMO 的游戏,然后就可以着手在此基础上发展。

至于后来找到陌陌合作,是个机缘巧合的故事。我们的试验项目完成,却没想好怎么推给玩家去玩(而这类游戏没有一定的玩家群体基本玩不起来),而陌陌游戏平台刚上线,仅有的一款产品(类似泡泡龙的游戏)成绩不佳。因为我们公司和陌陌的创始人都曾经在网易工作,非常熟悉。这款游戏也就只花了一个月时间就在陌陌游戏平台发布了。

一开始我们只把刚完成狂刃 启动器项目的阿楠调过来换掉我来做这个项目,我在做完了初期的图形引擎工作后,就把游戏的实现交给了他。我们只打算做客户端,因为只有这部分需要重新积累技术经验;而服务器不会和传统 MMO 有太大的不同。而我们公司已经围绕 skynet 这套服务器框架开发有很长一段时间了,随时都可以快速把这个手游项目的服务器快速搭建起来。

到 2013 年夏天,感觉应该开始动手做服务器部分了。晓靖在斗罗大陆的端游项目中积累了不少服务器开发的经验,也是除我之外,对 skynet 最为熟悉的人;如果这个试验项目只配备一个程序来开发服务器的话,没有更好的人选了。

从那个时候起,我们开始考虑服务器的结构,其中也包括了数据库的选型和构架。

skynet 有自己的 IO 模型,如果要足够高效,最好是能用 skynet 提供的 socket 库自己写 DB 的 driver 。因为 redis 的协议最简洁,所以最先我只给 skynet 制作了 redis 的 driver 。而我们代理的游戏狂刃的开发方使用的是 MongoDB ,为了运营方便,我们的平台也使用它做了不少东西,我便制作给 skynet 制作了初步的 mongodb driver 。到服务器开始开发时,我们有了两个选择。

February 10, 2014

如何让玩家相信游戏是公平的

去赌场参观过的同学应该都见过那种押大小的骰子游戏。庄家投掷三枚骰子,把骰盅盖住,玩家可以押大或小。开盅后,如果发现三个数字之和大于等于 11 就算大,小于等于 10 就算小。如果你猜对了,庄家就 1 赔 1 算给你筹码;否则输掉筹码。另外,还可以以不同赔率压数字,或压三个相同。

为了保障庄家利益,三个相同的数字算不大不小。从概率上讲,这让长时间内庄家必胜。概率分析见这里

如果把这个游戏搬到网络上如何呢?(注意:网上赌博在很多国家是被禁止的,这里只做技术分析而已)

如何让玩家相信庄家没有作弊,真的产生了随机的骰子呢?

January 12, 2014

COC Like 游戏中的寻路算法

同样是一篇老文章, 2013 年初我刚玩 COC 的时候写给公司内部分享的。由于当时公司还没有决定开手游项目,但有意向做一款 COC Like 的产品,并希望开发期间保密,所以相关技术文章都没有公开。

目前,我们的游戏上线,就可以把当初写的东西陆续贴到 blog 上了。


COC 的地图有 40x40 格,边界还有两格不可以放建筑的格子。所以整个地图是 44x44 格的。但在做坐标判定时,基于格子是远远不够的。COC 官方版本很可能是基于像素级的精度的,和本文所述方法不一致,所以本文仅提出一种可行的数据结构,

January 11, 2014

斜视角游戏的地图渲染

这篇文章是在大半年前, 我们的手游 刚刚做预研的时候写给公司内部分享的。由于这个项目公司不希望在开发阶段对外暴露信息,所以文章也没有在 blog 贴出来。

现在游戏已经上线 就没有消息保密的需求了,我就可以把当初开发时写的一些东西逐步贴出来。


所谓类似 COC 这样的斜视角引擎,在英文社区中叫作 Isometric Tileset Engine。它在早期计算资源匮乏的年代用的很多,后来内存不是问题了后,就很少有人用了。由于没有 Z-Buffer ,这种引擎的绘制次序其实很讲究。比如下图:

            /\
           /  \
          /    \
         /     / /\
  /\    /  B  / /C \
 /A \  /     /  \  /
 \  / /     /    \/
  \/ /     /
     \    /
      \  /
       \/

December 28, 2013

Ejoy2D 开源

我们的第一个手游差不多做完了,预计在明年 1 月初推广,目前内测的情况非常不错,我们也可以考虑开始下一步在手游领域立新项目了。

上个项目做的太匆忙,今年 4 月份才开始。因为决定做一个 2d 游戏,我觉得在 2d 游戏引擎方面我有超过 15 年的经验,使用一个流行的开源引擎,比如大家都在用的 Cocos2d-X 还不如自己写。这样对引擎的可控性更强,可以方便的定制自己需要的功能,并在性能上做针对性的优化。手机设备的硬件性能远不如 PC 机,即使程序性能足够,我们也要考虑硬件的能耗,让电池用的更久一点,让设备不那么放烫。优化引擎也是游戏程序员的乐趣之一。

我们这个项目还是做的太急了,只花了一个月时间做引擎,然后在上面做了太多应急的各种修改,到项目完成(10 月)时,已经很不堪了。我不想把这块东西带到下一个产品,所以打算重新把这块代码理一下。

开源是一开始就想做的事情,可以帮助到别人,也同时督促自己把代码写的更整洁。从这周开始,我满头写了一周代码,从头写了 4000 行代码,就有了这么一个项目:ejoy2d

当然它还很不完整,有兴趣的同学可以跟踪这个项目,我和我的同事会逐步完善它。同时欢迎其他同学推送 Pull-request 。下面对 ejoy2d 做一个简单的介绍:

September 03, 2013

字体勾边渲染的简单方法

我们的游戏中需要对渲染字体做勾边处理,有种简单的方法是将字体多画几遍,向各个方向偏移一两个像素用黑色各画一遍,然后再用需要的颜色画一遍覆盖上去。这个方法的缺点是一个字就要画多次,影响渲染效率。

前几年有人发明了另一种方法,google 一下 Signed Distance Field Font Rendering 就可以找到大量的资料。大体原理是把字体数据预处理一遍,把每个像素离笔画的距离用灰度的形式记录在贴图上,然后写一个专门的 shader 来渲染字体。好处是字体可以缩放而不产生锯齿,也比较容易缺点边界做勾边处理。缺点是字模数据需要离线预处理。

我们的手游项目以及 3d 端游项目都大量使用了勾边字体,我希望直接利用系统字体而不用离线预处理字体把字体文件打包到客户端中。前段时间还专门实现了一个动态字体的贴图管理模块 。btw, 苹果的平台提供了高层 API 可以直接生成带勾边效果的字模。

但是,勾过边的字模信息中同时包含了轮廓信息和字模主体信息,看起来似乎很难用单通道记录整个字模数据了。这给染色也带来了麻烦。

April 02, 2013

动态字体的贴图管理

汉字的显示,是基于 3d api 的图形引擎必须处理的问题。和西方文字不同,汉字的字形很难全部放在一张贴图上,尤其是游戏中有大小不同的字体的需求更是如此。即使放下,也很浪费内存或显存。如果不想申请很大的贴图来存放汉字字形,图形引擎往往需要做动态字形贴图的处理。

即,动态生成一张贴图,把最近常用的汉字画在上面。几乎所有成熟的基于 3d api 的图形引擎都需要有相关的模块才可以对汉字更好的支持。但我到目前为止,还没有看到有把这个模块独立出来的。大多数开源引擎都是在自己的框架内来实现差不多的功能。我觉得,这部分管理功能和如何管理贴图其实没有关系、和取得字形的方法也没有关系、不必和 3d api 打交道,也不用涉及到底用 freetype 还是 os 自带的 api 取得汉字字形,所以值得独立实现。

我们的需求本质上是对一张贴图的区块进行管理。每个汉字都占据其中的一小块。当贴图填满时,最久没有用过的汉字块可以被淘汰掉,让新的汉字覆盖上去。同样的字体的最大高度是相同的,可以排列在一行,但宽度可以不同。横向排列时,少许的空洞的允许的。

我设计了如下接口:

struct dfont_rect {
    int x;
    int y;
    int w;
    int h;
};

struct dfont * dfont_create(int width, int height);
void dfont_release(struct dfont *);
const struct dfont_rect * dfont_lookup(struct dfont *, int c, int height);
const struct dfont_rect * dfont_insert(struct dfont *, int c, int width, int height);
void dfont_flush(struct dfont *);

February 22, 2013

Clash of Clans

最近在玩一款 iOS 游戏, 叫做 Clash of Clans 。这款游戏让我发现,手机/平板的网络游戏和传统网络游戏、网页游戏,除了 UI 方面,设计也是可以有很大的不同的。

对于移动平台,最大的不同在于,玩家的游戏时间碎片化。所以能集中精力的游戏持续时间不宜过长。应该在五分钟以下。当然,也需要提供玩家长时间持续游戏的途径。

玩家的游戏时间很自然的分为了在线时间和离线时间。所以,离线玩法相当重要。

COC 开创了一种:在线创造内容,离线后供其他玩家娱乐的模式。这是以往的游戏所不具备的。

January 14, 2013

Pixel light 中的场景管理

这几天无意中发现一款开源的 3d engine ,名为 pixel light文档 虽然不多,但写的很漂亮。从源码仓库 clone 了一份,读了几天,感觉设计上有许多可圈可点的地方,颇为有趣。今天简略写一篇 blog 和大家分享。

ps. 在官方主页上,pixel light 是基于 OpenGL 的,但实际上,它将渲染层剥离的很好。如果你取的是源代码,而不是下载的 SDK 的话,会发现它也支持了 Direct3D 。另,从 2013 年开始,这个项目将 License 改为了 MIT ,而不是之前的 LGPL 。对于商业游戏开发来说,GPL 的确不是个很好的选择。

这款引擎开发历史并不短(从 2002 年开始),但公开时间较晚(2010 年),远不如 OGRE 等引擎有名。暂时我也没有看到有什么成熟的游戏项目正在使用。对于没有太多项目推动的引擎项目,可靠性和完备性是存疑的。不推荐马上在商业游戏项目中使用。但是,他的构架设计和源代码绝对有学习价值。

December 25, 2012

模糊逻辑在 AI 中的应用

今天收到人民邮电出版的杨海玲同学寄来的几本书,首先感谢一下。看来短期内是没有那么多精力全部去读了,所以先随便翻翻感兴趣的章节。

在《游戏人工智能编程案例精粹 》 和 《 Windows 游戏编程大师技巧 》 中都分别有一章谈及模糊逻辑。记得前几年我的同事 Soloist 同学曾经研究过一小段时间,给我做过简单介绍,我便仔细把这两章书读了一遍。感觉都是点到为止,所以又翻了一下 Wikipedia 的 Fuzzy Logic 的介绍。午饭时跟做 AI 的同事交流了一下,觉得可以做一点笔记记录理解的部分。


在游戏人工智能编程中举了个实际的例子来说明这个问题:在一个 FPS 游戏中,NPC 有多种武器可供选择,这些武器的威力、射程等有所差异;策划决定根据 NPC 和玩家的距离以及 NPC 武器弹药的余量,两个因素来考虑 NPC 当下应该选择哪一种武器对抗玩家。这个例子不错,不过我有另一个自己的例子:在 MMO 中,NPC 在追击玩家时可能会考虑几个因素:离开他的出生点的距离,以及玩家的实力,或是自己的 HP 量等等。下面我用自己的例子来说明问题。

先甩开模糊逻辑是什么不谈,我们在做 AI 定制的时候会遇到怎样的很难决策的问题呢?

策划往往会定义规则:当 NPC 距离出生点很远时,他们停止攻击玩家并返回出生点。

这里有一个问题,即使是最新手的策划也看得出来,这个规则描述是不严谨的。到底什么叫“很远”。所以通常,需要加一个定义,追击半径,变成“当 NPC 距离出生点超过追击半径时,他们停止攻击玩家并返回出生点”。然后,在策划表格里估计就会多出一个表项:追击半径 40 米 之内的东西。

顺便吐槽:把设计这个规则(追击条件),和填写这个数字( 40 米追击半径)的工作分开,分别称做系统策划和数值策划,我怎么看都是件极不靠谱的事情。

December 06, 2012

网络游戏中商人系统的一点想法

在网络游戏中倒卖货物是一大乐趣(如果经济系统做的不坏的话) 。我见到许多 wow 玩家以此为乐,在国产游戏中,以梦幻西游为首,开店摆摊也让人乐此不疲。我最近对此有一些想法,和我们公司的策划交流以后,发现几句话很难说清楚。

大部分人第一反应都是增强版的拍卖场,比如增加求购系统,或是更方便的拍卖寄售系统,各种其它的拍卖场增强。

对于稀有品,我认为现在 wow 的拍卖场已经可以解决大部分问题,是不需要替换的。但一般商品,我认为应以增加流通,方便需求类玩家为主。最简单的方案是让系统根据商品流通速度来自动调节价格,系统统一销售和收购。但我们也知道光靠这种计划经济是很难得到一个合理的市场环境的,也少了许多玩家参与的乐趣。

所以我才有了下面这些想法:

November 23, 2012

相位技术的实现

魔兽世界从巫妖王之怒那个版本开始完善了他的 相位 (Phasing) 技术。简单说,就是在场景的相同区域,不同的玩家根据一些条件(一般是任务进度)的不同,会感受到不同的环境:场景可以不同、能交互的 NPC 不同等等。

这个技术可以给玩家在 MMO 中带来许多以往在单机游戏中才能体验的融入感。在 wow 之前,许多游戏策划都曾经想过让玩家可以通过完成剧情改变游戏的环境,但苦于找不到好的表现方式。这或许是因为大部分游戏策划都在以现实世界为参考去考虑技术能够实现的可能性,而在这个微创新年代,没有摹本可以参考就没有了思路。但我有些奇怪的是,在 wow 把相位技术展现给世界已经超过 4 年了,为啥山寨大国还没有全力跟进呢?

莫非是因为这里面还真有什么技术难点?

November 21, 2012

开发笔记(29) : agent 跨机 id 同步问题

我们的大部分设计是围绕单个进程进行的,所有的数据在进程内都可以方便的共享。只需要这些数据结构是线程安全的即可。

但最终,我们不会在单台机器上运营整个游戏服务器,所以还是要考虑玩家在不同物理机器间移动的问题。

虽然我们还没有开始进行跨机方面的开发,但是不少服务已经要开始考虑这个问题了。目前、每个玩家接入游戏服务器并认证完毕后,都会有一个 lua 虚拟机伴随他。也就是我称之为 agent 的东西。

agent 和场景服务 map 间会有高频率的互动,所以,我们必须要求 agent 和玩家所在 map 在同一个进程内。当玩家跳转到不在同一进程内的 map 上时,需要把 agent 迁移到对应的进程内。

迁移 agent 并不算难做:只需要把 agent 的数据持久化,然后在新的进程内启动起来即可。难点在于,新的 agent 的通讯 handle 变化了。为了性能考虑,我并不想实现一套跨机器的唯一 id 系统。那么,就需要解决一个问题:如果 handle 发生变化,如何通知持有这个 handle 的服务模块。

November 01, 2012

开发笔记(28) : 重构优化

正如上一篇笔记 记载的,我们第 2 里程碑按计划在 9 月 30 日完成,但因为赶进度,有许多 bug 。性能方面也有很大问题,大家都认为需要重构许多模块。所以,在最后几天修补 bug 时,许多补丁是临时对付的(因为整个模块都需要重写了)。为此,我们留下了一个月专门重构代码、修改 bug 、并对最后的结果再做一次评测。

这项工作,终于如期完成了。

半个多月前在白板上留下的工作计划还没擦掉。我列出了 12 点需要改进或重写的地方,考虑到内容较多,又去掉了 3 项。在大家的通力合作下,完成的很顺利。

之前我曾经提到 ,我们的老系统处理 80 人同一战场混战就让服务器支撑不住了。当时我们的服务器 CPU 达到了 790% 。虽然我们的服务器硬件比较老,配置的是两块 Intel Xeon E5310 @ 1.60GHz ,更新硬件可以有所改善。但这个结果绝对是不能满意的。从那个时候起,我从重写最底层框架开始一步步起着手优化。

昨天的测试结果基本让人满意,在同一台机器上,200 个机器人的混战 CPU 占用率平均仅 130% 左右,而机器人 client 边数据包延迟只有 1 秒,完全可以实用。这离我们的设计目标 ( 500 人同战场流畅战斗)还有一些距离,但考虑到今年新配置两块 Intel Xeon E5-2620 @ 2.00GHz 的话,按其性能指标,应当再有至少一倍的性能提升。

ps. 参考这份报告 ,我们计划采购的 [Dual CPU] Intel Xeon E5-2620 @ 2.00GHz Benchmark 16707 分,而目前使用的 [Dual CPU] Intel Xeon E5310 @ 1.60GHz 仅 4160 分。即使仅考虑单线程分数,也在两倍以上。

October 19, 2012

星际争霸2编辑器的初接触

最近在接手改进我们的怪物 AI 的底层模块。这部分策划是希望可以由他们来直接配置而不是提交需求文档让程序实现。

我们的前一个版本有较大的性能问题,光是空跑一些场景,没有玩家的情况下 CPU 符合都相当之高了。我觉得是允许策划的配置项过于细节,以及底层模块实现的方式不对,导致了大量无用的 lua 脚本空转导致的。

目前的 AI 脚本是每个挂在独立的 NPC 上,利用心跳(大约是 0.5s 一次),定期让 NPC 去思考现在应该去干些什么。这些干些什么的具体逻辑在很细节的层面都是要去运行一个策划配置的脚本在一个沙盒中运行的。在实际监测中,一个心跳的一段 AI 脚本居然会跑上万行 lua 代码,想不慢都难啊。

游戏开发和很多其他软件开发的一个巨大区别就是,你无法把程序得到正确结果当成任务的完成。运行时间往往成为重要的约束条件。如果一件事情在规定的时间片执行不完,代码实现的再正确都没有意义了。而局部的优化热点往往也意义不大。因为如果只是需要小幅度的提高性能,那么采购好一些的硬件就够了。一个模块的性能,要从数量级上的提高,必须重新思考需求,改变需求,重定义我们要做什么。

我决定看看星际争霸2 的地图编辑器是如何工作的。

我没有玩过魔兽争霸3 的编辑器,也没有玩过星际 2 的。但似乎,它们可以让用户自定义出各种形式的游戏来,并不局限在 RTS 一种类型中。我相信这个发展了超过十年的自定义地图的编辑模式,一定有很多成熟的业务抽象。

有限的时间内,我没有从网上找到太多的相关资料。在暴雪的官方网站也没能看到完整的文档。星际2 的编辑器内建了一个叫做银河(Galaxy)的脚本语言,似乎所有 GUI 界面上的编辑器操作,都可以完整的对应成一段 galaxy 脚本。很可惜的是,暴雪似乎鼓励玩家用 GUI 编辑器创造地图,而脚本只是背后之物,galaxy 的手册我并没有找到。

我只好自己把弄编辑器,在自己的使用中,推想暴雪解决问题的思路。短短两天的研究肯定会有许多错误和遗漏,也希望借公开的 blog ,有行家看到能够赐教。

October 11, 2012

开发笔记(27) : 公式计算机

我们的项目的第二里程碑在中秋前结束了。这次耗时三个月的开发周期,堆砌了大量的游戏特性,大部分任务都是在追赶进度中完成的,导致最终有很多问题。

所以,节后我们决定调整一个月,专门用来重构不满意的代码以及修理 bug 。好在这次我们的代码总量并不算太大。服务器部分,所有已有的 C 底层之外的代码,全部 Lua 代码,总量在五万行左右。这是一个非常可控的规模。即使全部重写,在已有的开发经验基础上,都不是什么接受不了的事情。所以我对改进这些代码很有信心。

有部分代码有较严重的性能问题,节前我给底层增加了一些统计和检测模块,发现部分逻辑模块,单条消息的处理居然超过了一万条 Lua 指令。比较严重的是 AI 和怪物管理模块,以及战斗的 buff 和属性计算。

这几个问题,接下来我会一个个着手解决。这两天在优化属性计算的部分。

在半年前,我曾经参于过这部分的设计与实现。参见之前的一篇开发笔记

不过在写完这个模块后,我的工作从这部分移开。其他同学接手正式实现策划的需求时,并没有使用我实现好的模块。因为策划的需求更为复杂,最终采用了更间接和可配置的方案。相关的实现我未能跟进,Mike 同学就用 Lua 实现了一个基本等价的东西。

September 20, 2012

开发笔记(26) : AOI 以及移动模块

最近在试图优化游戏服务器,提升承载能力。

加了一些时间检测模块。由于已经重新编写了服务器底层框架,并修改了消息协议,这样底层就有能力监控每条服务的相应时间了。

以前这方面没有做到,是因为:从底层看来,仅仅是一个个的消息包的流动。而一条完整的协议响应却是有多个包进出的。当处理一个服务请求的过程中,向外做了一次远程调用,那么当前服务体就被切出了,直到回应的 session 响应才继续。在新的设计中,session id 被暴露给底层模块可见,这就使监控变得可行。

另外,由于是完全用 C 语言编写而不再使用 Erlang ,我们可以使用成熟的 profile 工具。gprof 使用起来非常方便,容易对性能热点定位。

不过,经过实测,大量的 CPU 时间是消耗在 lua 虚拟机内部。也就是说,在底层框架上做改进已经没有多少可以压榨的了。或许进一步优化通讯协议还有一些工作可以做(合理的协议可以使得上层的数据处理更简洁),不过,工作中心移到 Lua 层面会更有效一些。

相关工作从多个方面入手。

首先想到的是使用 LuaJIT 。不过,我们一开始是工作在 Lua 5.2 上面,而 LuaJIT 似乎不打算完全支持 5.2 版,我需要适度的做一些向前兼容工作。之前我已经记录了一些相关的东西 。在写完上篇 blog 后,我加入了 LuaJIT 的 mailinglist ,LuaJIT 的作者相当的勤奋,基本上提交的 bug 都在半天内可以修正;大多数合理的功能需求也可以满足。或许向前兼容这个事情不一定需要完全自己搞定。我们不需要完全支持 Lua 5.1 ,只需要支持 LuaJIT 2.0 即可。那么,推动 LuaJIT 2.0 去支持一些 Lua 5.2 的新特性也不错。比如就在昨天,LuaJIT 支持了 Lua 5.2 中增强的 debug.getlocal 。

由于我们的进度压力,LuaJIT 的兼容问题暂时搁置了。还有一些工作需要其他同学的协力支持,而他们,正在为努力完成游戏特性而努力。暂时不太可能安排的出时间来做这些调整。

September 03, 2012

Skynet 设计综述

经过一个月, 我基本完成了 skynet 的 C 版本的编写。中间又反复重构了几个模块,精简下来的代码并不多:只有六千余行 C 代码,以及一千多 Lua 代码。虽然部分代码写的比较匆促,但我觉得还是基本符合我的质量要求的。Bug 虽不可避免,但这样小篇幅的项目,应该足够清晰方便修正了吧。

花在 Github 上的这个开源项目上的实际开发实现远小于一个月。我的大部分时间花了和过去大半年的 Erlang 框架的兼容,以及移植那些不兼容代码和重写曾经用 Erlang 写的服务模块上面了。这些和我们的实际游戏相关,所以就没有开源了。况且,把多出这个几倍的相关代码堆砌出来,未必能增加这个开源项目的正面意义。感兴趣的同学会迷失在那些并不重要,且有许多接口受限于历史的糟糕设计中。

在整合完我们自己项目的老代码后,确定移植无误,我又动手修改了 skynet 的部分底层设计。在保证安全迁移的基础上,做出了最大限度的改进,避免背上过多历史包袱。这些修改并不容易,但我觉得很有价值。是我最近一段时间仔细思考的结果。今天这一篇 blog ,我将最终定稿的版本设计思路记录下来,备日后查阅。

August 29, 2012

开发笔记(25) : 改进的 RPC

自从动了重新实现 skynet 的念头,最近忙的跟狗一样。每天 10 点醒来就忙着写代码,一句废话都不想说,一直到晚上 11 点回家睡觉。连续干了快一个月了。

到昨天,终于把全部代码基本移植到了新框架下,正常启动了起来。这项工作算是搞一段落。庆幸的是,我这个月的工作,并没有影响到其他人对游戏逻辑的开发。只是我单方面的同步不断新增的逻辑逻辑代码。

Skynet 的重写,实际上在半个月前就已经完成。那时,已经可以用新的服务器承载原有的独立的用户认证系统了。那么后半个月的这些琐碎工作,其实都是在移植那些游戏逻辑代码。

在 Skynet 原始设计的时候,api 是比较简洁的,原则上讲,是可以透明替换。但实际上,在使用中,增加了许多阴暗角落。一些接口层的小变动,增加的隐式特性,使得并不能百分百兼容。另外,原来的一些通讯协议和约定不算太合理,在重新制作时,我换掉了部分的方案,但需要编写一个兼容的链路层。

比如:以前,我们把通过 tcp 接入的 client 和 server 内部同进程内的服务等同处理。认为它们都是通过相同的二进制数据包协议通讯。但是,同进程内的服务间通讯明显是可以被优化的,他们可以通过 C 结构而不是被编码过的数据包交换信息,并可以做到由发起请求方分配内存,接受方释放内存,减少无谓的数据复制。在老的版本中,强行把两者统一了起来,失去了许多优化空间。在新版本里,我增加了较少的约定,修改了一点接口,就大幅度提升了进程内服务间信息交换的效率。

另一方面,一旦固定采用单进程多线程方案,之前的多进程共享数据的模块就显得过于厚重了。新的方案更为轻量,也更适合 lua 使用。这项工作在上一篇 blog 中提到过。这和 skynet 的重写原本是两件事情,但我强行放在一起做迁移,增加了许多难度。但考虑到,原本我就需要梳理一次我们的全部服务器端代码(包括大量我没有 review 过的),就把这两件事情同时做了。

在这个过程中,可以剔除许多冗余代码,去掉一些我们曾经以为会用到,到实际废弃的模块。彻底解决一些历史变更引起的问题。过程很痛苦,但很值得。新写的代码各种类型检查更严格,就此发现了老的逻辑层代码中许多隐藏的 bug 。一些原有用 erlang 实现的模块,重新用 lua 实现了一遍,混合太多语言做开发,一些很疼的地方,经历过的人自然清楚。以后如非必要,尽量不用 lua 之外的语言往这个系统里增加组件了。

btw, 新系统还没有经过压力测试。一些优化工作也没有展开。但初步看起来,还是卓有成效的。至少,改进了数据共享模块,以及提出许多冗余后,整个系统的内存占用量下降到原来的 1/5 不到。CPU 占用率也有大幅度的下降。当然,这几乎不关 C 还是 Erlang 做开发的事,重点得益于经过半年的需求总结,以及我梳理了大部分模块后做的整体改进。


今天想重点谈谈下面一段时间我希望做的改进。是关于服务间 RPC 的。

August 17, 2012

Skynet 的一些改进和进展

最近我的工作都围绕 skynet 的开发展开。

因为这个项目是继承的 Erlang 老版本的设计来重新用 C 编写的。 再一些接口定义上也存在一些历史遗留问题. 我需要尽量兼容老版本, 这样才能把上层代码较容易的迁移过来。

最近的开发已经涉及具体业务流程了, 搬迁了不少老代码过来。 我不想污染放在外面的开源版本。 所以在开发机上同时维护了两个分支, 同时对应到 github 的公开仓库, 以及我们项目的开发仓库。

btw, 我想把自己的开发机上一个分支版本对应到办公室仓库的 master 分支, 遇到了许多麻烦。 应该是我对 git 的工作流不熟悉导致的。

August 06, 2012

Skynet 集群及 RPC

这几天老在开会,断断续续的拖慢了开发进度。直到今天才把 Skynet 的集群部分,以及 RPC 协议设计实现完。

先谈谈集群的设计。

最终,我们希望整个 skynet 系统可以部署到多台物理机上。这样,单进程的 skynet 节点是不够满足需求的。我希望 skynet 单节点是围绕单进程运作的,这样服务间才可以以接近零成本的交换数据。这样,进程和进程间(通常部署到不同的物理机上)通讯就做成一个比较外围的设置就好了。

为了定位方便,我希望整个系统里,所有服务节点都有唯一 id 。那么最简单的方案就是限制有限的机器数量、同时设置中心服务器来协调。我用 32bit 的 id 来标识 skynet 上的服务节点。其中高 8 位是机器标识,低 24 位是同一台机器上的服务节点 id 。我们用简单的判断算法就可以知道一个 id 是远程 id 还是本地 id (只需要比较高 8 位就可以了)。

我设计了一台 master 中心服务器用来同步机器信息。把每个 skynet 进程上用于和其他机器通讯的部件称为 Harbor 。每个 skynet 进程有一个 harbor id 为 1 到 255 (保留 0 给系统内部用)。在每个 skynet 进程启动时,向 master 机器汇报自己的 harbor id 。一旦冲突,则禁止连入。

master 服务其实就是一个简单的内存 key-value 数据库。数字 key 对应的 value 正是 harbor 的通讯地址。另外,支持了拥有全局名字的服务,也依靠 master 机器同步。比如,你可以从某台 skynet 节点注册一个叫 DATABASE 的服务节点,它只要将 DATABASE 和节点 id 的对应关系通知 master 机器,就可以依靠 master 机器同步给所有注册入网络的 skynet 节点。

master 做的事情很简单,其实就是回应名字的查询,以及在更新名字后,同步给网络中所有的机器。

skynet 节点,通过 master ,认识网络中所有其它 skynet 节点。它们相互一一建立单向通讯通道。也就是说,如果一共有 100 个 skynet 节点,在它们启动完毕后,会建立起 1 万条通讯通道。

为了缩短开发时间,我利用了 zeromq 来做 harbor 间通讯,以及 master 的开发。蜗牛同学觉得更高效的做法是自己用 C 来写,并和原有的 gate 的 epoll 循环合并起来。我觉得有一定道理,但是还是先给出一个快速的实现更好。

July 29, 2012

开发笔记(24) : Lua State 间的数据共享

最近工作展开后, 我们一共有 10 名程序员在目前的项目上工作。我暂时没有和其他人有依赖关系的工作,最近一周在改进以前做的一些东西,在不修改接口的前提下,争取提供更高的性能,以及完成一些之前没完成的功能,为以后的扩展做准备。

最近值得一提的东西是:关于我们的共享储存的数据结构。

最早在设计的时候,是按多进程共享的需求来设计的。希望不同的进程可以利用共享内存来共享一组结构化数据。所以实现了这么一个东西 。这个东西实现的难点在于:一、共享内存不一定在不同进程间有相同的地址,所以不能在结构中用指针保持引用关系;二、不希望有太复杂的锁来保证并发读写的安全性。

后来,我们采用了 Erlang 做底层的框架。在同一台机器上,只有一个系统进程。所以,这个东西可以不必实现的这么复杂。我抽了三天实现,重新实现了一个。这次不考虑跨进程的问题,只在同一进程的不同线程中,让独立的 Lua State 可以访问同一份结构化数据。至于结构化数据支持到怎样的数据类型,我认为和 Lua 原有的 table 类型大致一致就可以了。

最后,就完成了这么一个东西。我认为到目前这个阶段,这个模块还是比较独立的,适合开源分享。以后的工作可能会和我们具体项目的模块整合在一起,还需要做一些修改,就不太适合公开了。有兴趣的同学可以在我的 github 上看到代码。https://github.com/cloudwu/lua-stable

July 19, 2012

开发笔记(23) : 原子字典

问题是早就提出了的。在 开发笔记 18 中,就写到一个需求:一个玩家数据的写入者,可以批量修改他的属性。但是,同时可能有其他线程在读这个玩家的数据(通过共享内存)。这可能造成,读方得到了不完整的数据。

我们可以不在乎读方得到某个时间的旧数据,但不可以读到一份不完整的版本。就是说,对玩家数据的修改,需要成组的修改,每组修改必须是原子的。

起先,我想用读写锁来解决这个问题。方案想好了,一直没有实现。只是把读写锁的基本功能实现了。

这几天这个问题被重提出来。因为,前段我们都采用了鸵鸟政策,当问题不存在(事实上我们也没有发现实际中出现可观测到的问题)。

反正探讨了好几个解决方案,一开始都是围绕怎么加锁,锁的粒度有多大来展开的。甚至,我们把其中的一种方案都实现出来了,并写了压力测试程序测试。不过,这些方案都不太令人满意。大家担心锁的开销,以及逻辑代码编写者所需求关心的问题太多,导致有死锁的可能性。

昨天差一点决定用一个地图锁来解决这个问题,就是用牺牲同一个地图进程上,玩家间并行的可能性为代价的。这个方案也不无不可。但昨晚躺在床上一直睡不安稳。因为这样做,就失去了一开始我期望用并行方案来设计游戏服务器的初衷。如果这样,还不如全部退化到单地图单进程来编写程序。那么一定有方法是可以避开锁以及避免让写逻辑的程序员去关心数据共享的读写冲突问题的。

July 13, 2012

开发笔记(22) : 背包系统

我们项目的第一个里程碑杯已经比较顺利的完成了。主要是完成最基本的用户注册登陆,场景漫游,走跑跳,远程进程法术等技能攻击,怪物及 npc 的简单行为,等等。尝试了几十个玩家与上百个 npc 的混战,还比较流畅。

中间小作休整,然后开始做第二里程碑的计划安排。这次预计要三个月,完成几乎所有的 MMO 必须的游戏功能了。

其中一个大的任务就是要完成背包、物品、装备、货币、掉落、交易这些相互关联的模块。

下面简单记录一下背包系统的设计实现思路:

June 11, 2012

开发笔记(20) : 交易系统

物品交易和掉落系统不是一期里程碑的内容,手头事情不多,就还是先做设计了。

物品掉落和交易,本质上是一件事情。区别在于玩家之间的物品交换于玩家和系统之间的物品交换。这部分的设计,我在去年初曾经写过一篇 blog 总结过。

这次重新设计实现,做了少量的改动。主要是一些简化。设计原则是,交易服务器独立,简洁,健壮,容易回溯交易历史做数据恢复和分析。

交易服务,我认为是一个后备系统。在数据不出问题时,不大用理会它的存在。它仅仅是用来保证物品不被复制,确定每件物品可以有唯一的所有人。在出现争议的时候,可以作为仲裁机构。

在游戏服务中,玩家或其他实体储存的物品,同样会保存在玩家数据中,利用共享内存读写 。 每件物品记录在对应的包裹格类,直接记录物品名称或指代名称的 id 。即,同样的物品用同样的标识。之外,再额外记录一个(或若干个)物品唯一 id (用 64bit 或 128bit ) 。这样做,避免在访问物品的功能时,额外去查一张表。

交易认证服务,其实只关心一件事情。即,某个物品 id 归属于谁。它不关心物品 id 代表的是什么含义。当然,可以有另一个数据库记录这个关系。在物品的所有者改变时,交易服务来保证其原子性。除了玩家间的转换,还有大量的和系统间的交换。掉落物品其实是从系统改为玩家所有,而玩家销毁物品则是把所有者改为系统所有。

May 15, 2012

开发笔记(19) : 怪物行走控制

这段时间项目进展还算顺利,叮当同学在盯项目进度,我专心解决程序上的各种小问题。

最近我在协助解决 NPC (包括地图上的怪物)的行为控制以及 AI 的问题。

目前,我们的进度还处在玩家可以通过客户端登陆到服务器,可以在场景上漫游,以及做一些简单的战斗和技能动作的阶段。按最初的设计原则,我们的每个玩家是在服务器上有一个独立的 agent 服务的。目前写到现在,和中间想过的一些实现方法有些差异,但大体上还是按这个思路进行的。 关键是在于去除大部分的回调方式的异步调用;编写的控制流程自然完整,不需要太多的去考虑 agent 行为之外的交互性。

如果丝毫不考虑性能问题,我很想把每单个怪物和 NPC 都放在独立服务中。但是估算后,觉得不太现实。在 这一篇 的最后一段已经提过这个问题了。

今天展开来谈谈我的方案。

我想把怪物的移动行为独立出来做,以减少 AI 的压力。也就是说,地图上所有的怪,在设定的时候,都可以设定他们的巡逻路径,或是仅仅站立不动。我希望在没有外力干扰的时候,处理这些行为对系统压力最小。

我不想让怪在没有任何玩家看见的时候就让它静止不动,因为这样可能会增加实现的复杂性,并在怪物行为较为复杂时,无法贯彻策划的意图。

最好的方法还是把之隔离,使其对系统的负荷受控。同时也可以通过分离,减小实现的复杂性。

这个子系统是这样的:

April 18, 2012

开发笔记(17) : 策划表格公式处理

前段时间为策划提供过一些技术上的支持, 设计过一个简单的 DSL 。但随着更多策划加入团队,我觉得这个思路不能很好的贯彻下去。

策划更喜欢通过 excel 表格来表达他心中的数值关系,而不是通过代码语言。我需要找到另一种更切合实际的方案来将策划的想法转换为可以运行的代码。

研究了一下策划历史项目上的 excel 表格后,我归纳了一下其需求。我们需要把问题分步解决,excel 表格到程序可以利用的数据结构是一项工作,而从表达了数据之间联系的结构到代码又是另一项工作。

对于数值运算,有怎样的需求呢?

我更多看到的是一种声明式的表达,而不是过程式的。比如,策划会定义,“血量”这个属性,实际上是等价于“耐力 * 10”。我们把后者定义为一个公式。

许多表格其实就是在不同的位置表达了这种公式推导关系:一个属性等价于另一些属性组成的表达式。而在运行时,根据人物的一些基础属性,可以通过一系列的公式推导,得到最终的一系列属性值。满足这个需求并不难,读出表格里的对应项,做简单的解析就可以了。(这里涉及到另一个问题,表格里的对应项在哪里,今天暂且不谈)

对于这种声明式表达,程序要做的工作是进行一次拓扑排序,把需要先求值的属性排在前面,有依赖关系的属性求解放在后面。这样就可以转换为过程式的指令。

另一种表格称为查表。其实就是表达一种映射关系。如下表:

法术伤害物理伤害
战士0.51
法师10.5

March 31, 2012

开发笔记(16) : Timer 和异步事件

这几天,安排新来的王同学做数据持久化工作。一开始他是将 sharedb 里的数据序列化为文本储存的。这步工作做完后,开始动手把数据放到 Redis 数据库中。我们的系统主干由 Lua 构建,所以需要一个 Lua 的 Redis 库。google 来的那份,王同学不满意。三下五除二自己重写了一个。据说把代码量减少到了原来的三分之一(开源计划我正在督促)。唯一的问题是,如果直接采用系统的 socket 库,不能很好的嵌入我们的整个通讯框架。我们的 skynet 全部是通过异步 IO 自己调度的,如果这个数据服务单方面阻塞了进程,会使得别的进程获得不了时间片。

蜗牛同学打算改进 skynet 增加异步 IO 的支持。

我今天在考虑现有的 API 时候,对比原有的 timer 接口和打算新增加的异步 IO 接口,发现它们其实是同一类东西。即,都是一个异步事件。由客户准备好一类请求,绑定一个 session id 。当这个事件发生后,skynet 将这个 session id 推送回来,通知这个事件已经发生。

在用户编写的代码的执行序上,异步 IO 和 RPC 调用一样,虽然底层通过消息驱动回调机制转了一大圈,但主干上的逻辑执行次序是连续的。

受历史影响,我之前在封装 Timer 的时候,受到历史经验的影响,简单的做了个 lua 内 callback 的封装。今天仔细考虑后发现,我们整个系统不应该存在任何显式的回调机制。正确的接口应该保持和异步 IO 一致:

March 26, 2012

开发笔记(15) : 热更新

这几天我的工作是设计未来游戏服务器的热更新系统。

这部分的工作,我曾经在过去的一个项目中尝试过 。这些工作,在当时一段时间与广州网易其他项目交流时,也对网易其他项目的设计产生过一些影响,之后,也在实战中,各个项目组逐步发展出许多热更新的系统来。

我最近对之前所用到的一些方案,如修改 lua module 的加载策略,增加一些间接层,来达到热更新代码的系统设计做了一些思考。感觉在处理热更新这个问题时,还不够严谨。经过两天的思考,我按我的构思实现了新系统的雏形。

在函数式编程语言中,热更新通常比较容易实现。erlang , lisp 都把热升级做为核心特性之一。函数副作用越小的语言,越容易做热升级:你只需要简单的把新写的函数替换回去就好了。

对于纯粹的请求回应式的服务,做热升级也相对容易。比如大多数 web server ,采用 REST 风格的协议设计的网站,重启服务器,通常对用户都是透明的。中间状态保存在数据库中,改写了代码,把旧的服务停掉,启动新版的服务,很少有出错的。

按我们现在的游戏服务器设计,大多数服务也会遵循这个结构,所以许多底层的子模块简单重启进程就可以了。但和游戏逻辑相关的一些东西却可能要考虑更多东西。

我想把我们的系统分布设计实现,先实现最简单的热更新功能,再逐步完善。如果一开始就指望系统的任何一个部分都可以不停机更新掉老版本的代码是不太现实的,考虑的太多,容易使系统变的过于复杂不可靠。

那么第一步,我们要实现的就仅仅是游戏逻辑有关的代码热更新。而不考虑更新服务器框架有关的模块。我想把这部分称为热修复,而不是热升级。主要用来解决运行时,不停机去修复一些 bug ;而不是在不停机的状态下,更新新版本。在这个阶段,也不考虑可以更新服务间的通讯协议,只考虑更处理这些协议的代码逻辑。

做以上限制后,热更新系统实现起来就非常简单了。

March 22, 2012

开发笔记(14) : 工作总结及玩家状态广播

总结一下最近的工作,修改 bug 以及调整以前设计的细节是主要的。因为往往只有在实现时,需求和问题才会暴露出来。

pbc 自从开源以来,收到了公司之外的许多朋友的反馈。因此找到了许多 bug ,并一一修复了。一并感谢那些在不稳定阶段就敢拿去用的同学。我相信 bug 会初步趋进于零的,会越来越放心。

之前的 RPC 框架 经历了很大的修改,几乎已经不是一开始的样子了。简化为主。几乎只实现了简单的一问一答的远程调用。但在易用性上做了更多的工作。coroutine 是非常重要的语言基础设施,所以这个很依赖 lua 。主要就是提取出函数定义的元信息,把远程调用描述成简单的函数调用,把函数的参数以及返回值映射为通讯协议中的消息结构。这部分和整个框架结合较紧,本来想做开源,但不太想的明白怎么分拆出来。

skynet 是由蜗牛同学用 erlang 实现的,原来是很依赖每个服务的 lua 虚拟机来跑具体应用。最近一个月,我们把 lua 虚拟机从 skynet 模块中完全剥离出来了。而服务则仅以 so 的形式挂接到框架中。lua 服务做为一个具体应用存在。这样,独立启动 lua 和 luajit 也是可以的了。这个过程中,因为 lua 的链接问题 ,我们再次调整了 makefile 中的链接策略。完整的读过了 dlopen 的文档,把每个参数都熟悉了一遍,要避免插件式的 so 服务工作时不要再出问题。

上面这个工作的起因之一是,在我们这里实习的同学回学校了,把他在做的 sharedb 的部分工作交接到新同事手里。需要把这个模块整合进 skynet ,然后围绕它来实现后面完整的场景服务。在一开始我实现的时候,因为早于 skynet 的实现,我用到了 zeromq 做一些内部通讯,这次想一并拿掉。由于不想在这个 C 写的底层模块上嵌入 lua 来写服务,所以整理了框架的 C 接口。

sharedb 这一系列的工作中还包括了数据格式的定义,解析,持久化。我们也做成了独立的服务。关于持久化,目前暂时用的文本文件,尚未 整合入 redis 。具体数据储存怎样做,是以后的独立工作可以慢慢来。毕竟在运行期都是直接通过共享内存直接访问的。

下面谈谈对于下面工作的一些思路。主要是场景服务以及一些同步策略。

March 06, 2012

开发笔记 (13) : AOI 服务的设计与实现

今天例会,梳理了工作计划后,发现要开始实现 AOI 模块了。

所谓 AOI ( Area Of Interest ) ,大致有两个用途。

一则是解决 NPC 的 AI 事件触发问题。游戏场景中有众多的 NPC ,比 PC 大致要多一个数量级。NPC 的 AI 触发条件往往是和其它 NPC 或 PC 距离接近。如果没有 AOI 模块,每个 NPC 都需要遍历场景中其它对象,判断与之距离。这个检索量是非常巨大的(复杂度 O(N*N) )。一般我们会设计一个 AOI 模块,统一处理,并优化比较次数,当两个对象距离接近时,以消息的形式通知它们。

二则用于减少向 PC 发送的同步消息数量。把离 PC 较远的物体状态变化的消息过滤掉。PC 身上可以带一个附近对象列表,由 AOI 消息来增减这个列表的内容。

在服务器上,我们一般推荐把 AOI 模块做成一个独立服务 。场景模块通知它改变对象的位置信息。AOI 服务则发送 AOI 消息给场景。

AOI 的传统实现方法大致有三种:

第一,也是最苯的方案。直接定期比较所有对象间的关系,发现能够触发 AOI 事件就发送消息。这种方案实现起来相当简洁,几乎不可能有 bug ,可以用来验证服务协议的正确性。在场景中对象不对的情况下其实也是不错的一个方案。如果我们独立出来的话,利用一个单独的核,其实可以定期处理相当大的对象数量。

第二,空间切割监视的方法。把场景划分为等大的格子,在每个格子里树立灯塔。在对象进入或退出格子时,维护每个灯塔上的对象列表。对于每个灯塔还是 O(N * N) 的复杂度,但由于把对象数据量大量降了下来,所以性能要好的多,实现也很容易。缺点是,存储空间不仅和对象数量有关,还和场景大小有关。更浪费内存。且当场景规模大过对象数量规模时,性能还会下降。因为要遍历整个场景。对大地图不太合适。这里还有一些优化技巧,比如可以把格子划分为六边形 的。

第三,使用十字链表 (3d 空间则再增加一个链表维度) 保存一系列线段,当线段移动时触发 AOI 事件。算法不展开解释,这个用的很多应该搜的到。优点是可以混用于不同半径的 AOI 区域。

接下来我要来实现这个 AOI 服务了,仔细考虑了一下,我决定简化以前做的设计

首先是最重要的协议设计。以前我认为,AOI 服务器应该支持对象的增删,可以在对象进入对方的 AOI 区域以及退出 AOI 区域时发出消息。

March 03, 2012

开发笔记(12) : 位置同步策略

最近两周,陆续有些新同事到岗,或即将到岗。所以我不想过多的再某个需要实现的技术细节上沉浸进去了。至少要等计划的人员齐备,大家都有事情做了以后,个人再好领一块具体事情做。所以属于我个人的代码不多。我主要也就是维护一下前面我自己实现的模块,以及把之前写的一些代码交接给下面具体负责的同学。

哦,这里值得一提的是,我写的 protobuf C 库 慢慢算是可用了。自从提交到 github 上后,有两处 bug 是不认识的网友指出的。当然我自己在用的过程中发现修正的 bug 更多。现在算是基本完善了吧。接下来还会大量使用到,等整个项目做完,应该就基本没问题了。目前主要是用它的 lua binding 。为了用起来更方便,昨天我甚至自己实现了一套 proto 文件的 parser ,作为一个选项,可以不依赖 google 官方提供的工具来编译了。

前两天我们开程序例会。dingdang 主持会议。提到,在工作全面展开前(还有几个程序和策划没有到位),我们最后的一点时间应该把一些技术点解决掉。其中之一就是解决好即时战斗游戏中的位置同步问题。要做到好的操作手感不太容易,至少现在看到的国内的 MMO 没有做的特别让人满意的。我们比较熟悉的网易的产品,天下二,这方面就比较糟糕。

至少要达到 wow 里的水准吧,在网络不稳定,延迟在 200 到 2000 ms 波动时,玩家还要玩的比较舒服。

话说到这里,我想起 6 年前,我们就做过这方面的探索 ,并写了许多代码验证。花了不少的时间。这次怪物公司同学回头又开始看 paper 重新研究。当年和我一起做这块的人不在了,换了一拨人,感觉场景仿佛相识。不过这次技术储备更完备一些,许多工具,Engine 什么的也完整。应该会更顺利吧。

这块 dingdang 认为很重要,如果一开始不做好,以后没完没了的任务堆过来,就不再能回头弄了。天下就是如此。我当年也是这样想的,只不过花了太多时间去弄了,项目开发的节奏控制的不好。下面把我当初理的思路在整理一次,重新列出来,算是个记录。

February 14, 2012

开发笔记 (10) :内存数据库

离上一次写 开发笔记 快有一个月了。当然,中间我们放了 10 天的长假。

项目的进展比较缓慢、主要是解决一些琐碎的技术问题,客户端的比较多,服务器这边就是节前的一些 bug 修改和功能完善。大部分工作都不是我自己在做。由于感到人手不足,小规模私下的做了一点点招聘工作。也算物色到一两个同学可以过来一起干的。好久没做招聘工作了,都不知道怎么开始谈。唉,我们这里条件不算好,要求还多,都不好意思开口。

可写的东西其实也不少。今天挑一点来记录一下。

话要说回 开发笔记第六篇 ,我谈过结构化数据的共享存储。这个模块细化其实有挺多工作要做。核心部分我自己完成了,没有用太多时间。然后,有位华南理工的同学想来实习。想到他们学校距离我们办公室仅有十分钟步行距离,我便考虑让 logicouter 同学过来试试接手这个模块,做后续开发。当然,他不能全职来做,对旧代码也需要一定时间熟悉,进度比较慢。

我的规划大约是这样的:

核心部分仅仅实现了结构化数据在内存中的表达。但储存在内存中的数据还不能直接使用。C API 虽然实现好了,但性能比较低。我实现的是无锁的数据结构,采用单向链表保存。检索一项属性值都是 O(n) 的复杂度。这显然是不能在项目中直接使用的。为了提供性能,需要再做一层 cache 。在 Lua 虚拟机中,用 hash 表映射。使得读写数据的速度都降低到 O(1) 。因为我自己对 Lua 比较熟悉,所以这步 Lua 的薄封装还是我自己完成的。实测下来,和原生的 Lua 表访问差距不到一个数量级(3,4 倍左右),是可以接受的范围。比常规 IPC 通讯要快的多,也没有异步通讯的负担。以后编写逻辑代码的时候稍微注意一点就好了。

需要额外开发的工作是,要定义一个数据描述的小语言。类似 C 语言的结构定义。在数据储存中,我没有实现无格式信息的字典类型。 map 的 key 都是在结构定义中预先定义好的,内存中存放的是编号。这一是因为实现简单,而是可以实现成无锁的数据结构。再就是数据结构也能严谨一些,减少 typo (可以立刻检查到)。

January 09, 2012

开发笔记 (8) : 策划公式的 DSL 设计

今天很早就起床了,以至于到了办公室还不到 11 点。中饭前有一个多小时可以做各种杂事。

我把周末做的工作和蜗牛同步了一下信息,然后得到了几个新需求。主要就是还是需要在协议定义中加入 protobuf 中等价于 service 的东西。思索了一下,觉得有必要,就花了一个小时把特性加上。 C Binding API 方面还有一点疏漏的地方。大概是源于基于 Erlang 框架下的一些小困难。略微修改了下 C 接口协议就 OK 了。然后很 happy 的去食堂吃饭。

然后我暂时就可以转向 Client 方面的一些需求分析以及解决了。

在生成动画树的数据方面,我们的交换格式使用了某中文本中间格式,最终利用 protobuf 来做二进制持久化。在解析文本格式方面,我操起了多年前耍过的 LPEG 。(我曾经用 lpeg 写过 protobuf 文本的解析工具)这个绝对是解析文本的利器啊。午饭时在食堂大家围在桌子边吐槽 Java ,说道 Java 社区最二的莫过于抓着个 XML 当救命稻草不放,所以便有了各种淡疼的基于 XML 的框架。如果早期 Java 社区能多那么几个受过 Unix/C 传统熏陶过的程序员,就能知道设计 DSL 来解决问题是多么爽快的事情。也不至于在 XML 的树上吊死了。唉,搞得现在积重难返了。

下午正式和策划进行沟通,观看他们这段时间写的各种 excel 表格。我说,你们放开了想怎么把问题表达清楚吧,只要逻辑清晰有条例,信息不要漏掉,怎么表达那些公式都行。我慢慢看,然后规范写法,最终方便程序解析。

以前见过许多项目,有的设计出繁杂的 excel 表格式,然后 export 给程序用;有的干脆让策划写程序代码;甚至有的做一堆漂亮 UI 的公式编辑器。我想最快也最方便达到效果的,莫过于设计一个最小需求集合的 DSL ,让策划认同其语法,然后使用 DSL 来编辑了。

December 15, 2011

开发笔记 (6) : 结构化数据的共享存储

开始这个话题前,离上篇开发笔记已经有一周多了。我是打算一直把开发笔记写下去的,而开发过程中一定不会一帆风顺,各种技术的抉择,放弃,都可能有反复。公开记录这个历程,即是对思路的持久化,又是一种自我督促。不轻易陷入到技术细节中而丢失了产品开发进度。而且有一天,当我们的项目完成了后,我可以对所有人说,看,我们的东西就是这样一步步做出来的。每个点滴都凝聚了叫得上名字的开发人员这么多个月的心血。

技术方案的争议在我们几个人内部是很激烈的。让自己的想法说服每个人是很困难的。有下面这个话题,是源于我们未来的服务器的数据流到底是怎样的。

我希望数据和逻辑可以分离,有物理上独立的点可以存取数据。并且有单独的 agent 实体为每个外部连接服务。这使得进程间通讯的代价变得很频繁。对于一个及时战斗的游戏,我们又希望对象实体之间的交互速度足够快。所以对于这个看似挺漂亮的方案,可能面临实现出来性能不达要求的结果。这也是争议的焦点之一。

我个人比较有信心解决高性能的进程间数据共享问题。上一篇 谈的其实也是这个问题,只是这次更进一步。

核心问题在于,每个 PC (玩家) 以及有可能的话也包括 NPC 相互在不同的实体中(我没有有进程,因为不想被理解成 OS 的进程),他们在互动时,逻辑代码会读写别的对象的数据。最终有一个实体来保有和维护一个对象的所有数据,它提供一个 RPC 接口来操控数据固然是必须的。因为整个虚拟世界会搭建在多台物理机上,所以 RPC 是唯一的途径。这里可以理解成,每个实体是一个数据库,保存了实体的所有数据,开放一个 RPC 接口让外部来读写内部的这些数据。

但是,在高频的热点数据交互时,无论怎么优化协议和实现,可能都很难把性能提升到需要的水平。至少很难达到让这些数据都在一个进程中处理的性能。

这样,除了 RPC 接口,我希望再提供一个更直接的 api 采用共享状态的方式来操控数据。如果我们认为两个实体的数据交互很频繁,就可以想办法把这两个实体的运行流程迁移到同一台物理机上,让同时处理这两个对象的进程可以同时用共享内存的方式读写两者的数据,性能可以做到理论上的上限。

ok, 这就涉及到了,如何让一块带结构的数据被多个进程共享访问的问题。结构化是其中的难点。

方案如下:

December 07, 2011

开发笔记 (5) : 场景服务及避免读写锁

这周我开始做场景模块。因为所有 PC 在 server 端采用独立的 agent 的方式工作。他们分离开,就需要有一个模块来沟通他们。在一期目标中,就是一个简单的场景服务。用来同步每个 agent 看到的世界。

这部分问题,前不久思考过 。需求归纳如下:

  1. 每个 agent 可以了解场景中发生的变化。
  2. 当 agent 进入场景时,需要获取整个世界的状态。
  3. agent 进入场景时,需要可以查询到它离开时自己的状态。关于角色在场景中的位置信息由场景服务维护这一点,在 开发笔记1 中提到过。

大批量的数据同步对性能需求比较高。因为在 N 个角色场景中,同步量是 N*N 的。虽说,先设计接口,实现的优化在第二步。但是接口设计又决定了实现可以怎样优化,所以需要比较谨慎。

比如,同步接口和异步接口就是不同的。

请求回应模式最直观的方式就是设计成异步接口,柔韧性更好。适合分离,但性能不易优化。流程上也比较难使用。(使用问题可以通过 coroutine 技术来改善 )即使强制各个进程(不一定是 os 意义上的进程)在同一个 CPU 上跑,绕开网络层,也很难避免过多的数据复制。虽有一些 zero-copy 的优化方案,但很难跨越语言边界。

December 04, 2011

开发笔记 (4) : Agent 的消息循环及 RPC

话接 开发笔记1 。我们将为每个玩家的接入提供一个 agent 服务。agent 相应玩家发送过来的数据包,然后给于反馈。

对于 agent 服务,是一个典型的包驱动模式。无论我们用 Erlang 框架,还是用 ZeroMQ 自己搭建的框架,agent 都会不会有太多的不同。它将利用一个单一的输入点获取输入,根据这些输入产生输出。这个输入点是由框架提供的。框架把 Agent 感兴趣的包发给它。

我们会有许多 agent ,在没有它感兴趣的包到来时,agent 处于挂起状态(而不是轮询输入点)。这样做可以大量节省 cpu 资源。怎样做的高效,就是框架的责任了,暂时我们不展开讨论框架的设计。

下面来看,一旦数据包进入 agent 应该怎样处理。

游戏服务逻辑复杂度很高,比起很多 web 应用来说,要复杂的多。我们不能简单的把外界来的请求都看成的独立的。把输入包设计成 REST 风格,并依赖数据服务构建这套系统基本上行不通。几乎每个包都有相关的上下文环境,就是说,输入和输入之间是有联系的。简单的把输入看成一组组 session 往往也做不到 session 间独立。最终,我把游戏服务器逻辑归纳以下的需求:

November 19, 2011

游戏数值策划

这篇很淡疼的文章来源于我在微博上的争论。需要列数据,字数限制不合适,所以单列一篇了。

昨天翻出了元帅同学的一篇旧文,游戏数值设计(1):定义与目标 , 转发到微博上。我一向是喜欢看他吐槽的,这篇吐国内的 MMORPG 游戏策划分工的文章,深得我心。

我一直对国内 MMORPG 制作把设计人员分为 文案策划、系统策划、数值策划不以为然。文案拆分出去做倒还说得过去,这所谓系统策划和数值策划的拆分简直就是莫名其妙了。现代电子游戏从桌面游戏一路发展过来,怎样让玩家享受规则,一直是一个整体。如果一个人来设计一个游戏,那么脑子里必然要逐步形成这个游戏做出来是什么样子的,然后细化里面的细节,玩家在他设计的规则下怎么进行游戏。所谓数值设计,是这些细节里重要的一部分。

很难想像,一个设计人员来想游戏的玩法,然后说细节我不管了,有另一个人来负责就好了。然后再有一个人专心于填写 excel 表格,用加加减减模拟一些玩家行为。我看,国内的 MMORPG 产业之所以有向这样发展的趋势,更是因为提起 MMORPG ,游戏整体已经确定下来了,每出一个新产品,必然先找一个已有产品做原型的缘故。即,架子其实已经在那里了,只需要不同角度的修饰工罢了。

如果类比软件开发,我很难想像有一堆人专心做各个模块的接口定义(系统策划),另一堆人专心做接口背后的代码实现(数值策划)。据说这种软件开发模式倒真的存在,只是我没经历过而已。

November 18, 2011

开发笔记 (2) :redis 数据库结构设计

接上回,按照我们一期项目的需求,昨天我简单设计了数据库里的数据格式。数据库采用的是 Redis ,我把它看成一个远端的数据结构保存设备。它提供基本的 Key-Value 储存功能,没有层级表。如果需要两层结构,可以在 Value 里保存一组 Hashes 。

这是我第一次实战使用 Redis ,没有什么经验。不过类似的设施几年前自己实现过,区别不大。经过这几年,有了 Redis 这个开源项目,就不需要重造轮子了。但其模式还是比较熟悉的。也就是说,是按我历史经验来使用 Redis 。

一期项目需要比较简单,不打算把数据拆分到不同的数据服务器上。但为日后的拆分需求做好设计准备。以后有需要,可以按 Key 的前缀把数据分到不同的位置。例如,account 信息是最可能独立出去的,因为它和具体游戏无关。

November 16, 2011

开发笔记 (1)

折腾了好久,终于可以开始正式项目开发了。

之前的这段日子,我们陷落在公司的股权分配问题中,纠结于到底需要几个人到位才启动;更是反复讨论,到底应该做个怎样的游戏。林林总总,终于,在已经到位的几位同学的摩拳擦掌中,叮当决定自己挂帅开始干了。

就这么不到十个人,空旷的办公室,跟我们起先想像的情况不太一样。尤其是主策划还没有落定。我说,叮当,你好歹也是一资深游戏玩家,带了这么多年的游戏部,跟了这么多成功的项目,没吃过猪肉总见过猪跑吧,我就不相信你干着会比别人差。若不是我必须盯着程序实现这块,我都想自己做主策划了。不过有你干,我放心。

主策划的位置,咱们可以先空着,前期工作不能延。产品经理代理一下游戏主策划的位置也是创业公司必须的;正如我这挂牌的 CTO ,除了负责系统架构以外,也得兼个程序员做实现嘛。

经过两天对项目计划表的讨论后,我们今天就算正式开工了。

游戏是怎样的?半保密,不在这里多写,怕大家骂。再说了,这个东西现在说是怎样的,一两年后肯定有变化,多说无益,多解释无益。简单说呢,就是一个战斗系统类似魔兽世界,但系统核心玩法差异很大,更为轻松,更偏重 PvP 的 MMORPG 。为什么是这样一个东西,不想解释,反正不是拍脑袋想出来的。

既然是开发笔记,就写写现在在做些啥,打算怎样做下去。

October 22, 2011

游戏收费方式的一点思考

由于在筹备做新的项目,所以关于网络游戏方面的讨论,在朋友圈子中多了许多。前几天和投资人吃饭,说起前几年盛大提出所谓免费游戏的策略转型,问道有没有可能在未来几年出现新的收费模式。从这个话题引开,饭桌上我跟叮当讨论了很久。

回想那一年,所谓免费游戏之风逐步侵蚀中国网络游戏市场之时,网易的开发团队起初是有所抵触的。我刚到杭州不久,远离广州的开发团队,静心思考了许多。所谓公平的游戏,我想我比大多数人都在乎。把玩游戏变成一个金钱投入的游戏,我是万般不愿意的。但我相信道具收费的方式有它内在的逻辑。实际上,在网易成熟的时间收费游戏,梦幻西游中,点卡交易系统早就存在,并良性运作了很久了。许多玩家已然在游戏中花掉了远远超过时间收费收取的金钱。

那段日子,记得 wding 和我讨论过道具收费的问题。我想了很多谈了很多。后来,wding 希望我回一趟广州,把我的想法和广州的策划同事们分享一下。我便去了趟广州,与各个项目的策划同事开了一天的会。主要是我在讲,但也了解了许多我想了解的具体数据。回来后,写了这么一篇 blog 。可以说,这是我第一次书面总结对时间收费之外的思考。

又经过了 5 年多,市场已经证明了时间收费的局限性。但我依然认为,对游戏本身来说,纯粹的时间收费最容易保证游戏世界构建合理。作为设计者,我们抛开挣钱的问题不谈,时间收费可以保证经济系统做良性发展。当我们把网络游戏真的看作一个虚拟世界时,不干涉虚拟世界中的活动是最好的。玩家的时间固定,玩家的规模容易保持在一定规模,那么玩家整体在虚拟世界中的创造活动就可以被估算出来,达到一个平衡。我们的数值设计都是围绕这点而做的。

一旦换成了道具收费,玩家投入的金钱则不那么容易被估算了。这带来了更多的可变因素。游戏的生命期很可能被大幅度压缩而不被设计人员所知。杀鸡取卵这种事情不那么容易被意识到。

游戏的公平性问题反而是次要的,无论如何,我们无法阻止游戏中的玩家用金钱来换取时间。除非完全禁止掉物品交易,但那恐怕已经不是网络游戏了。

August 10, 2011

MMORPG 中场景服务的抽象

MMORPG 中,场景信息同步是很基础而必不可少的服务。这部分很值得抽象出来,专门做成一个通用的服务程序。

此服务无非提供的是,向有需求的对象,同步场景中每个实体的状态信息。那么,我们分解需求,可以看到两点,一是提交状态,二是同步状态。

每条状态信息其实是由三部分构成,状态对象名(key)、状态值(value)、时间。

玩家、NPC、场景中的可变物品,其实都有可改变的状态。比如对象的位置坐标是最常见的状态。其它的状态还有玩家或 NPC 做的动作,玩家离线,上线,等等。

June 10, 2011

传统 MMORPG 通讯模式实现的一点想法

既然 MMORPG 都有千篇一律同质化的趋势,好歹我们技术人员也应该总结出点东西来,新项目开发可以用现成的模式。

一般来说,MMORPG 服务器要解决的问题无非是,同步玩家的位置,状态,把这些信息广播出去(细分的话,有非战斗环境和战斗环境);需要建立一个聊天服务,供玩家文字交流;有一个信息发布渠道;有任务 NPC 和玩家一对一交流;玩家调整自己的装备(也可以看成是和一特定 NPC 交流)。

以上,我们可以看到几个基本需求是可以正交分解出来,并有不同的实现方式的。

同步玩家的状态,基本上是由玩家自己不断提交自己的最新状态,然后由服务器把这些信息广播给其他人。

聊天服务比较成熟,基本上就是玩家订阅聊天频道,并按权限向频道内发布信息。

信息发布可以看成是一个特殊的聊天频道。

任务 NPC 或是整理自己装备,都类似于向特定节点做请求等待回复。

May 31, 2011

游戏开发中美术资源的管理

今天,公司的同事在 popo 群中讨论 svn 管理美术资源的问题。当资源量大了以后,工作起来效率特别低。因为 svn 储存的是压缩过的文件 diff ,对于大文件,计算最终结果的时候比较消耗 cpu 。

我认为 git 会好一些,但是 git 不满足权限管理的需要。不过更换同质工具并没有解决本质问题。就这个,我想了一下。或者应该换个思路,从工具入手,可能会更适合 3d 游戏的开发。当然,前提是,要有足够的引擎开发和改造能力。对现有工具做适量改进。

我希望从模型编辑器入手。这个工具通常是资源处理环节的最底层,直接面向美术人员的生产工具(max maya 等)。资源在这个工具中被转换到 engine 可用的状态,然后在其它开发工具中被加工。

April 11, 2011

如果从头开发新的 3d engine

最近闲了下来。断断续续的再想,如果业余时间弄一个开源项目玩儿,什么是比较好的题材,适合自己来做。想来想去,3d engine 是一个比较好的选择。以我来看,对外开源如果要吸引到足够的有经验的同学参加,必须项目有一定价值。要可以迅速的搭建工作环境并直接看到结果,这样才能有兴趣迭代做下去。起步必须自己一个人来做,东西得有个雏形。所以必须是自己比较熟悉的领域。

若论熟悉,其实游戏服务器的架构和实现,我这些年考虑的更多,也做的更多。但这个东西不适合做成开源项目。因为受众太小(不是每个人都有机会和意愿去架设网络游戏服务器的),而且它不能单独运行看到结果。没有玩家的游戏服务器是没有意义的。

3d engine 这些年也有在开发。但是公司不可能允许开源已有项目。如果想玩儿,从头做是唯一选择。而且重新开始有更多的乐趣,再造轮子原本就是程序员们的欢乐源泉之一。如果自娱是唯一目的的话,就不必过多考虑商业引擎必须考虑的问题。比如兼容更多的底层 3D API ,做全平台兼容,支持低端硬件等等。甚至于还要去考虑日后防外挂等无聊的问题。

March 16, 2011

服务器排队系统的一点想法

今天突然想到的,先记下来。

以前考虑过这个问题,写过一篇 blog 。我今天想到,其实可以把排队完全独立出来。和原有系统分离。这样,所有不支持排队的游戏系统,只要简单加上就可以用了,不用对系统结构做大的调整。

想法是这样的:

March 09, 2011

梦幻西游服务器 IO 问题

当多核解决了 CPU 运算能力问题,当 64 bit 系统解决了内存不足问题,IO 问题依然让人困扰。

梦幻西游的服务器从更早的产品延续,已经跑了 10 年了。当初只求快速把项目做出来,用最简单的方法做出来,保证稳定可以用,自然遗留了无数问题。逻辑脚本中充斥随手可见的磁盘操作。

最终,当磁盘操作堆积起来,尤其是阻塞方式请求,并行的过程全部都在磁盘访问处串行起来了。固然整个系统的处理能力并没有下降。但用户的反应时间却会变长。

仔细分析了问题后,我发现,系统利用磁盘其实是有两种不同的用途。

一是备份需求,定期需要把数据持久化下来。因为服务器数量很多,硬件故障率几乎是每周一到两次。所以必须保证定期(不长于半小时)的数据备份。避免硬件故障的意外导致长期回档。

二是数据交换的需求。由于游戏逻辑写的很随意,以快速实现的新功能为主。许多脚本中随便读写文件,提供给逻辑需要来使用。这部分甚至许多是阻塞的。整个游戏逻辑进程串行执行逻辑,任何点的阻塞都会导致服务阻塞。这有很大的历史原因在里面,是不可能彻底修改其结构的。

根据系统调用的监测,我们发现,在定期写盘的高峰期段,逻辑进程中偶尔的读文件操作 API 可能用长达 1 到 2 秒读一个较小的文件。据我理解,导致这个现象的产生多是因为硬盘设备上的操作队列被阻塞。有大量未完成的磁盘 IO 工作在进行,导致系统延迟。

理论上,所以真正写到磁盘上的操作都应该被安排到优先级很低,保证所有的读操作可以以最快的速度完成。这样才能让反映速度变快。即,写盘操作,应该先刷新内存 cache ,然后把真正的写操作推迟到 IO 队列尾上。中间有任何的读操作都应该允许插队完成。由于我们的逻辑进程只有一个线程在跑逻辑,而所有的读文件操作都只发生在这个线程里。(其它工作进程都无读操作)整个系统对磁盘的读操作本身是不会竞争。而写操作则几乎是允许无限推迟的。

可惜,我们很难在 os 的底层控制 IO 操作队列的细节。

今天,想了一个变通的方案来解决这个问题:

February 25, 2011

ZeroMQ 的模式

在需要并行化处理数据的时候,采用消息队列通讯的方式来协作,比采用共享状态的方式要好的多。Erlang ,Go 都使用这一手段来让并行任务之间协同工作。

最近读完了 ZeroMQGuide。写的很不错。前几年一直有做类似的工作,但是自己总结的不好。而 ZeroMQ 把消息通讯方面的模式总结的很不错。

ZeroMQ 并不是一个对 socket 的封装,不能用它去实现已有的网络协议。它有自己的模式,不同于更底层的点对点通讯模式。它有比 tcp 协议更高一级的协议。(当然 ZeroMQ 不一定基于 TCP 协议,它也可以用于进程间和进程内通讯。)它改变了通讯都基于一对一的连接这个假设。

ZeroMQ 把通讯的需求看成四类。其中一类是一对一结对通讯,用来支持传统的 TCP socket 模型,但并不推荐使用。常用的通讯模式只有三类。

January 26, 2011

极不和谐的 fork 多线程程序

继续前几天的话题。做梦幻西游服务器优化的事情。以往的代码,定期存盘的工作分两个步骤,把 VM 里的动态数据序列化,然后把序列化后的数据写盘。这两个步骤,序列化工作并没有独立在单独线程/进程里做,而是放在主线程的。IO 部分则在一个独立进程中。

序列化任务是个繁琐的过程。非常耗时(相对于 MMORPG 这个需要对用户请求快速反应的环境)。当玩家同时在线人数升高时,一个简便的优化方法是把整个序列化任务分步完成,分摊到多个心跳内。这里虽然有一些数据一致性问题,但也有不同的手段解决。

但是,在线人数达到一定后,序列化过程依然会对系统性能造成较大影响。在做定期存盘时,玩家的输入反应速度明显变大。表现得是游戏服务器周期性的卡。为了缓解这一点,我希望改造系统,把序列化任务分离到独立进程去做。

方法倒是很简单,在定期存盘一刻,调用 fork ,然后在子进程中慢慢的做序列化工作。(可以考虑使用 nice)做完后,再把数据交到 IO 进程写盘。不过鉴于我们前期设计的问题,具体实现中,我需要通过共享内存把序列化结果交还父进程,由父进程送去 IO 进程。

因为 fork 会产生一个内存快照,所以甚至没有数据一致性问题。这应该是一个网络游戏用到的常见模式。

可问题就出在于,经过历史变迁,我们的服务器已经使用了多线程,这使得 fork 子进程的做法变的不那么可靠,需要自己推敲一下。

January 12, 2011

网络游戏物品校验系统的设计

网络游戏若要有支持一个稳固的经济系统,服务器底层必须有一个可靠的数据服务。要设计出精简的数据协议可不容易。它需要保证在发生异常(可能是硬件异常,也可能是软件异常)时,不会出现物品/货币丢失,复制的问题。

使用带事务的数据库是一个不错的选择,但对程序员的要求较高。程序员需要仔细考虑每个数据操作如何编写事务才是安全的,还需要考虑数据操作的异步性。这对需求变化迅速,量又比较大的游戏,做的好真的是很困难。

我思考了很久,几经易稿,大约得到了这么一个东西:

数据存储和合法性校验应该分开,独立为不同的服务,这样才容易做的稳固。也就是说,数据服务不必做到完全的完备,简单的去读写修改数据即可。这部分,我倾向于用简单的 key/value 方式储存数据到数据库,可以自己实现,也可以用 redis 这样的现成产品。不必使用事务机制。

但是我们应该提供一个强的校验系统,所有的虚拟物品发放、转移,都应该经过这个校验系统。一切操作都需要经过事后的核对。由此系统来修正数据异常。

January 06, 2011

梦幻西游服务器的优化

在历史工程上修补是件麻烦的事情。

前两天说起梦幻西游服务器的优化。这几天我到广州住下来,打算专门花一周时间搞定这件事。由于以前都是网上聊天,只有坐到一起才能真正理解问题。

目前,梦幻西游,只使用单台机器,最高配置 8 个 CPU ,配置 8G 内存。就算最热闹的服务器,也用不完这些资源(大约只用满了 3 个 CPU ,一半的内存)。核心程序差不多就是 10 年前写的,从大话西游延续至今。这两年一直在享受免费的午餐,随着硬件配置提升,现在单台服务器同时在线容量达到一万两千人。观察服务器回应速度的图表可以发现,目前的问题在于,定期会出现反应迟钝的现象。周期性的,服务器回应时间会超过 1000ms 。查得原因在于那个时候,磁盘 IO 非常拥塞。有定期保存玩家数据的服务对 IO 的占用,以及 SA 做的定期备份数据的脚本占用了大量的 IO 时间。最终造成了机器负荷过重。

IO 负荷过重最终怎样影响到游戏服务的性能,这个暂时不过于深入探讨。我这两天主要是分析以有的系统结构,并想一下改进方案。

December 24, 2010

梦幻西游服务器 IO 的一点优化

关注梦幻西游服务器的性能问题,是源于前几天跟同事的聊天。谈到能否把梦幻西游服务器做成无盘站,或是放进虚拟机里,便于日常维护管理。

意外的了解到,现在磁盘 IO 性能居然成了梦幻西游服务器的瓶颈。而不是 CPU 或是网络带宽。据我所知,梦幻西游的服务器数据储存是这样做的:

主游戏进程不负责储存,一切都在内存中。所有玩家的数据就是内存数据结构。只是在玩家登陆的时候去读取一下本地的文本文件,以及登出的时候把数据序列化成文本,然后保存在本地文件中。

为了防止中途发生意外,游戏进程会定期把内存全部数据序列化,然后通过共享内存的方式让另一个 IO 进程不断的把数据保存在磁盘上。

这些都是 10 年前做的设计决策,无论是否合理,都已经稳定运行了很多年了。不少朋友问起,我们的游戏服务用的什么数据库系统,我都只好说,我们没有用数据库,只用了文件系统。面对诧异的目光,我都不想过多解释。好吧,其实我也觉得 SQL 神马的都是浮云。

目前在 8 千人以上同时在线的服务器上,磁盘 IO 非常繁忙,据说已经影响到了正常的游戏。由于长年的修修补补,整个系统已经不是上面提到的那些单纯。还有一些额外的 IO 操作,这些被定期写盘的 IO 操作影响到了。

November 05, 2010

多进程资源共享及多样化加载

梦幻西游在去年出了个新版本,在这个版本中,采用了 3d 技术渲染人物。我参加过部分的技术讨论。总的来说,对于公司的关键产品,是以稳定性为第一。所以不希望对程序做大改动。最好以独立扩充的模块为主。所以最终采用的技术是用 3d 技术渲染成图片,再依靠旧的程序框架跑起来。

采用 3d 渲染,主要是为了解决人物换装时的图片资源组合爆炸问题。当然还有更绚的特效等。

最近,梦幻西游的项目经理提出,新的版本性能依旧有些问题。当老的版本可以同时打开 5 个客户端时,新的版本只能开两个。允许用户同时开多个客户端,对梦幻西游这款产品非常重要。我最近几天就开始着力解决这个优化问题。

August 31, 2010

在游戏引擎中播放视频

美术同学给我们的游戏做了段片头视频,正要加到产品中去时,才发现我们的引擎居然没有提供视频播放的功能。我想这个东西开源库一大堆,那做起来还不是小菜一碟。可没想到还是折腾了一整天才搞定。

第一件事是考察 License 。结果用的人做多的 ffmpeg 是 GPL 的,不适合我这种商业应用。虽然有一部分功能可以以 LGPL 的方式使用,但是遵守起来也是麻烦一大堆。想了一下还是做罢。我可不想学暴风影音和 QQ Player 那样上耻辱榜

最终考察结果,居然没太多的选择。还是 google 的 vp8 最好。License 最为宽松,所以就去下载了一份 libvpx 的最新版试用。

August 26, 2010

游戏资源的压缩、打包与补丁更新

9 年前,我设计了网易游戏的资源包以及补丁包的数据格式。

当初的设计目的是:方便解析,快速定位资源包内的文件,方便更新、每次更新尽可能的节约带宽。这些年来,虽然各个项目修修补补的改进了资源包的格式,但本质上并没有特别大的修改。

一开始我们直接把需要打包的文件连接起来,在文件末尾附上文件索引表。当初为了快速定位文件名,文件名做了 hash 处理,可以用 hash 值直接定位文件。而资源包里并没有储存文件名信息,而是保存在一个额外的 index 文件中。这个 index 文件并不对外发布。所以直接对资源包解包是无法准确还原文件名的。

btw, 暴雪的 mpq 文件也是作类似处理的。除非你猜测出文件名,否则也很难对文件名还原。网上许多 mpq 解包工具都针对特定游戏附了一个额外的文件名列表。

和许多其它游戏 Client (比如暴雪的 MPQ 文件)不同。我们的包格式里文件与文件之间是允许有空洞的。这是考虑到资源包文件都比较大。如果用传统的打包软件运作的方式:从包内删除一个文件,就重新打包或移动内部数据。在玩家更新资源的时候,就会有大量的文件 IO 操作。比如 WOW 或 SC2 在更新的时候,下载更新包的时间往往只占整个更新时间的一小部分,大部分时间花在把补丁打在已有的资源包上。

如果频繁更新客户端,对于用户,这会有很讨厌的等待。

所以当初考虑到这个因素,我们在删除包内文件时,并不移动资源包内的数据,而是把空间留下来。如果新增加的文件较之小,就重复利用这个空间。如果利用不上,就浪费在那里。这有点像内存管理算法,时间久了,资源包内会有一些空洞,但也是可以接受的。

同时,还有另一个方式更新新的资源。那就是将需要更新的文件单独打包,以相同文件名(后缀不同)保存在用户硬盘上。游戏引擎在读取资源的时候,优先在更新的资源包内检索。这个方式在 Id soft 的 Quake/Doom 系列中也有采用。

为了保证用户补丁更新速度。我们的补丁中并不是保存的资源包内的小文件。而是在开发机上以增量方式重新打包。补丁文件其实是整个资源包的 diff 文件。由于前面所述的打包方案,这个 2 进制 diff 文件其实可以做到很小。尤其对某些文件的局部修改,对整个资源包的影响很小。

在公司,有后来的同事质疑过这种方式,觉得其对减少补丁体积的作用不大。反而增量打包增加了许多制作补丁包的时间。主张直接在补丁中放入更新的小文件,然后让最最终用户机上以小文件为单位做 patching 。

的确,2 进制 diff 的作用有限,现在很多项目改用文本数据格式,很小的修改就会影响整个文件的 diff 结果。不过原始的设计也有其历史原因。因为 10 年前硬盘 I/O 速度很慢,而大话西游在设计时又需要实现无缝加载的大地图。所以地图文件的格式是经过特别设计的。这种方式很适合地图文件的修改和更新。另外,对于未压缩的图片文件的更新也有其意义。

July 19, 2010

游戏多服务器架构的一点想法

把网络游戏服务器分拆成多个进程,分开部署。这种设计的好处是模块自然分离,可以单独设计。分担负荷,可以提高整个系统的承载能力。

缺点在于,网络环境并不那么可靠。跨进程通讯有一定的不可预知性。服务器间通讯往往难以架设调试环境,并很容易把事情搅成一团糨糊。而且正确高效的管理多连接,对程序员来说也是一项挑战。

前些年,我也曾写过好几篇与之相关的设计。这几天在思考一个问题:如果我们要做一个底层通用模块,让后续开发更为方便。到底要解决怎样的需求。这个需求应该是单一且基础的,每个应用都需要的。

正如 TCP 协议解决了互联网上稳定可靠的点对点数据流通讯一样。游戏世界实际需要的是一个稳定可靠的在游戏系统内的点对点通讯需要。

我们可以在一条 TCP 连接之上做到这一点。一旦实现,可以给游戏服务的开发带来极大的方便。

December 29, 2009

点光源的管理

我们 3d engine 的点光源相关的代码,以前设计的是比较糟糕的。最近几天,我决定自己动手重新设计和实现这块东西。性能倒是次要的东西,重要的是要把模块分离,减低耦合度。

这个光源模块设计要解决的问题在于:

GPU 的处理能力,目前看来比较有限,不可能实时处理无限数量的光源。所以,当你的场景里设置了许多的光源时,必须可以拣选出最可能影响被渲染物体的光源信息,把这个信息交给驱动去处理。

November 26, 2009

骨骼动画的插值与融合

花了三天时间搞定了困扰了我半年的事情,心情非常之好。

是这样的。我们的 3d engine 中的骨骼动画的插值融合总有点问题。如果是两个变化比较大的动画想从一个过度到另外一个,效果总是不太对。对于图形程序,很难用测试驱动的方式开发的缘故之一,就是因为对与不对有很大程度是人的视觉感受,而不是可以简单校验的数值。想写一个判定正确与否的程序,估计不比把代码写正确简单。

所谓不对呢,问题其实老早也想明白了。现象就是有些动作之间过度像纸片一样,而不是直觉中,人物用合理的方式运动。这时,只显示骨骼动画信息中的关键帧反而效果更好。(这些关键帧,是美术人员在 max 这样的软件中制作出来并导出的:或是用动作捕捉而来的)

其问题在于,没能对骨骼的变换信息做正确的插值。

原来这部分代码写的很脏乱,且原作者已经离开。后继人员要么知其然而不知其所以然的在上面修补,要么就没心思把这个东西真的弄对。(毕竟暂时看起来,这并不是迫切需要解决的问题。它不会引起程序崩溃,也不影响开发进度。)

我倒是一直很想自己来重新设计制作这一块,可一直忙于更多别的事情。加上,大学线形代数也没学好,把其中的数学基础知识补上,还是颇要些精力的。

拖了这么久,终于,这周一咬牙,求人不如求己。还是自己弄吧。

September 18, 2009

AOI 的优化

去年写过几篇过于 AOI 模块的设计。将 AOI 服务独立出来的设计早就确定,并且一定程度上得到了其他项目组的认可。(在公司内部交流中,已经有几个项目打算做此改进)

最近一段时间,我在这方面做进一步的工作。(主要是实现上的)

首先,基于 KISS 的考虑,删除了原有协议中的一些不必要的细节。比如,不再通知 AOI 模块,对象的移动速度。移动速度虽然对优化很有意义,但毕竟是一冗余数据。考虑这些,容易掉入提前优化的陷阱。

其次,增加了一条设置默认关心区域的协议。这是出于实用性考虑。

September 09, 2009

游戏动作感设计初探

最近两年似乎大家一致的想把网络游戏向所谓动作感、打击感的这个方面推进。我想是因为,已经有太多人厌倦了打木桩吧。

我们在三年前就一直在考虑这个问题,但似乎走向了歧路。一个劲的考虑如何解决网络同步问题,怎样在一定网络延迟下保持公平,怎样避免作弊……

结果,尝试了多次后的结论就是,我们依然没有找到正确的方法解决以上问题,暂时把这一块搁置起来。最近,公司又有许多同事热切希望给游戏加入更多的操作感。我们又重拾这个课题。这次,换了个角度看这个问题:假设我们完全不考虑网络这个因素,就算做单机游戏,我们其实也没有太多动作游戏的设计实现经验。那么首先要解决的是如何把游戏的操作手感做出来,之后才应该考虑怎样在网络环境下实现它们。

为此,我纠结了很久。花了很长时间思考,以及编写了大量的代码印证,总算感觉自己的思路稍微清晰了一点。做一下记录,不一定正确,只是写出来或许可以给其他同僚提供一些思路。在内部讨论的过程中,这些也被广泛的质疑,许多人都不太认同。所以,一家之言,辜枉看之。

July 29, 2009

3d engine 中的贴图资源管理

今天有同事问了我一个涉及贴图管理的问题,看起来他们是想改进他们项目现在用的 3d engine (前些年由网易方舟工作室开发的)。我们随便聊了一下,最后的结果是他们取消了一开始的一个需求。

是的,知道自己最终需要什么是特别重要的,不要把过程当成目的。

下面要写的和今天的讨论无关,只是想记录一下:我们的 3d engine 中的贴图资源管理方案。

资源管理是一个很复杂的模块,当然我们应该尽量简化它。在此之前,我曾经记录过我们的资源管理模块的设计变迁。上篇文章到现在也有几个月了,我又做了些设计上的简化,不过变化不大,暂时不提。

而贴图资源作为一种特殊资源,又有所不同。仔细斟酌之后,我把它放在高一层次专门管理。

如前辈所言:确保特殊的情况是真的特殊。在《The Elements of Programming Style》和 《Unix 编程艺术》中都有提及。

那么,第一个问题是:为什么其是特殊的?

May 10, 2009

树结构的管理

要写过多少代码才能得到哪怕一点真谛?

多少年过来,我在潜意识的去追求复杂的东西。比如我自幼好玩游戏,从小到大,一直觉得玩过的游戏过于简单(无论是电子游戏还是桌面游戏),始终追寻更复杂规则的游戏,供我沉浸进去。或许是因为,有了更高的理解和控制复杂度的能力,就可以更为轻松的驾御复杂性。

这很好的解释了 2000 年到 2004 年我对 C++ 的痴迷。还有对设计模式的迷恋。

Eric S. Raymond 说:尽量不要去想一种语言或操作系统最多能做多少事情,而是尽量去想这种语言或操作系统最少能做的事情——不是带着假想行动,而是从零开始。禅称为“初心”(beginner's mind)或者叫“虚心”(empty mind) 。

代码写多了,问题见过了,甚至是同一问题解决多了。模式这种东西自在心底,不必拿出来。时时的从零去想,总能重新明白一些道理。

为什么说语言重要也不重要,算法和数据结构重要也不重要。对要解决的问题的领域的理解很重要(即明白真正要做什么)。理解了,我们才可以用面向对象,用模式去套问题;可理解了,我们又不真的需要这些繁杂的抽象。

闲话放一边,今天想谈谈树结构的管理。

April 06, 2009

为 lua 插件提供一个安全的环境

wow 开放了一套用户自定义的插件系统,很多人都认为,这套系统是 wow 成功的因素之一。反观国内乃至韩国的网游,至今没有一款游戏能提供相当自由度的用户自定义插件系统。

最开始,暴雪是想让用户可以由用户甚至第三方自定义操作界面。后来,这套基于 XML 和 lua 的插件系统不仅仅用来做界面了。

从我在网游行业从业这么多年的经验,游戏界面相关的开发也是颇费人力的。甚至于,Client 开发到了维护期,几乎都是在 UI 上堆砌人工。一套自由的插件系统,对于开发商和用户是双赢的。

但是,插件系统也是双面剑。最典型的负面问题就是安全。越是自由,越是给机器人制作开放了自由之门。这里暂且不提这个方面的问题。首先关注一下另一个:尽可能的保护系统中不想被插件系统访问的数据,避免利用插件编写木马。

March 25, 2009

关于地表贴图

首先,我不是混 3d 圈的人,所以我对圈子里各种地表的渲染方法并不熟悉。今天写这一篇,纯粹是因为前段时间帮负责 3d 模块开发的同事解决了一个算法问题。

从我有限的 3d engine 相关知识来看,我最早了解的是类似 2d tile 拼接的地面渲染方式。玩过星际的地图编辑器的就知道我指的什么。等到魔兽三,虽然 engine 改成 3d 的了,但是方法没有大的变化。就是用贴图做成图素去拼接起来。依稀记得无冬之夜也是这种方法,时间太久,不敢确认。

后来,我看了 big world 的方案,就是在地面高度网格的顶点上记录一个贴图混合系数。最多在一个 chunk (记得似乎是 25 格)里最多可以用四张不同的贴图,然后根据地面网格的顶点混合系数,混出最终的效果。

这个方法比较节省资源,听说很多 3d engine 对地面的渲染都这么干。

不过 big world 默认的地面网格,每个格子单位长为 4 米。结果地面的过度就显得非常粗糙了。这点,天下2 的场景美术私下跟我抱怨过。说是不能做出 wow 里的效果。到了创世西游,应美术的强烈要求,单位格被改成了 2 米,很大程度是为了让地表渲染的更漂亮。当然数据量也增加了 4 倍。

March 10, 2009

关于游戏中资源管理的一些补充

最近在实践中印证了我的一些想法,是关于资源管理的。确认自己的猜想是正确的总是件开心的事情。

先简单回顾一下以前写的几篇东西:

资源的内存管理及多线程预读

这不是最早的思路,但是是我能找到的最早在 blog 上公开并实际实现的思路。里面提到的部分东西,尤其是多线程设计部分,后来都一一被换掉,理由是过于复杂并实际上没能达到要求。或是细节上很难有 stable 的实现。

胡思乱想续

经过一些思考,以及经历了许多实践后。对上面的东西,一部分做了肯定,一部分做了否定。肯定的是,资源管理的大策略是对的:那就是和其它部分正交化,尤其是要独立于游戏逻辑之外。生存期不应和游戏逻辑耦合。

重构

到目前最后一次在 blog 上记录思考和资源管理相关的问题。认清的最重要一点就是:

至于多线程预读的问题,资源模块已经可以了解资源数据文件间的关联信息。简单的开一条线程 touch 那些未来可能读到的文件即可。把预读和缓冲的工作交给 OS ,这应该是一个简洁有效的设计。并不需要太多顾虑线程安全的问题。

那么今天想说什么?

February 28, 2009

关于地图编辑器的一些想法

大凡 RPG 游戏(包含 MMORPG),在制作期都需要开发一个地图编辑器。

早年 2d 游戏就有这个需求,基于 Tile 的 Engine 如是,基于整图的 Engine 亦如是。到了 3d 游戏,这个东西更少不了。

对于 3d engine 中附带的地图编辑器,通常有几个用途:拉地形(也有基于模型的地形)、摆放物品(以及特效)、设置玻璃墙(或是设置障碍格);有时也包括设置摄象机、事件触发点、摆放 NPC 等等。

在很长时间里,我都倾向于制作一个大而全的编辑器 IDE 。但最最近两年,随着编程风格及开发习惯的改变,我对这种做法产生了怀疑。

November 29, 2008

XMPP 简单研究

最近想做一个游戏服务器和 IM 互通的服务。最初的想法是可以增进游戏帐号的安全,比如游戏用户可以通过绑定一个 IM 帐号,从而不用登陆游戏就向游戏服务器发一些指令。这些指定通常是用来冻结一些帐号的功能。而游戏服务器也可以通过 IM 帐号向离线用户发送一些关键消息。这样,只需要解除绑定 IM 帐号需要一定的时间,或使用更安全的途径,即可以让游戏帐号更加安全。(至少,游戏用户可以从 IM 上获知他的游戏帐号每次登陆登出的时间、IP 等等)

后来细想,这里面可以做的东西还有许多。玩家会因为多一个信息通道,而更轻松的去玩那些需要长期驻留的游戏。游戏厂商也可以多一个挽留玩家的渠道,甚至用来宣传新游戏或游戏的增值服务,等等。好处不再列举。

其实、绑定 IM 帐号和绑定手机号本质上区别不大。只不过,IM 帐号几乎是零费用,又不像 SMS ,控制权掌控在移动手里。IM 更适合做双向交流(SMS 的双向交流不那么方便,而且对用户和游戏运营商都有经济负担)。独立提供一个 Game2IM 的服务供众多游戏运营商使用也是个有趣的主意。和 SMS 一样,只要给出一个简单接口让游戏运营商调用,把游戏网络和 IM 网络互联就可以了。

实现这个想法有两个方案。其一是制作各种 IM 的机器人,通过机器人和用户 IM 沟通。这个方案技术门槛稍低,有许多现成的机器人可以使用。缺点是,受 IM 提供商的限制(比如好友数量限制)。无法使用机器人的签名针对性的向用户传递特有的消息。除非你为每个游戏用户定制一个机器人,但那样,每个机器人都需要单独一个连接,对资源消耗过大。

第二个方案就是使用已有的 IM 互通方案,自己提供一个特有的 Game-IM 网络,跟已有的 IM 网络互通。比较流行的 IM 互通协议用基于 SIP 的 SIMPLE 和起源于 Jabber 的 XMPP 。

我最常用的 IM 是 google talk ,本身就实现了标准的 XMPP Client 和 XMPP Server 协议;而我们的 网易 popo 也实现了 XMPP 的 s2s 网关。我想研究一下 XMPP 是个不错的选择。

November 25, 2008

AOI 服务器的实现

以前谈过多次 AOI (Area of Interest) 的实现,因为我们的游戏尚在开发,模块需要一个个的做。前期游戏世界物件不多的时候用个 O(N^2) 的算法就可以了:即定时两两检查物件的相对距离。这个只是权益之计,这几天,我着手开始实现前段时间在 blog 上谈过的 独立的 AOI 服务器

既然是独立进程,设计协议是最重要的。经过一番考虑,大约需要五条协议,四条是场景服务器到 AOI 服务器的(列在下面),一条由 AOI 服务器发送消息回场景服务器。

  1. 创建一个 AOI 对象,同时设置其默认 AOI 半径。注:每个对象都有一个默认的 AOI 半径,凡第一次进入半径范围的其它物体,都会触发 AOI 消息。

  2. 删除一个 AOI 对象,同时有可能触发它相对其它 AOI 对象的离开消息。

  3. 移动一个 AOI 对象,设置新的 (2D / 3D) 坐标,并给出线速度的建议值。

  4. 设置一个 AOI 对象相对另一个的 AOI 半径,覆盖其默认设置。注:AOI 半径可分两种,一为进入半径,二而离开半径。通常一开始,每个 AOI 对象为其它对象均设置一个进入半径;当消息触发后,由场景逻辑重新设置一个离开半径。例如,一个 AOI 对象的默认半径是 10 米,当它被创建并指定坐标后,任何物体进入它的 10 米范围内,都会立刻由 AOI 服务器发送出一个 AOI 消息;而后两者之间不会再自动触发消息。场景服务器收到消息后,可主动向 AOI 服务器设置新的 AOI 离开半径 12 米,当此物体远离到 12 米远后,离开消息触发;下一步再由场景服务器重置进入半径。

这套协议相对简单,可以满足游戏的一般需要,并隐藏 AOI 服务的实现细节。对象全部由 handle 方式传递,由场景服务器自己保证 handle 的唯一性。在我这次的实现中,每个 AOI 对象同时只能拥有一个 AOI 半径触发器,但是协议本身无此限制。

下面,我们再来看一下实现细节。

July 27, 2008

把 AOI 的部分独立出来

应该是在 blog 上第 2 次讨论 AOI 的问题了,另外还要算上一篇写完没有公开的。

昨天刚出差回来,晚上跟前大唐的服务器主程聊了一下。扯到了 AOI 的问题,我提了个分离这个模块的方案,在此记录一下。

AOI 主要有两个作用,一个是在服务器上的角色(玩家或 NPC )做出动作时,把消息广播到游戏地理上附近的玩家。当进程中负责的玩家不是很多的时候,可以直接对整个进程内的连接广播。这样的处理最简单,随着硬件的提高,可能是未来的主流方法。但是目前,为了减少处理的数据量,尤其是带宽,我们通常需要 AOI 模块裁减一些数据。

第二,当玩家接近 NPC 时,一旦进入 NPC 的警戒区域,AOI 模块将给 NPC 发送消息通知,以适合做一些 AI 的反应。

July 20, 2008

一个简单的寻路算法

周末跟同事聊天,问了一下最近公司新项目的一些需求。发现现在无论是玩家还是策划,纷纷要求引擎可以实现自动寻路。虽然我对此不以为然,但其实这也不是什么难题,打算随便做一个好了。

我们现在的 engine 是用矢量线段描述场景中的障碍的。这些线段无所谓人工标记还是从场景模型中自动生成出来,又或者是用传统的打格子的方法变换出来。在这些障碍线构成的矢量场景中寻路其实不是什么难事。

我们只需要在场景中标记出若干 waypoint ,这些 waypoint 一般在大片无障碍区域的中央,以及分叉路口。游戏中故意设计迷宫为难玩家绕来绕去又没有什么玩点内容在里面是没有什么意义的。(除非是“不可思议的迷宫”这种以迷宫为重要玩法的游戏)所以,大多数游戏场景,路径的拓扑关系复杂度非常有限。 waypoint 靠人工设置就足够了。

btw, 自动生成 waypoint 也不是难事,反正这个东西不会是性能要点,机器生成 waypoint 不会比人工设置的糟糕太多。算法就不展开谈了。waypoint 的设置要点是:场景中所有可达区域都能看到至少一个 waypoint ,这个要求可以用程序检测。

我们把所有 waypoint 间可以直线连接的线路连起来,得到一张图。可以预存下这张图,以后的工作则非常简单。

June 24, 2008

摄象机接口的设计

最近在调整 3d engine 的接口。前段时间把 GC 模块加到底层后,很多部分都需要修改实现了(可以简化许多实现代码)。既然重构代码,不如就再次审查一遍接口的设计,结合最近的一些想法,重新弄一下。

嗯,那么 3d 引擎是什么?跟 3d api (Direct3D 或 openGL)有什么区别?固然,engine 如果只是做 3d api 的一层薄薄的封装,抹平各套 3d api 的差异。那么,就过于底层,显得小了。

如果为特定形式的游戏写死代码,让开发者写一些 MOD 插件就可以形成不同的游戏,那么又显得太高。在这种高层次上,游戏类型会限制于 engine 的实现。比如魔兽争霸 3 就直接用户写 MOD ,并的确有人以此发展出许多玩法。但你永远不可能在 魔兽争霸 3 的基础上写一个 MOD 实现第一人称射击游戏。

所以我指的 3d engine ,是处于 3d 游戏软件结构中间地位的东西。

那么,我们的 3d engine 到底要解决的是什么问题?做 engine 绝对不是以我能在 3d api 的基础上扩展出什么东西为设计向导。因为,对于完成一个软件,是一个从机器实现域映射到问题域的过程。这两个领域的模型是不同的。3d api 完成的是实现域的扩展,engine 则应该完全从实现域到问题域的一个变换,让开发者可以用最接近问题域的语言来表达问题。

May 31, 2008

3d 引擎中对场景数据的接口设计

目前是我第一次设计 3d engine ,虽然主要贡献 3d 方面代码的同事不是第一次做了。我们做了两年多,大方向上是我在把握设计。但是毕竟是没有什么经验,也就老在修改。

这个周末再审以前做的东西,觉得接口上还需要调整一下。比如精灵的控制接口、摄象机的控制接口、场景描述的接口等等。以一个游戏开发人员的角度来看,我需要接口是什么样子的?

我希望是最方便的描述虚拟世界中各样东西的相互关系,引擎当隐藏住不必要的细节,比如 3d 方面的数学知识,专业化的术语,复杂的坐标转换等等。今天写下这些,不是定论,而是做些思考的记录。

我想知道,对于 3d 引擎的使用人员,如何描述虚拟场景,暴露出怎样的接口是比较合适的。

May 23, 2008

关于 openGL 的 4444 贴图

我们在实现游戏界面时,用了一张 RGBA 4444 的贴图做 buffer 。最近同事测试效率时总不满意,发现上载 4444 贴图时,openGL 表现出来的性能实在是太差。(显卡为 ATI X300 ,最新版驱动)

几个人一开始怀疑是显卡或驱动程序的缺陷,进而想换总做法,不用软件渲染界面的方案。改把界面元素全部放到显存里。前几年,我为天下二设计界面的模块时,也有同事有此疑惑,编写代码做过比较。记得当时的结论是:仅仅从性能角度上考虑,把界面所用资源全部放到显存里,并不能提高太多速度(反而可能性能更低)。当时用的 Direct3D 做底层,似乎没有今天遇到的问题。

May 07, 2008

数值调整、模拟器、编辑器

最近做游戏数值有点头大。也研究了一些游戏的设定,有点心得。举个很小的例子来谈谈:

wow 里的护甲对物理伤害吸收是乘一个百分比的,其公式为:

min (护甲/(护甲 + 400 + 85 * 敌人等级) , 0.75)

怎样理解这样一个公式的内在含义?为什么会设置成这样?

May 01, 2008

那些日子(二)

我在北京生活了半年。接触北京这个城市要更早一些。大约 85 年的时候去过,一周时间几乎玩遍了北京所有的景点。小时候的记忆特别美好,我在那个时候爱上了北京。记忆中,天安门广场是那么的大,紫禁城的宫殿如此雄伟…… 以至于回到学校时,不知道用怎样的形容词才能形容。

10 多年后,我在学校的机房上网,泡 bbs ,做个人主页,写一些关于游戏的自己的想法。交了许多的网友。大家都是业余的,年轻气盛,想自己做出好玩的游戏。王欣是第一个给我发 email 的职业游戏人。更早的 97 年,国内有两家大的游戏公司,前导和腾图,有如台湾的大宇和智冠。可惜生不逢时,前导做不了大陆的大宇,腾图也远不及智冠。

腾图命中注定的散掉,王欣的八爪鱼工作室从腾图分的出来。98 年,王欣邀请我在假期去北京玩,他把我带进游戏这个圈子。我又有机会站在天安门广场,原来并没有那么大。我想,如果毛泽东纪念馆挪一下地方的话,广场会更宽广一些,和我儿时记忆中的一样。小时候怎么就没这种感觉呢 :) 。

那个年代,我成月成月的逃课,去北京帮王欣做些兼职工作,并听他说些早年北京游戏圈子有趣的八卦,有如今天我给新人说起往事。回忆起来,自己其实什么也没做,似乎又做过点什么,反正最后一次,我拿了一小笔兼职工资。不过没有花掉,因为回到学校的时候,一个同班同学缺钱交学费,我全部借给了他,毕业那天他才凑起来还我。

当然,身在北京,我就有机会四处拜访网友同好。随即发现,其实许多网友都已经专职在做游戏了。有些人后来再没怎么联系,比如曾经在金洪恩做《自由与荣耀》的 3d engine 的 rick huang ,我还记得他在那个晚上,他抱怨他的后继者让代码从几万行膨胀到十几万;又比如做《独闯天涯》的郭巍,他念叨过来北京前没有钱吃饭,小组里每个人一个冷馒头就可以啃一天,脑子里只想着把游戏做出来 ……

两个很重要的朋友,也是在那段时间见的面 —— 余雪松和吴东黎。我们通过在网易个人空间(当时叫 N-SPACE)交换链接认识的。他们搭档了很多年(直到今天),经历很传奇。

April 30, 2008

那些日子(一)

明天就是五一假期了,同事都已放假。我不打算在假期加班,因为加班也无事可做,手头上的工作都需要与人合作。

前几天和新同事吃夜宵,大家聊的异常兴奋,我也忍不住开始想当年。当年那些美好的日子,记忆已经很模糊了。我想再过个两年,估计我都不能准确回忆起那些曾经对我影响深刻的日子准确的时间。是时候记下点什么,对自己是一种纪念。

我这人有个优点,选择性记忆,那些不快的回忆很容易随风而去。活在我记忆中的人们,对他们只留下感激。我也曾经爱写日记,很早我就写电子日记,记在自己的机器上,PDA 上,当我有一些不愿意再回忆的事情时,我会个将整个文件加上密码,长长的一次性密码,保证自己只能记住一小段日子。当这段日子过去,密码就消失在记忆中。然后再也打不开这些文字,等到下次更换硬盘,无论我多么的想再看一眼当年的自己,也无能为力,只好把加密过的文件删去。

我想我就是这么成长过来,没有什么挫折的感觉被反复咀嚼,都已经抛在脑后。生命中没有什么不可以失去的,这个道理很早就明白了。我曾经懊恼过丢失了大量的源代码、自以为写的不错的文章、早年的聊天记录、珍贵的日记、数年的电子邮件…… 最后我明白了,一切的一切不过是身外之物,我能拥有回忆中最美好的部分,那么已经是特别幸福了。

不过也正是如此,以下的记录也只能是我努力的回忆。或许因为时间久远,跟真实有所偏差,或许从我的角度只看到的事物的一面,但是、我可以保证,并没有故意在叙述中掺差虚假的东西。

是的,我想讲一个真实的故事,一个拥有数千万玩家的游戏诞生的故事。我并不喜欢这个游戏系列本身,但是我为这个产品自豪。我的代码曾运行在几千万用户的机器上,作为一个程序员,还有什么比这更让人满足的呢?也许有,比如让这个用户数量再扩大 10 倍。

April 27, 2008

游戏数值公式的表象与本质

周末、睡的比较晚,起的也比较晚。总的来说比平常睡眠时间长一些,所以到了这周一的第一个小时,我还是全无睡意。随便写点什么,关于游戏的,关于游戏设计方面的东西。

鉴于我在游戏设计领域还没有什么建树,远不如游戏程序设计方面有发言权。甚至对游戏设计说三道四的话,还不如在软件开发上乱侃几句有分量。在下充其量也就是个没吃过猪肉天天能见着猪跑的家伙,如果有猪肉已经吃腻味的方家读到这里,请一笑了之,别跟我一般见识。

本来在本文成文前打的腹稿中想举出些实际的例子来,可是发现怎么都会或多或少的得罪圈子内的策划朋友。罢了,只说些空话好了,算我做了半个月游戏数值,闲暇时发的点牢骚。

从为我们的游戏设定角色基础属性,以及设计战斗伤害的计算公式开始。

April 21, 2008

不那么随机的随机数列

曾经看过这样一种赌徒的策略:假设在一场赌大小的赌博游戏中,赔率是 1:1 ,而庄家不会出千,开大和开小的概率均等(皆为 50%)。赌徒一开始压一块钱,如果他压错了,那么下一次就压两块,再错继续加倍。一旦压对,赌徒永远可以保证有一块钱的进帐。并且从 1 块钱重新开始。

看起来,这种策略能保证永远包赚不赔。但实际上为什么没有人用这个方案发财呢?

放到现实中,试图采用这个策略去赌博的人,几乎都会赔的倾家荡产(当然只要是赌博,差不多都是这个结局)。不要怪运气,不要怪庄家出千,因为这个策略从一开始就注定了失败。

April 10, 2008

游戏的帧率控制

我曾在不同场合,多次向人表达我对游戏程序设计的一个重要观点:时间控制,对于游戏程序设计至关重要。

这是因为,大多数电子游戏,都是一个实时人机互动系统。在非人机互动的软件中,软件及硬件一起是一个封闭系统,时间参数对这个系统是否正确工作是无意义的量。这样的系统,我们尽量应该设计成自动化工作模式,只要有正确的输入,得到正确的输出即可。通常,这样的系统的开发,还应该有自动化测试的流程去驱动它。

但实时人机互动软件则不一样,我们得把人看成系统的一部分。人和机器的交互是通过人眼、人耳、键盘、鼠标等完成信息交换的。如果把人脑看成一个线程、计算机看成另一个线程,我们几乎没有能力实现一个资源锁,利用锁的方式保证系统工作永远正常。人脑在全速运转时,适时得不到正确的信息输入,整个系统就可认为出现了故障。

可见,在这样的系统中,时间控制显得多么的重要。

April 06, 2008

负反馈系统在模型动画控制中的应用

最近在解决 3d engine 接口的一处设计问题。我们知道,在 3d 游戏中,engine 的接口设计往往比性能更加重要,这决定了 engine 是否好用,甚至是否能用。简单的功能堆砌不是 engine 。

目前我想弄清楚的一个技术点就是,当模型置入场景后,如果播放的动画本身有位移,引擎应提供怎样的接口,让使用者最为方便的表达他意图。

具体到一个问题点来说,当我们的美术人员制作了一组四足动画奔跑的动画后,怎么在游戏中最自然的表现出来。

就我有限的眼界所知,有许多人在制作 3d engine 时是这么规定的:美术需要把动画调整成原地奔跑的样子。(或者换个方式,在从制作软件中导出时,让原本具有位移的动画变为原地移动)如此,就可以在最终表现时,自由控制播放的速度。

但是稍加思考,就知道这样做导致的图象表现和我们期待的样子是有偏差的。下面让我们来分析一下。

March 15, 2008

基于 lua 的热更新系统设计要点

很久没写 blog 了,主要是忙。项目排的很紧,一个小项目上线,发现不少问题,所以把多余精力都投进去了。最后人手不够,亲自上场写代码。在不动大体的情况下,最大能力的修改了一些设计,并把能重写的代码重新写过了。

这一周,写了三天半程序(其中通宵了一次)。平均每天写了千多行程序。基本上把原来项目里用的几万行东西替换下来了。重构总是能比老的设计更好,不是么? :) 不过这事不能老干,个人精力再充沛。故而还是找到称心的合作人比较好,也不能啥都自己做啊。程序员的效率差别可不只十倍二十倍的差别这么少。btw, 前段时间通过这里找到的新同事不错,呵呵。如果有缘,还想再找一个能干的。我相信,聪明人在一起做事可以获得更多快乐。

闲话扯到这里,总结下这两天做项目的一点经验。那就是:当我们需要一个 7*24 小时工作的系统时,实现热更新需要注意的要点。当然,这次我用的 lua 做实现,相信别的动态语言也差不多。只是我对 lua 最为熟悉,可以快速开发。

March 02, 2008

MMO 的排队系统

这两周那是忙的天昏地暗。都是些琐碎的事情,两个项目的。理理代码,发发邮件,打打电话,改改 bug ,开开会,签签字,写写报告。周末也加了一天班,工作居然是安装一个论坛系统,外加修改 css ,以及修改模板,调整版面。没办法,时间紧,人手少。

btw, 在服务器上装 php 时,因为开始 ports 没有更新,出了好多问题。mysql 一开始忘记装 gbk 的支持,困扰我老半天。鄙视一下公司购买的某著名 php 写的论坛系统,居然默认不是用 utf-8 编码的。

闲话扯到这里,今天想谈一下上月底,四年逢一次的好日子里,我们公司憋了好久的《天下2》终于又一次体验测试的事情。

February 19, 2008

角色动作控制接口的设计

最近一段时间工作的侧重点转移,我从服务器的设计转到客户端这边来。

自从最底层引擎的架构完成后,我们的客户端留了两个人在全职写代码,一个人负责 C 层面的 3d engine ,另一个人负责设计高层面的应用接口,并在此基础上完成游戏 demo 。

从程序员角度上看,人都是相当不错的。要设计有设计能力,有编码有编码能力。代码审美观上大家也很一致,所以我们几个人一起工作的很愉快。

唯一美中不足的是,做 demo 的程序员游戏接触的太少,所以在操作手感调整方面有点相形见绌。需要经常性的跟负责战斗动作系统的策划沟通调整。而策划毕竟不太明白程序背后再用怎样的方法实现,最终还是有点不尽人意。如果有足够的时间,倒总是能调整好。不过现在项目进度比原来计划有所提前,我就直接参于进来参合了。加快这方面的开发进度。

上周花了几天时间通读了所有相关的代码,整体设计还是不错的。不过这两天用下来,还是觉得有点别扭。

我希望游戏能有比较流畅的操作感,可以灵活的操作游戏角色做出各种动作,包括战斗时的腾挪,组合技能等。写了不少脏代码尝试我希望的手感时,终于发现一些设计问题了。

February 17, 2008

键盘毕竟不是手柄

周末在写 demo ,为了给同事示范我希望得到的操作手感。作为一个能写点程序的游戏设计者,自己随时实现出来看看恐怕是不可多得的优势了。

我们的游戏有一定的动作成分在里面,所以我不想采用鼠标控制角色的行动,以对拍砖头的形式表现格斗场面。本质上我更偏好 console game 和游戏手柄的操作感。不过在 PC 平台上,手柄并不普及,只能在最普及的输入设备——键盘和鼠标上下点工夫了。

手柄控制方向主要有两种:

其一是模拟杆,在 PSP ,PS ,Wii 等手柄上均有安装。好处是至少可以感知两级力度,并可提供非常细的方向信息输入。控制游戏角色行动时,操作感很不错。

鼠标通过移动可以提供方向信息,甚至通过移动速度可以模拟出力度的差别。但毕竟鼠标缺乏力的反馈,且必须通过移动才能提供信息,本身不能保留状态(鼠标的 button 可以提供状态,所以很多 fps 把前进操作设置在鼠标的按键上),和手柄的感觉还是差了很远。

btw, IBM 的 TrackPoint 如果特别针对去开发,其实是个不错的游戏输入设备,可惜还是太专有化了。

其二,就是类似任天堂的十字键。由于有专利,到别的厂家那里换成了其它形式,不过大体还是差不多的。这类设备可以精确的输入八个方向,在格斗游戏中用的最多。比如我的最爱 VF4 EVO 的 PS2 版,干脆就禁止了模拟杆的输入,只允许用八方向键。

对应到 PC 上,大多数游戏用 WASD 四个按键模拟这个设备。不过要小心的是,由于键盘硬件设计的缘故,大多数键盘处理三个以上按键同时按下时,会发生所谓锁键问题。比如我的 DELL 键盘,同时按下 W 和 D 键后,再按 E 键,系统就接收不到 E 键的消息。这类情况试硬件决定,不同键盘处理能力不同。据说有比较强悍的键盘可以处理任意 7 个按键同时按下的消息,我就没有试过了。

好在现在的键盘处理两个按键的能力还是绰绰有余的,用 WASD 模拟八个方向的输入足够了。

November 30, 2007

讲稿

如果不出意外的话,我现在正在准备在 2007软件开发2.0大会 上的一个演讲:大世界网络游戏服务器的构架 。马上就要上讲台了。

其实主要是介绍下我们这两年正在开发的网络游戏引擎服务器部分的设计。这里有 PPT 可以下载

November 25, 2007

随机数有多随机?

作为一个常识,每个程序员在做入门学习时,都会被老师谆谆教导:我们用的编程语言中的随机函数,只能产生出伪随机数。它有它的内在规律,只能作为对显示世界的随机事件的近似模拟。接下来,我们通常会被传授随机种子的概念。以及用物理上更随机的量做种子。比如系统时间、两次敲击键盘的时间间隔、多次移动鼠标的偏移、甚至系统出错的出错信息码等等。

作为游戏数值策划,除了加减乘除,用的最多的数学概念恐怕就是随机数了。有经验的数值策划或许从他的前辈那得知计算机中程序产生的随机数并不太可靠;或者他本身就受过程序方面的训练。如果游戏项目更幸运一点,担当数值策划的他是一个数学爱好者,并读过诸如《计算机程序设计艺术》这样的技术书籍,那么事情会好的多。可惜大多数境遇下,策划们从不深究计算机随机数背后的细节,也不太关心所谓“伪”随机数究竟“伪”到什么程度。

最近几天,有测试人员向我抱怨,我们游戏中某些概率设定总感觉有点怪怪的。似乎跟文档上的不同。

这种抱怨并不少见,许多网络游戏玩家都在抱怨系统生成的随机数不太对劲。善良点的玩家会归咎到自己的 RP 上,阴谋论者则指责系统作弊。运营着的游戏背后,数值策划和程序员们有苦说不出。

有必要科普一些数学常识,也作为我周末读书的一些笔记。

November 15, 2007

思维的惯性

晚上在办公室晃荡,对面的同事在加班写代码。我凑上去看看在写什么。我向他了解了后明白了,大约是服务器上角色 buff 的实现吧。

BUFF 这个术语是现在网络游戏中非常常见的。给角色加一个 BUFF 通常意味着对虚拟角色的一些数值上的临时修正:例如,攻击力 +5 ,防御 -10% ,速度加倍,等等。

玩过魔兽世界的朋友应该很容易理解这些。通常游戏里的 BUFF 设定比我上述的例子更加的复杂。

这里不谈游戏设定,谈谈实现。

October 07, 2007

网络游戏的技术基础

晚上已经回到办公室,飞机下降的时候天气不好,颠簸的很厉害,居然有点晕机的感觉。可能是这几天身体不是好的缘故吧。下了飞机大雨,直接打车到楼下的 KFC 吃垃圾食品,然后就坐到了熟悉的椅子上。

真还别说,环境不同,心境就不同。一坐上办公桌前的椅子上,就有做事情的感觉。沾着家里的椅子就是放松休息了。

刚才发现了透明的一篇文章:从游戏看货币,大约是对我前段时间写的一篇东西的评论。

正好这两天在家没事也在想这个问题,想着想着就不由自主多写几句了。

July 19, 2007

模型顶点数据的压缩

缘起:听天下组的同事谈起,游戏的资源数据量太大,导致了加载时间过长,影响到了操作感。最终不得不对数据进行压缩。

当玩家配置的物理内存足够大时,往往资源数据压缩会适得其反。因为多出来的数据解压缩的过程会占用掉太多的 CPU 时间。物理内存够大的话,操作系统会尽量的拿出物理内存做磁盘 IO 的 cache ,即使你的程序不占用太多的逻辑内存,而反复从硬盘加载大块数据,也不会感觉到磁盘速度的影响。当数据量大一个一个临界时,被迫做不断的外存内存的数据交换,这个时候,数据压缩就会体现出优势。因为解压缩的 CPU 时间消耗是远远小于硬盘 IO 所消耗的时间的。

btw, 玩现在的 MMORPG 往往配置更大的物理内存得到的性能提升,比升级 CPU 或显卡要划算的多,就是源于这个道理。通常 MMORPG 都需要配置海量的资源数据,来应付玩家的个性化需求。

天下组的同事暂时只做了骨骼动作信息的压缩。这个这里暂且不谈,我想写写关于模型顶点数据的压缩。记录下这两天在思考的一个有损压缩算法。

July 13, 2007

游戏中的货币

曾经写过一篇关于网络游戏中的货币经济系统的文章,由于我本人对经济学只是浅尝,读书不多,所知甚为有限,怕是贻笑大方了。但这不影响我对此继续抱有极大的兴趣。最近,又再思索那个困扰游戏设计者的问题:究竟如何用游戏规则来稳定虚拟货币。

首先,我认为现在的网络游戏中,货币并不完全等同于现实社会中的货币。现实社会中,货币只是一种能直接起交换手段或支付媒介作用的东西,它本身没有价值;货币的投放由央行控制。但在大多数网游中,货币本身有了实用价值,它可以从系统那买到各种补给品,或是可以直接增加虚拟角色的能力。而且货币本身也多由玩家自己的主动行为生产。

同时,在健康的虚拟经济环境中,虚拟货币也起到了现实货币同样的作用——作为支付媒介。

虚拟货币的多重职能,使得在架构虚拟社会的规则时,需要万般小心。而虚拟货币也往往因其多重职能,使得游戏中的市场调节能力减弱。现存的几乎所有网游中都没有设计出一个央行的角色,还真怪不得通货膨胀得不到有效的控制了。

那么,能否构建出一个更接近现实社会的经济模型呢?对于设计一个较之现存游戏更有趣好玩的经济系统,即使这不是唯一之道,但至少是一条值得尝试的路。今天在这里记录一些尚未深思熟虑的想法,权作自己的笔记。方家读过,请一笑了之。

July 06, 2007

唯一的游戏世界

今天写下上一篇 blog 之后,跟另一个组的同事讨论了我的这篇文章。他们的项目先是多服务器的架构,后来重构后改成了单服务器的设计。我们的讨论第一个出现的分歧就是在于对多服务器设计的复杂度会造成的影响。

讨论了一段时间后发现大家的对一些问题的定义从一开始就有区别。回头来大家换个角度讨论了一下,发现其实那些分歧都是不存在的。

讨论告一段落后,我浏览了一遍聊天记录,觉得有必要总结一下 :)

三年前,我跟公司一些策划同事有接连几天的争执:到底玩家是否需要一个宏大的虚拟世界?或是我们若实现这样一个虚拟世界,到底有没有价值?该怎样维护这样的一个大世界,让它不会受到一些暂时性设计 bug 的毁坏性影响?

可以说,三年前的争论是导致我成立独立团队做现在这样一个项目的重要起因之一。因为当时,我几乎得不到任何的支持,策划,程序,美术方面等等几乎都是不赞成的意见。

那个时候,我也的确找不到确凿的证据来说服大家。但今天,思路清晰了许多。

July 05, 2007

游戏服务器组间的通讯

网络游戏世界的构建有越来越大的趋势,游戏设计者希望更多的人可以发生互动。技术人员的工作就是满足这些越来越 BT 的需求。

我们目前这个项目由于是自己主导设计,而我本人又是技术方案的设计者。所以,技术解决不了的问题就不能乱发牢骚了。作为游戏设计者,我希望整个游戏世界的参于者可以在一个唯一的大世界中生存,他们永远有发生互动的可能。注意这里只是保留这种可能性,实际上,即使是现实社会,每个人的社交圈子都不大。即使是千军万马的战场上,无论是将军还是士兵,都不需要直接跟太多人互动。

我们的游戏的技术解决方案仍旧是将游戏大世界分成若干独立服务器组,人为的将人群切分成更小的独立单位。这里,技术上需要解决的是:服务器组间可以灵活的交换数据。

June 21, 2007

平台无关的游戏引擎

一直以来,我们的新引擎一直以跨平台为设计目标。这倒不是说,我们有多重视非 Windows 平台的用户。只是我觉得,一个好的设计一定是很容易做到平台无关的。对于做跨平台开发这件事情,公司里支持的人寥寥。光老丁都几次三番劝我不要把精力花在无谓的地方。

唉,怎么说呢。写了这么多年程序,我一直把编写代码和设计软件作为一件很有趣的事情在做。所以我并不认为我做的一切是一种工作,它是我的玩具。早就不需要担心后半辈子的生活问题,所以没有人可以阻止我做想做的事情,更何况我认为良好的设计造就优秀的产品。今天看似多花的精力,日后慢慢的会给出回报。

回想当年做西游的客户端,我固执的把内存的占用量控制在 64M 左右,让低配置的机器也可以流畅的运行。为了达到这一点,当年多花了好多的精力。做内存中的精灵压缩,做地图的动态加载,做图象数据的 cache 和主动交换,改动许多我认为会更多占用内存的数据结构,阻止美术的一切可能过于消耗内存的设计。

这些在我离开西游的开发后的这么多年里,配合硬件的发展,往日做的那些,得以让后来的不断扩展玩法和美术资源可以稳定的进行。后期的开发维护人员可以用足够的东西来“浪费”。前期的种种约束和正是为了后期的无拘束扩展,直到这些项目顺利的走完生命期。

我希望几年之后,依旧有人感谢我当下做过的努力。

June 08, 2007

2007 年玩家主流显卡配置

本文版权归所有、谢绝任何形式的(部分或全部内容)转载

花了两天和同事一起做了一次数据挖掘工作。

前几天拿到了大量的显卡标识串统计数据(百万条级别),开始做数据分析。因为数据不太规则,所以颇花了些工夫。我想数据的来源和分析结果未得到公司允许前,都不能公开。不过我想在这里透露一些大略信息,对从事游戏开发的朋友(尤其是商业 3d 游戏)会有不小的帮助。

目前,NVidia 、ATI 、Intel 三家几乎垄断了玩家的显卡市场,市场份额超过了 95% 。如果你硬要考虑一些小厂商的话,不妨关注一下 VIA(包括其收购的 S3)和 SiS 。算上这两家已经涵盖了 99% 以上的玩家机器了。

一些若干年前风光过的显卡,如 Voodoo Trident 等现在依然有人用,但如今已经非常稀少。当然,还有人用一些更为稀少的专业显卡,或是我没听过的品牌的垃圾卡(疑是电子垃圾)在玩游戏 :D

May 24, 2007

DXT 图片压缩

这两天在写 DDS 格式的解码程序。DDS 是微软为 DirectX 开发的一种图片格式,MSDN 上可以查到其文件格式说明:DDS File Reference

其中的 DXT 图片压缩格式,现在已经为绝大多数 3D 显卡硬件所支持。(它使用了由 S3 公司所发明的一种有损图象压缩算法。btw, 在我的那本书中,P232 有所提及)。DXT 格式 也叫作 S3TC ,现在可以被流行看图软件直接显示的图象格式中,只有 .dds 文件支持这种压缩。为了开发方便,我们的引擎也就支持了 .dds 文件的加载。

一起做引擎的同事希望即使在硬件不支持的时候,我们也能正常加载并使用贴图,所以便有了对 DXT 软解码的需求。

好在以前研究过一些,写起来也不麻烦。

May 11, 2007

资源的内存管理及多线程预读

网络游戏的 client 开发中,很重要的一块就是资源管理。游戏引擎的好坏在此高下立现。这方面我做过许多研究和一些尝试。近年写的 blog 中,已有两篇关于这个话题的:基于垃圾回收的资源管理动态加载资源

最近在重构引擎,再次考虑这一个模块的设计时,又有了一些不算新的想法。今天写了一天程序,一半时间在考虑接口的设计,头文件改了又改。最终决定把想到的东西在这里写出来,算是对自己思考过程的一个梳理。

March 30, 2007

游戏服务器内的组播

游戏服务器在设计时,会有许多组播的需求。比如一个 NPC 需要向周围的观察者播出它的状态信息。无出不在的各种聊天频道中需要向频道中的参于者广播聊天消息等。

通常,我们会为每个组播的请求维护一张列表,然后再把需要的信息包发送给指定列表上的多个连接。这个过程在很多地方都会遇到,一个设计的不太好的引擎,再这里有可能滋生诸多 bug 。尤其在多服务器的设计时尤其如此。

这两天,我试图寻找一种简洁统一的解决方案。

March 29, 2007

游戏服务器处理多个连接入口的方案

最近在考虑为一组游戏服务器配置多个连接入口。这个需求来至于我们的国情。作为大的游戏运营商,势必要考虑国内的网络状况——南北不通的现状。以往别的公司的代理游戏,由于不是自己开发,都选择了一个实际的方案:在北网通和南电信各放若干组服务器。北边来的在北边玩,南方住的安居在南方。

我们的游戏却不行,因为我需要一个完整的大世界,必须解决南北互通的问题。据我所知国内运营的游戏 EVE 是比较好解决了这个问题的。

我们自己的游戏大多也解决了,只是宣传上还是鼓励玩家登陆相应的服务器。我们的解决方案本质上很简单。建立有多个出口的机房,同时拥有电信和网通的线路。或是用自己的线路互联电信和网通的机器。这后者普通用户自己在家也可以做,只要你肯花钱,同时购买电信的 ADSL 于网通的宽带即可。目前许多城市两者都向大众提供服务。

当然,最终我们还是需要编写服务器的程序员做一些配合。

March 20, 2007

为何麻将如此流行?

曾经有种流行的说法是,中国人喜欢打麻将,日本人喜爱围棋,而美国人多打桥牌。这类文章 google 一搜一大把。

当然中国人或是韩国人也喜欢下围棋,而日本人也没以前那么爱围棋了。八十年代的时候,似乎在国内校园里也非常流行打桥牌。而桥牌在美国现在还是不是那么那么流行,就不得而知了。所谓国民的特性从所喜爱的游戏中可看出总总之观点,我一直是不置可否的。今天写的跟这些无关。

由于家庭渊源,我个人不喜欢打麻将。但对麻将也没有感情上的厌恶。小时候即没见父母打过,也不曾见过传送中的因为麻将而导致的家破人亡、妻离子散。只觉得麻将无非一种有竞技性质的桌上游戏而已。

我对麻将所知非常有限,规则陆陆续续的了解了许多。大约知道各地的细则不太一样,大体却是相差不大。似乎体育总局定过一个国标麻将 ,我们的 popo 游戏 就是按这个做的,据我所知,喜欢玩这个规则的不多。大家还是按地域各打各的一套。这也是边锋游戏成功的秘诀之一。

那么,在我看来的一个普通游戏,甚至没有统一明确的规则,为何风靡整个华人世界呢?

February 12, 2007

我们需要 photoshop 之外的选择

游戏的美术工作有时是挺枯燥乏味的。我在公司这几年看到太多美术,连续几天的辛勤工作,只为了将一批图按同样的模式修改成另一个样子。甚至这个工作只是修修通道、转转图片格式等等。

由于有些工作流程比较繁琐,甚至可能会有些变化,并不是每次操作的人都愿意使用 photoshop 中带的批量处理模式来提高效率。最终造成了一种奇怪的现象:明明人手充足,每个人每天都在重负荷的工作,但是整体的完成度却提不上去。在许多时间里,大家干的并不是创造性的工作,而是一些并不需要多少脑力的活儿。

今天碰到件事,我们需要给一些人物角色图片勾一下边。也就是把动画图素展开成带 alpha 通道的图片序列。然后把通道部分提取出来,轮廓扩大、羽化、上色,再叠加回原来的图。这样做的目的是可以把游戏里的角色从背景中突显出来。

February 01, 2007

多服务器的用户身份认证方案

当游戏服务器群达到一定规模后,让用户只从一个入口连入会给这个入口带来很大的压力。这样,我们就需要让服务器群中的多台机器都允许用户直接连接。

当服务器开放给用户直接登陆后,必须面临的一个问题就是用户身份认证的问题。

大多数提供网络服务的公司都做了一套统一的用户认证系统,比如微软的 passport ,网易的通行证,等等。为了避免重复验证用户身份而给用户认证系统带来过大的负担,云风在这里给出一个参考解决方案。

January 15, 2007

3D engine ,中间层的缺失

我们的游戏引擎已经开发了一年半了,其中 3d 引擎部分也做了近一年。

一个好的引擎,渲染部分总是容易做的。因为面对的问题比较单一,资料也很丰富。需要克服的问题很有针对性,即使短期解决不了的问题,放在那里也关系不大。我们团队有两个同事拥有完整 3d engine 开发经验,所以在渲染引擎接口设计部分也不会走太大弯路。(不过事实上,因为基础构件的重写,渲染引擎也是几经修改的)

最近 3d engine 部分又面临一次大的重构。起因是这样的:

December 29, 2006

碰撞检测

我始终认为,在 MMORPG 里采用多边形碰撞检测是件很傻的事情。当然傻不是坏事,基于多边形碰撞检测,一帧检查一次的这种做法,实现起来非常简单。很符合 kiss 原则。我们只需要一点点数学知识和一点点编程技能就能做出来了。反正 client 上,也就检查一个主角。加上可以使用简化的碰撞模型,进一步的减少运算量。

但是放在服务器上,这个运算量可不小。所以这几天我寻思着找个更好的方法出来。

December 06, 2006

在 Windows 下使用 Timer 驱动游戏

在 Windows 平台下写游戏,相比 console 等其它平台,最麻烦之事莫过于让游戏窗口于其它窗口良好的相处。

即使是全屏模式,其实也还是一个窗口。如果你不去跑窗口的消息循环,一个劲的刷新屏幕,我估计要被所有 Windows 用户骂死。

那么怎样让你的游戏程序做一个 Windows 下的良好公民呢?

October 29, 2006

用四叉树管理散布在平面上的对象

周末一直在考虑怎样在游戏服务器中保存和管理那些有平面坐标的对象。希望能方便的查到每个对象附近的东西。以往用过的方法是给(平面)场景打上格子,然后再把每个对象放入相应的格子中。

这次又遇到这个问题,却不想用网格的方法来解决问题了。

原因主要是这么几点:一,用网格对于大场景特别消耗内存,而且不太适合做无限延展的场景。二,查询每个对象周围的东西时,不太方便。格子的粒度越小,速度越慢。

虽然这两个问题,都可以通过改进算法来回避。不过这次,我想尝试一下用四叉树解决这个问题。

October 19, 2006

数据服务器的设计

今天开始正式开始做数据服务器。在这点上,我希望一组服务器上所有的逻辑服务遇到的数据存取需求都通过一个单一的数据服务器完成。而且,写入数据是单向通讯的。即,逻辑服务器只提读写盘请求,而无须确认。写数据好说,读数据稍微难处理一点,我现在的方案是,数据服务器加载数据的部分只对位置服务器负责,把数据提交到位置服务器即可。位置服务器可以通过分析数据知道玩家的数据流应该流向哪台逻辑服务器。

以上逻辑是基于每个玩家有独立的数据的,而且一个玩家同时只存在于唯一一个场景。也就是说,当一组数据存活的时候,只唯一属于一台逻辑服务器。这样做的好处是,切换场景非常的简单,只是让玩家从一个场景退出,给数据服务器发出写盘指令,并发送所有数据。数据服务器写盘的同时也 cache 了这些数据,并向位置服务器提交新的位置,并把这些数据转发向位置服务器。位置服务器可以再转交给新的场景。

对于玩家登陆和登出的处理并没有特别之处,我们可以设立两个虚拟场景,一个负责登入,一个负责登出。每个新连接自动导入登入场景,这个场景负责发出加载指令(下面可以看到,甚至无须设置加载数据的协议)。然后再做一个自动的场景切换操作即可。而玩家登出,则是转入登出场景,这是一个黑洞,玩家的连接可以在这里安全的断掉。

当玩家不停的穿梭于各个场景之间时,为了避免频繁的数据转发,我们可以给玩家数据做一个赃标记。如果没有弄赃,实际的数据可以不被转发。这个标记的另一个意义在于,若数据没有弄赃,而数据服务器的 cache 中又没有数据时,就需要从外存加载了。其实这就是一个读请求。

月初我在公司内部讲解这个设计时,遭到了一些同事的疑问。最典型的一个是,帮派信息如何处理。的确,类似帮派的信息,不属于任何一个玩家。如果你单独设一个非玩家对象保存这些数据时,可能会分布到不同的逻辑服务器上。的确对数据服务器的设计是一个挑战。

今天仔细考虑过以后,我发现可以从设计上避免这个问题。方法如下:

October 18, 2006

多进程的游戏服务器设计

目前,我们的游戏服务器组是按多进程的方式设计的。强调多进程,是想提另外一点,我们每个进程上是单线程的。所以,我们在设计中,系统的复杂点在于进程间如何交换数据;而不需要考虑线程间的数据锁问题。

如果肆意的做进程间通讯,在进程数量不断增加后,会使系统混乱不可控。经过分析后,我决定做如下的限制:

  1. 如果一个进程需要和多个服务器做双向通讯,那么这个进程不能处理复杂的逻辑,而只是过滤和转发数据用。即,这样的一个进程 S ,只会把进程 A 发过来的数据转发到 B ;或把进程 B 发过来的数据转发到 A 。或者从一端发过来的数据,经过简单的协议分析后,可以分发到不同的地方。例如,把客户端发过来的数据包中的聊天信息分离处理,交到聊天进程处理。

  2. 有逻辑处理的进程上的数据流一定是单向的,它可以从多个数据源读取数据,但是处理后一定反馈到另外的地方,而不需要和数据源做逻辑上的交互。

  3. 每个进程尽可能的保持单个输入点,或是单个输出点。

  4. 所有费时的操作均发到独立的进程,以队列方式处理。

  5. 按功能和场景划分进程,单一服务和单一场景中不再分离出多个进程做负载均衡。

性能问题上,我是这样考虑的:

我们应该充分利用多核的优势,这会是日后的发展方向。让每个进程要么处理大流量小计算量的工作;要么处理小流量大计算量的工作。这样多个进程放在一台物理机器上可以更加充分的利用机器的资源。

单线程多进程的设计,个人认为更能发挥多核的优势。这是因为没有了锁,每个线程都可以以最大吞吐量工作。增加的负担只是进程间的数据复制,在网游这种复杂逻辑的系统中,一般不会比逻辑计算更早成为瓶颈。如果担心,单线程没有利用多核计算的优势,不妨考虑以下的例子:

计算 a/b+c/d+e/f ,如果我们在一个进程中开三条线程利用三个核同时计算 a/b c/d e/f 固然不错,但它增加了程序设计的复杂度。而换个思路,做成三个进程,第一个只算 a/b 把结果交给第二个进程去算 c/d 于之的和,再交个第三个进程算 e/f 。对于单次运算来算,虽然成本增加了。它需要做额外的进程间通讯复制中间结果。但,如果我们有大量连续的这样的计算要做,整体的吞吐量却增加了。因为在算某次的 a/b 的时候,前一次的 c/d 可能在另一个核中并行计算着。

具体的设计中,我们只需要把处理数据包的任务切细,适当增加处理流水线的长度,就可以提高整个系统的吞吐量了。由于逻辑操作是单线程的,所以另需要注意的一点是,所有费时的操作都应该转发到独立的进程中异步完成。比如下面会提到的数据存取服务。

对于具体的场景管理是这样做的:

October 02, 2006

服务器消息的广播

MMO 的 engine 中,需要解决的最重要的问题之一,就是如何把游戏世界中的状态改变消息正确的通知给需要知道这条信息的玩家。

通常,我们会设定每个玩家的 AOI ( Area Of Interest )。当一个对象发生改变时,它会把消息广播出去;那些 AOI 覆盖到它的玩家会收到这些广播消息。

但是,在我们现在的游戏系统中,简单的 AOI 系统是不够用的。比如,类似 wow 中的盗贼隐身,明明已经离你很近,但是你的 client 却看不见他。诚然,我们可以在 client 判断这个逻辑,对盗贼不于显示,而 engine 依然广播盗贼移动的消息。对于不作弊的 client ,这是个简单的解决方案。但在理论上却提供了看见隐身人的可能性,所以,我期望有更好的方法让 engine 可以只将消息广播到那些必须接收这些消息的 client 。但是,在实现这个同时,又不能让底层 engine 牵扯太多逻辑信息。

这里提出一个简单的方案:

September 16, 2006

心跳服务器

我们目前游戏服务器的初步架构是一个连接服务器处理来自多个客户端的连接数据,这个服务器将所有数据汇总后通过一个 socket 发送到后方的逻辑服务器。这个设计曾经写过一篇 blog 提到过

今天,我在两个服务器之间加入了一个控制心跳的服务器。其原始作用很简单,就是按心跳(目前的设定是 10Hz)从连接服务器上拿到数据,再转发给逻辑服务器。并把逻辑服务器发出的数据转出。

September 13, 2006

目前我们的游戏服务器逻辑层设计草案

我们一开始的游戏逻辑层是基于网络包驱动的,也就是将 client 消息定义好结构打包发送出去,然后再 server 解析这些数据包,做相应的处理。

写了一段时间后,觉得这种方案杂乱不利于复杂的项目。跟同事商量以后,改成了非阻塞的 RPC 模式。

首先由处理逻辑的 server 调用 client 的远程方法在 client 创建出只用于显示表现的影子对象;然后 server 对逻辑对象的需要client 做出相应表现的操作,变成调用 client 端影子对象的远程方法来实现。

这使得游戏逻辑编写变的清晰了很多,基本可以无视网络层的存在,和单机游戏的编写一样简单。

June 14, 2006

魔兽世界之过?

看到这样一篇文章,魔兽世界之过 。老实说,作为一个从美服 beta 开始接触 wow ,然后在美服付费玩了大半年,既而转战国服一年的老 wow 玩家来说。我在喜爱 wow 的同时, 也赞同文中的观点。

group > solo 就我自己来说,是反对的。不过之前一直没说出口。我自己打 wow ,一直到 60 级都是 solo 并且觉得非常有趣。而且每个任务都不放过,每个副本都要完成(当然不包括后期必须太多人的)。前期的 5 人副本,都是在不太高的级别,邀请几个非常熟悉的同事以 3~4 人完成的。那种克服困难后的喜悦无以言表。尤其是在 24 级的时候单身一人完成圣骑士的圣锤任务,跑断了腿不说,还在死生之间转了很多次。

整个游戏过程,没有接受任何经济上的资助,最多跟同级的玩家借点点钱。

现在我已经不玩 wow 了,但是我有同事还整天泡在里面。听取了他的观点后,更坚定了我的想法。raid 的确是 wow 的硬伤之一。当然,还有文中提到的荣誉系统等。

June 07, 2006

网络游戏中的货币系统

现在 MMO 中经济系统往往是设计的关键。可能是网络游戏发展的时间较短,并没有形成系统的理论的缘故,我所了解的网络游戏中都没有特别好的处理好经济系统中货币的控制和流通问题。

我们知道,货币只是一种经济活动中一种媒介,货币本身是没有价值的。但是,网络游戏中却很难体现这一点。系统对游戏直接的货币投放是不可控制的,虽然很多设计人员企图控制,但只是收效不同而已。大体上,大家都是用玩家的在线时间换取系统货币总量的增加,而用玩家自身的修炼回收掉货币。还有一小部分是用上税和赌博的形式回收掉。

May 21, 2006

脏矩形演示 demo

上个世纪,我在个人主页上发布了风魂。2000 年的时候,我给它加上了脏矩形的支持。之后,就再也没有发布新的版本。

实际上,我经历了好几个游戏项目后,那么久远的代码已经被抛弃不用,但是由于工作原因,新的代码没能公开。现在回首那些老代码,居然发现如此的丑陋不堪。

不过,一些思路还是可以保留下来使用的,其中之一就是脏矩形技术。

May 19, 2006

不太精准的时钟

目前,我们的游戏的同步是依赖机器时钟做预测的。这种做法的前提是,client 的机器和 server 的机器的时钟走速必须相同。这样,当我们把时刻校对了以后,可以不通过网络就可以知道时间信息。即,每个通过网络包传送过来的事件已经发生了多久。这只需要发送方为事件打上时间戳,然后接收方以自己的时候为准求出时间差即可。

April 26, 2006

广播和监督服务器

前几天写到我们把服务器组分成连接服务器和逻辑服务器的想法,这代码部分已经完成,并且比最初的构想增加了不少东西。比如允许逻辑服务器广播数据包,由连接服务器分发等。

随着工作的深入,这两天想了更多东西。

April 19, 2006

IOCP , kqueue , epoll ... 有多重要?

设计 mmo 服务器,我听过许多老生常谈,说起处理大量连接时, select 是多么低效。我们应该换用 iocp (windows), kqueue(freebsd), 或是 epoll(linux) 。的确,处理大量的连接的读写,select 是够低效的。因为 kernel 每次都要对 select 传入的一组 socket 号做轮询,那次在上海,以陈榕的说法讲,这叫鬼子进村策略。一遍遍的询问“鬼子进村了吗?”,“鬼子进村了吗?”... 大量的 cpu 时间都耗了进去。(更过分的是在 windows 上,还有个万恶的 64 限制。)

使用 kqueue 这些,变成了派一些个人去站岗,鬼子来了就可以拿到通知,效率自然高了许多。不过最近我在反思,真的需要以这些为基础搭建服务器吗?

April 15, 2006

网络游戏的对时以及同步问题

大多数实时网络游戏,将 server 的时间和 client 的时间校对一致是可以带来许多其他系统设计上的便利的。这里说的对时,并非去调整 client 的 os 中的时钟,而是把 game client 内部的逻辑时间调整跟 server 一致即可。

一个粗略的对时方案可以是这样的,client 发一个数据包给 server,里面记录下发送时刻。server 收到后,立刻给这个数据包添加一个server 当前时刻信息,并发还给 client 。因为大部分情况下,game server 不会立刻处理这个包,所以,可以在处理时再加一个时刻。两者相减,client 可以算得包在 server 内部耽搁时间。

client 收到 server 发还的对时包时,因为他可以取出当初发送时自己附加的时刻信息,并知道当前时刻,也就可以算出这个数据包来回的行程时间。这里,我们假定数据包来回时间相同,那么把 server 通知的时间,加上行程时间的一半,则可以将 client 时间和 server 时间校对一致。

April 05, 2006

贴图的合并

3d 游戏会用到大量的帖图,许多显卡要求贴图的尺寸必须是2的整数次方。这样,许多贴图的边角都会被浪费掉。尤其是大量无关的小贴图,我们通常想把他们合并在一张贴图上,尽量充满整个区域。

合并这些零碎贴图的算法,在学术上被称为排料问题。我尚未找到特别好的解决方法,所以这里就不展开写了。这里想讨论的是这些小贴图的管理。

March 08, 2006

基于垃圾回收的资源管理

游戏中有大量的资源数据,这部分数据通常数以百兆,甚至上G。如何在运行时管理好这些数据,也是游戏软件相对其他许多软件的一大难题。

管理这些资源分为加载和Cache两个方面。对于资源数量比较少的游戏,我们可以采用只加载不释放的方式,即使资源总量大于物理内存数,OS 在虚拟内存的管理方面也自然有它的优势。至于加载的时机,为了追求用户玩的时候的流畅体验,我们可以在一开始就进行 loading。当然也可以为了减少 loading 时间,去做动态加载。动态加载的问题比较复杂,可能涉及多线程,以及预读操作。这篇 blog 不展开谈。前段时间写过一篇动态加载资源 可以参考。

如果总的资源数很大的时候,我们就需要 cache 管理。因为在 32 位 OS 下,虚拟地址空间是有限的。这里主要谈 cache 的管理。

March 06, 2006

以人为本,美术资源的归档

游戏的 client 最文件数量最多,数据量最大的,往往是美术资源。(几乎所有的商业游戏都会在游戏发布时对资源文件打包,我们这里讨论的是开发期未打包的文件)当一个游戏的规模大到一定时,我们需要调动巨量的美术人力来制作这些资源。伴随着游戏规模和制作团队的扩大,设计资源文件的存放目录结构和文件命名规则往往成了项目一开始的头等大事。

2d 游戏这个问题稍微轻一点,3d 游戏更是有模型,贴图,骨骼等等的文件关联性在里面。这次我们的 3d 项目,让我又一次面对和思考这个问题。

January 05, 2006

动态加载资源

如今很多游戏engine宣称自己支持动态加载地图,也就是说可以作到跨地图时的零读取时间。听起来很高深的技术,实际不难实现,当然我们在大话西游和梦幻西游中早已经实现了。最近我正在考虑更加通用的解决方案。

先说说基本的思路。也就是我们需要把地图数据切割成小块,让每一块的数据读取解析量并不太大。然后,就可以根据玩家所在坐标读取最小数据量的数据。当玩家移动的时候,利用一些机器闲置时间去读周围的场景,或者进一步可以根据玩家移动方向把前方数据预读的优先级别调高。

December 30, 2005

当编辑器也成为游戏

最近我们立了个新项目,一个 2d游戏。和另一个主要的 3d 项目同时进行。这样比较方便更充分的利用资源。2d 游戏的技术已经很成熟了,所以我希望在两三个月内可以完工。

虽然以前的 engine 是现成的,但是最近脚本研究的比较多,所以我还是给出了个关于嵌入脚本的计划,把原来的 engine 重新包装一下,改成完全脚本驱动的。

October 11, 2005

游戏,一种奇怪的软件

现在做半职业游戏策划了,但是还是改不了程序员的老毛病。尤其是在写策划案的时候,方更加觉得程序的优美。程序是可以调试的,可以用工程的方法做质量控制,可以有测试的机制。大多数时候是可以被验证的。而且掌握了正确的方法,我们可以一步步的把程序搭出来,逐步看到成果。

但是游戏,作为一种软件则不然。