Spiga

从头开始思考游戏的数据管理系统(一)

2010-04-12 22:53 by Milo Yip, 5391 visits, 收藏, 编辑

M.C.Escher 1956年作品 "smaller and smaller"

 

和一般应用软件有点不同,大部份游戏软件都需要使用大量的游戏数据(game data)──或称为资源(resource)、资产(game asset, 但通常asset包括数据的原始格式, 不是最终运行游戏所需的数据)。在制作游戏时,如何管理这些数据是一个非常重要的问题。曾经看过和使用过不同的方案,现在归回原点,分析基本的需求,随笔记录笔者所选的方案思路。

游戏数据管理的重要性

如果从游戏软件的产出(deliverables) 来分析,一个游戏软件可以分为三部份: 

  1. 游戏引擎: 比较固定的、和游戏性无直接关系的程式
  2. 游戏唯读数据: 脚本、图像、音效、关卡、影像
  3. 游戏读写数据: 游戏存档、游戏设定、玩家自制内容

从现今的游戏实际容量来看,游戏引擎可能占1~10MiB,游戏读写数据10KiB~10MiB,其余以GiB为单位的全是唯读数据。以开发人员来计算,开发引擎可能是数人,但制作那些唯读数据的队伍是以百计的,主要是游戏性程式设计师、人物美工、场境美工、关卡设计师等等。 本文主要谈游戏唯读数据,以下「数据」或「游戏数据」皆指「游戏唯读数据」。

游戏数据可以有几多笔?

接着笔者随便估计一个"小"游戏需要的数据笔数(笔数在下节定义) 

  • 20 关卡 × (200关卡贴图 + 100关卡模型 + 200游戏物件) = 10000
  • 40 人物 × (3人物贴图+ 2人物模型 + 20人物动画) = 1000
  • 200视觉效果 × (5效果贴图 + 5动画) = 1000
  • 20使用者接口 × 25贴图 = 500
  • (100物件脚本 + 80人物脚本 +  20使用者介面脚本) × 5原始代码文件 = 1000
  • 180音效 + 20音乐 = 200
  • 总和 = 13,700

以这个推算游戏的数据笔数,其数量级一般在104 至106。笔者觉得极限只是会到107,因为大量数据是模型、贴图、动画等,平均值不会少于10KiB,而107 × 10KiB 已经是10GiB。

游戏数据的概念分析

笔者心目中的游戏数据是有以下特点

  1. 唯一性(uniqueness): 每笔数据有它唯一的标识符(identifier),透过一个标识符可以读取一笔数据。
  2. 不可分割(atomicity): 当把一些数据分割成多笔数据是没意义的,就算是一笔数据。例如假设一张贴图不会读取、使用其中一部份,则视为一笔数据。
  3. 依存关系(dependency): 一笔数据可能会靠identifiers引用其他数据。这和上不可分割属性也有关系,因为是用identifier依存某一个数据,而不是该数据内的一部份。笔者未想到会有循环的依存关系,暂时把依存关系当作一个Directed Acyclic Graph (DAG)。

一般文件系统的文件,和这里的数据有点不同。文件的路径可以视为Identifier,但很多文件的内容是可以再分割的,而且没有显性(explicit)的依存关系。

选择标识符

笔者看过和想到的数据标识符(identifier)有:

  1. 路径 (path): 例如 Texture/wall.jpg
  2. 统一资源定位符(URL): 例如Texture/wall.jpg、http://www.mysite.com/news.jpg
  3. 整数: 例如 0x54AF4C58
  4. 全局唯一标识符(GUID): 例如{3F2504E0-4F89-11D3-9A0C-0305E82C3301}

路径

档案系统的路径可能是最直觉的数据标识符。我以前做过的引擎,和很多商用引擎也会使用文件和路径这个表达方式。路径通常是相对于某一个目录、或一个压缩文件(例如id公司的引擎会把档案压缩在一个zip文件里)。

路径的好处是和我们日常用的操作系统管理档案的方式一样。层阶式(hierarchical)的目录结构让使用者可以自行分类管理,你亦可以历遍(traverse)目录结构,例如读取某一目录的*.lua。它实作简单,因为操作系统已提供所需功能。也可以使用一些现存的版本管理工具去直接管理这些数据,如SVN、Perforce等。

路径的坏处包括: 大小写问题、多国文字问题、储存效率差、运行效率差。前二者太概都容易明白,不再详述。储存效率差是指用字串去记录每个标识符,包括引用时的标识符都会花费不少空间,而且路径(不单是档案名)的长度可以很长(例如可能要256 位元组)。而运行效率差是因为比较两个路径慢,计算一个路径的hash code (例如用路径identifier做hash_map或hash_set 的key)也很慢,並且不是常数速度。在游戏运行期,这些路径文字资讯是冗余的,因为玩家不会看到。

统一资源定位符

统一资源定位符(Uniform Resource Locator, URL)算是路径的延伸。这延伸的好处是它是一个标准(不会有平台相关、大小写、多国语言等问题),而且有些XML里也会用URL作为引用的标识符。另外,可以选择性支持不同的协定(protocol),例如透过http存取互联网上的资源。其他坏处和路径差不多。

整数

整数是最简单的标识符。通常关联式数据库(relational database)的表(table),都以一个整数栏作为列的主键(primary key)。相对前两种标识符,整数的好处是储存量小而速度快。整数标识符的缺点是,整数对使用者而言没有任何意义,也没有层阶式的管理系统。

另外,多少位元才足够呢? 从之前的估计,107笔数据用32-bit就足够了。用另一个角度想,如果一个5年的项目有100个成员会添加数据,一周工作7天,平均每人每日可增加23534笔数据。这应该足够吧,除非数据经常大量删去又创建数据(每次都产生新的标识符)。

要澈底杜绝标识符不够用,可以重用旧的删去了的标识符,或是把现有的所有标识符重新编号。 整数标识符在多人同时建立数据的时候,还要考虑如何令标识符不重复。解决办法之一,是连线到一个有标识符产生器服务的伺服端程序。

全局唯一标识符

最后,全局唯一标识符(GUID)的好处是确保在不连线情況下,每人也可以得到不会重复的标识符。缺点是GUID的大小一般为128-bit,是32-bit的4倍。

之后

笔者将继续写有关这些游戏数据的使用流程,并最后阐述现在做的引擎在这方面的设计。


本文原来是繁体中文,在2009-03-13发表于http://miloyip.seezone.net/?p=104,本文經過修正。

Add your comment

10 条回复

  1. #1楼 麒麟      2010-04-12 22:54
    sf
     回复 引用 查看   
  2. #2楼 zzfff      2010-04-12 23:34
    对游戏开发完全不懂,但“数据管理系统”这个标题很有趣,乱侃一下。
    我觉得,这和WINWORD.EXE操作一个20G的word文档的思路类似(不知道word文档的实际限制是多少,这不妨碍纸上谈兵)。.doc文档使用COM Structured Storage(http://en.wikipedia.org/wiki/COM_Structured_Storage),.docx文档使用Open Packaging Conventions(http://en.wikipedia.org/wiki/Open_Packaging_Conventions)。
    我更倾向于使用标准,OPC看上去挺美,并且System.IO.Packaging(在WinBase.dll程序集中)提供了其实现。很可能因为效率等原因需要用原生代码重新实现,但至少不需要费脑筋去设计个山寨OPC标准:)
     回复 引用 查看   
  3. #3楼 Justin      2010-04-12 23:43
    先挂名,这个得慢慢拜读
     回复 引用 查看   
  4. #4楼 zzfff      2010-04-13 00:28
    逻辑结构 vs. 物理结构
    对于“数据管理系统”的使用者来说,只关心逻辑结构,通常以API形式展现出来(如System.IO.Packaging,或更高层的封装),至于这20G的xx,xxx个文件到底怎么存放(物理结构),是一个巨型zip文件,还是文件夹,或一半在本地一半在服务器上,那是API实现者的事。
     回复 引用 查看   
  5. #5楼 Tmac_      2010-04-13 09:50
    恩,大致了解了游戏数据的管理~
     回复 引用 查看   
  6. #6楼[楼主] Milo Yip      2010-04-13 10:19
    @zzfff
    謝謝你對於OCP等的解釋,筆者對OCP等技術並不認識。

    不過,id software公司的遊戲如《Quake》、《Doom》系列等,採用的是一個zip文件放置所有唯讀數據。有些人稱之為virtual file system(VFS)。昨天看見雲風的博文討論這個,就想起以前寫過相關的博文和大家分享討論。

    本文並沒有詳細描述一些遊戲隊伍常見的需求(例如跨平台、協作編輯、版本控制、編譯數據等等),所以可能會引起一些誤會。

    另外,的確,使用者關心的是API抽象後的層次。但比如本文比較多篇幅是關於identifier的選擇,這方面使用者必然是要知道的。對於數據間如何參考,也需要使用者參與。

    引用但至少不需要费脑筋去设计个山寨OPC标准


    基於很多原因,商用的遊戲引擎經常要按特別需求重新發明輪子,STL、C/C++ IO、預設內存管理等等都不能使用。對於遊戲數據管理方面,很可惜,現在沒有甚麼標準。
     回复 引用 查看   
  7. #7楼 zzfff      2010-04-13 11:55
    @Milo Yip
    我知道并尊重现实,如前所述,纸上谈兵
    Windows 7提供了对OPC的原生API支持:http://blogs.msdn.com/opc/archive/2009/05/18/adventures-in-packaging-episode-1.aspx
    不知性能如何

    我有点郁闷的是奥卡姆剃刀为什么不适合IT:若无必要,勿造轮子:)
     回复 引用 查看   
  8. #8楼 韦恩卑鄙 v-zhewg @waynebaby      2010-04-16 13:20
    楼上 xbox倒是可以用 ps3怎么办
     回复 引用 查看   
  9. #9楼 msnweb      2010-05-28 15:47
    我们原来的产品也有这个些问题。 考虑到资源的安全。(大家都不希望自己辛辛苦苦的画的资料,音乐很容易的被人从游戏里面crack 出来),以及文件碎片多非常影响安装和更新的速度。所以还有加密和打包的因素在里面。 这点暴雪做的很好啊, 就是几个大文件。

    产品发布的前后 还有一个将资料整体打包加密 的过程,用唯一的d号方式比较简单。 开发的时候用一个类型的系统。 加密后变成靠id 这样的方式。实现只是在读取的api上面。 加密也在这些api上面。
     回复 引用 查看   
  10. #10楼 huhuc      2010-05-28 17:30
    楼主想说其实是结构化存储.
    可从接口,数据管理,介质管理,容错,并发,约束等等方面来考虑.
     回复 引用 查看