« March 2010 | Main | May 2010 »

April 12, 2010

实现一个简单的虚拟文件系统

Windows 游戏软件在发布时,通常会把所有数据文件打包。这通常出于两个目的:一是保护数据文件不被最终用户直接查看,二是 Windows 的文件系统一度相对低效。尤其是在处理非常多小文件的时候,无论是安装、分发还是运行时处理都有性能问题。

而游戏软件通常会有大量的资源文件,对数据文件打包的需求更为强烈。一般游戏引擎都会支持至少一种资源打包的形式。

打包数据文件的概念和实现,我最早是对 Allegro 的源码阅读学习来的,算起来也是十多年前的故事了。之后一段时间,我从 Doom/Quake 里又看到了类似的东西。再之后,见过了星际争霸对数据包的处理。大体上,大家都支持一种用法:即可以让数据包和本地文件系统中的数据文件共存。非打包的数据在开发期用起来非常方便,打包的数据用于发行。

一旦一种包管理的模块成型,在一个公司内部就很容易长期用下去。在上面做些修补,但基本会维持下去。我指的不仅仅是数据包的格式,也指相关的实现代码。

前者是因为对包管理的相关工具会在漫长的时间内不断的增加完善。包括了打包解包工具,Patch 的生成,发布工具。btw, 好的包格式会极大的提升 patch 的速度。比如网易的游戏系列,升级补丁的时候,网络下载时间占的整个升级时间的百分比最大(几乎超过 95% 的时间);而暴血的升级补丁时间中,网络下载时间则占用的百分比不大(甚至小于 50% ),大部分时间用户在等待 patching 而不是 downloading 。

后者缘于维护的程序员的更替,大部分人对稳定的代码不太愿意去重写。这个时候,合理的接口设计就突显出其重要性。往往设计接口就圈定了思维。实现倒成了很次要的东西。如果一开始设计有些许问题的话,很多年都不会有人去仔细考究。一开始的概念更重要。

2001 年的时候,我为大话西游设计了一个简陋的数据包格式以及实现了相关的引擎代码。这么多年用在网易游戏各个项目中,再无大的改变。

2006 年开始,为新的 3d engine 构思设计新的这套东西,我刻意避免了和以前相同的思路。希望从零来考虑这个问题。为了让系统支持数据的预读,我增加了表达文件依赖关系的信息在资源文件的结构中。并且把字符串文件名从资源系统中去掉。在数据包中,采用 id 而不是文件名来关联文件。

经过这几年的实际使用,我感觉到,保持一般的惯例更有利于开发。而看似不错的设计,由于人员沟通的成本,会极大的降低原本应该带来的好处。每次有新的程序员进来参与相关的开发,我都需要费很大的气力来解释整个资源系统的工作方式和原理。虽然,精心设计过的结构,会使得资源预读以及多线程的资源管理更流畅。资源数据的加载也会有更高的性能。但这都抵不回偶然的错误使用(来至于对系统理解的偏差)带来的负面效应。

除非你想一个人或两个人搞定所有的底层,提供一个 All in One 的解决方案。不然,还是保持惯例的好。不要自己创建新的概念让程序员学习。这也是我这些年越来越不喜欢 C++ 的原因之一。C++ 的哲学下设计出来的东西,都有 All in One 的倾向;而 C 的哲学设计的出来的东西往往可以更好的相互融洽的工作。如果想一个人做(设计)一整套系统,C++ 可能更方便;如果想创造积木的零件,那还是 C 来的实在。

扯远了。

其实今天想写的是,在上周经过几天的仔细思考,以及和同事的一两次讨论后。我想改写目前在开发的引擎中的数据文件管理的部分。选在这个时候来做,也是因为别的部分已经比较顺当,一起写程序的同事基本都能有条不紊的做不同的模块并协同起来工作。而修改这个底层子模块,几乎不会对别的东西有影响。而经过好几年的时间,我觉得我对需求已经有了清晰的理解,可以把这件事情做好了。

其实我想实现的是一个简易的虚拟文件系统。对外的接口在概念上和每个程序员都熟悉的文件控制操作相同。使用树状结构描述文件的集合,用字符串标识目录和文件。可以支持相对和绝对文件路径的检索,支持文件软连接。

大部分时候,我需要的只是对文件的读操作。这会极大的简化设计。

我需要从不同的介质中读取文件。从操作系统提供的文件系统中访问文件。也可以创建一个内存文件系统,把临时资源放在内存。也可以从数据包中检索文件。数据包可以是自定义格式,也可以是标准的 zip 格式(方便开发期使用)。如有可能,我希望支持嵌套的包结构,即可以将一组文件打包成一个 zip 文件,再将这个 zip 文件打包到另一个 zip 包中。程序可以用多层目录的形式直接访问到内层包内的数据。

按我以前的想法。我定义了一个文件加载器的接口。并实现了不同的加载器。从内存加载的、从文件系统加载的、从数据包加载的…… 然后将不同的加载器注册进系统,每次打开文件时,轮询已注册的加载器,分别尝试打开文件。

但这几天专门考虑过这个问题之后,我的想法有些变化。我发现,其实我需要的是一个和 Linux 的 VFS 几乎相同的东西。只是功能上有所削减而已。

文件树结构的管理应该是独立出来的。可以不依赖任何已经实现好的具体文件系统而管理虚拟文件树。这颗树上的节点对应着真实的文件,且这些文件并不需要统一在一种文件系统下(可从不同的途径操作)。再这个层次,模块管理的是文件名和目录项,以及 cache 。

每个独立的文件系统,可以通过 mount (挂接)操作把自己挂在 VFS 的一个挂接点上,取代其下的子树。只需要按需要展开一级目录项,把特定文件系统中的文件项生成在 VFS 的对应挂接点下。VFS 可以有 cache 机制加速对相同文件的第二次访问。

我觉得这个 VFS 的工作方式可以表现的和 linux VFS 的行为一致,每次挂接一个文件系统在一个挂接点上,就把原来这个位置的子树覆盖掉。在打开 /foo/bar/foobar.txt 这个文件时,如果存在 /foo.zip ,就会尝试在 foo.zip 里去查询文件。这会以自动把 /foo.zip 挂接在 /foo 这个挂接点上来实现。但挂接会使原来的 /foo 下所有文件不可见。

这个形式不满足我的需求:因为我希望在 foo.zip 里查找不到某个文件时,依然会在本地文件系统中尝试查询。

改进的方法是,写一个专门的文件系统,用来自动查找别的文件系统,并在自己的系统下做软连接指向成功打开的文件。

比如一开始在根文件系统下创建 /auto ,并将这个特殊的文件系统挂接上去。创建 /zip 用于包系统,/os 用于本地文件系统,/mem 用于内存文件系统。

当我们试着打开 /auto/foo/bar/foobar.txt 时,auto 文件系统尝试打开 /zip/foo/bar/foobar.txt ,如果成功,则创建一个连接,让 /auto/foo/bar/foobar.txt 指向 /zip/foo/bar/foobar.txt 。如果不成功,则继续尝试 /os/foo/bar.foobar.txt 。


大体上就是这样。等实现完了,再写篇 blog 列一下最终的数据结构定义和 api 定义。

April 07, 2010

筹码选配问题

大部分经济类的桌面游戏都需要 vp 筹码。每个游戏配的都不一样。有的为了降低成本,就是用薄薄一张纸,手感很不好。现代艺术的中文版用的纸板也远差于英文原版里的塑料片。

所以我想买 200 个黏土筹码,可以各个游戏通用,拽在手里沉甸甸的,玩起来特有感觉。

在网上搜了一下,看中一款。一共有 10 种面额。1,5,10,20,50,100,500,1000,5000,10000 。

我比较发愁每种应该配多少个,才能达到最大利用率。适应各个游戏。

没有一个游戏会使用全部跨度的筹码。但是,根据游戏本身的设定,有的游戏会用 1 做基本单位,而另一些会用 100 。

比如并购,最小单位就是 100 。我们也可以把 1 的当成 100 的用,曾经使用现代艺术的筹码来玩 DIY 的并购也不错。不过如果能按游戏设定的数值来玩游戏可能更自然一些。

那么在购买筹码的时候配比就应该按每个面额的使用频率来估算。

如果我们认为在游戏过程中,每个人手上的数值出现的概率相同,就可以算出每种面额的筹码的比例。同时应该让找兑次数尽量少,这样玩起来更方便。

另外考虑到一般游戏中大面额的筹码需求量会少于小面额的,再对以上分配比例做一个修正。

我们还需要进一步考虑最坏情况。比如如果存在一种 8 人游戏,最小面额是 1 。那么最坏可能,每个人手上都需要抓 4 个 1 。所以基础面额为 1 的游戏,至少就需要 32 个 1 的筹码。

我大概估算了一下,打算这样配 200 个筹码。

10000 的 10 个,5000 的 10 个,1000 的 30 个,500 的 10 个,100 的 40 个; 50 的 10 个,20 的 20 个,10 的 10 个, 5 的 20 个,1 的 40 个。

如果计划配 100 个筹码的话,就放弃面额超过 100 的筹码。

100 的 10 个,50 的 10 个,20 的 15 个,10 的 10 个,5 的 15 个,1 的 40 个。


随便估算的,不知道是否合理。

小店开张了

清明节期间忙坏了。

本来没打算开张的,只是在筹备,随便写了几篇 blog ,算是预热吧。结果星期天就有一群朋友找上门来。没仔细问,但应该不是我的 blog 的老读者。似乎是有人转贴了 blog 连接,然后直接打电话找了上来。

这是一个意外惊喜,我以为除了几个同事,不会有新朋友来的。结果一高兴,把全场的茶饮的单都签在自己名下了。第二天这帮朋友又跑过来,嚷嚷着要开星际争霸。并且强调说,不准我再请客了。:D

看起来这是群三国杀玩家。我其实并不反感这个游戏。话说,游卡的老板还是我朋友呢。我们一起开过银河竞逐。那次一起吃饭,我向他投诉过我买的三国杀质量不怎么样(早期版本)。

不过,我更珍惜向他们推荐更深度一点的策略游戏的机会。

连开了几个游戏:都是比较传统的。比如电网,并购等。可惜似乎兴趣不是非常大。至少不是所有人都玩得起兴。可能对比三国杀这种 bluffing 的游戏,经济类有太多零碎小部件拿在手上,且需要计算 vp 。是有一定门槛的吧。我想多找几个 bluffing 且不靠 vp 取胜的游戏会好一些。比如 Battlestar Galactica 和 Cosmic Encounter 可能更能接受。为了克服故事背景和文字恐惧。我得做点贴条了。

第二天开星际就好一些了。虽然也不是每个人都兴致盎然,但明显有人显出了兴趣。这是个很好的现象。我得好好研究一下各种玩家的偏好了。当然,更平坦的学习曲线也相当重要。提高教人玩游戏的技能也非常重要。

starcraft-20100405.jpg

我在 http://bg.codingnow.com 继续写各种游戏的介绍,过几天再整理一个已买游戏的清单出来,再给大家一个推荐列表。主要是按游戏机制,时长,和策略度分分类。

这次买了好多好多新游戏,我喜欢最终可以完成我的 bgg 排名前 50 的补完计划。把世界排名最前的游戏一一收藏。接下来的几个周末肯定要一个个啃规则书了。这样才能更流畅的向朋友推荐好游戏。

研究各式各样的桌面游戏,同时也给了我太多启发,为以后的电脑游戏设计积累了不少东西。有空应该好好总结一下。