游戏开发论坛

 找回密码
 立即注册
搜索
查看: 22252|回复: 18

高级游戏资源打包技术详解

[复制链接]

41

主题

148

帖子

184

积分

注册会员

Rank: 2

积分
184
QQ
发表于 2008-4-25 08:13:00 | 显示全部楼层 |阅读模式
    一个大型的商业游戏包含很多资源,如图像、声音、文本、脚本和其他各种类型的数据,为游戏提供一个完整和高效的资源管理系统(包括SDK与编辑器)是游戏引擎开发商必须完成的工作。我们在Numen Game Engine 2.0中实现了一个底层资源打包接口,在接口的基础上实现了纹理编辑器、模型编辑器、单位编辑器和建筑物编辑器等工具,整个系统采用32位的ID来标识资源,具有一级分类的能力,支持资源包的各种常用操作。
    但是在Numen Game Engine 3.0中,我们为了实现一个完美的资源打包系统,完全放弃了以前的所有接口,采用更多新的技术,最终构建了一个功能强大,性能高效的资源管理接口,这篇文章中,我们将从资源编辑器的特性简介、关键技术和资源编辑器功能三个方面,介绍构建一个商业级资源打包系统需要掌握的关键技术和实现细节。

一、特性简介
    Numen Pack Editor 2.0支持以下主要功能特性:
1、创建、打开和关闭资源包。
2、压缩资源包。消除数据添加、修改和删除带来的数据碎片,扩展索引表容量,重设资源包密钥。
3、合并资源包。将外部的资源包整合进当前打开的资源包。
4、释放资源文件。将资源包内的任意文件释放到磁盘。
5、创建文件夹。
6、(批量)添加文件。可以指定文件的独立密钥与压缩选项。
7、从文件夹添加数据(包括文件夹与文件)。可以指定文件的独立密钥与压缩选项。
8、(批量)更新文件。可以选择按列表顺序与按文件名两种方式进行数据更新。
9、(批量)复制、剪切、粘贴、删除和重命名单元。
10、查看和修改资源属性。
    资源编辑器的功能实现主要依托于底层SDK提供的接口,同时,细致周到的用户界面设计和大量方便的组合功能,是编辑器使用层次提高的关键。Numen Pack Editor 2.0在设计中,仅界面的设计就花费了大量时间,期间主要是对各种细节进行反复测试和修改。

二、关键技术
    总的来说,为了确保资源管理系统的高效,需要实现散列表、目录树、存储空间管理、加密/解密和数据压缩五个主要的接口,但是要实现完整的资源系统,还需要其他大量的基础函数,Numen Game Engine 2.0升级到3.0后,只需要实现前三项技术即可完成整个系统的构建。
    (一)散列表
    众所周知,散列表的出现就是为了解决字符串或非简单数据类型(如32位整数)的索引。一般来说,首先将需要索引的数据进行计算,产生一个32位的索引值,好的算法可以使这个索引值分布比较平均,减少产生冲突的可能性;插入操作时,用这个索引值与索引表大小进行模运算,获得它在索引表的位置,如果计算出的位置为空,则直接插入元素,否则将进行冲突处理;查询操作时,先在计算出的第一个索引表位置进行查找,发现元素则返回,否则继续进行查找;删除操作同查询操作,只是找到元素后会给元素打一个删除标记。
    我们有很多的散列算法可用,这里推荐暴雪在MPQ中开发的索引算法,其实该算法最大的好处在于不在散列表中存储要索引的数据,这样在比较操作时就不需要进行长数据的比较,也可以节约存储空间(后面会说到这个只对索引表有直接益处),举例来说:我们要插入一个字符串”e\world\texture\char\tank.png”(长度29字符),如果我们在索引表中存储完整的字符串,最理想的情况下我们也需要比较29个字符长度的数据,如果发生冲突,还将进行更多的比较。
    在MPQ中引入了两个额外的32位索引值来提供比较,这样在任何情况下都不会进行长数据的比较,如上面那个字符串,我们得到索引值dwHash,计算两个参考值dwCheck1和dwCheck2,这样每次元素比较最多只需进行3次32位整数值的数据比较。值得一提的是,这个算法也并不是万能的,在实际应用中,如果我们不需要大量长索引,那么普通算法的劣势将不明显,同时还将节约dwCheck1和dwCheck2的计算时间。
    另一个关键就是如何在索引表中放置冲突元素,一种方法是用链,另一种方法使用顺序,在MPQ中使用的是顺序存储,这样做的好处是实现简单,但是在最坏情况下,查找一个元素可能将遍历一次索引表,但是在正常游戏中,一般不会查询错误的单元,所以这样的情况很少出现。顺便提一下,在将冲突数据进行链的处理时,由于索引表需要存储在文件中,所以这里链需要改造成索引链(如下一个位置是15),而不是采用内存链表。
    (二)目录树
    在资源包中我们对每个单元的全路径进行索引,如e\texture\tank.png,那么在资源管理器中我们不可能通过遍历索引表来构建整个目录树(在1万个以上项目时速度将会明显变慢),而且我们的索引表也不存储任何名称信息。这就需要我们实现一个类似文件树的接口,它将保存所有的目录信息。
    在Numen Game Engine 3.0中,我们用链表实现了目录树,目录树的单元数据结构如下:
struct FILETREEITEM
{
        FILETREEITEM*        pPrevItem;                // 前一个单元
        FILETREEITEM*        pNextItem;                // 后一个单元
        FILETREEITEM*        pChildItem;        // 子单元
        FILETREEITEM*        pParentItem;        // 父单元
        DWORD                dwHashIndex;        // 索引序号
        int                nNameLength;        // 名称长度
        WCHAR                strItemName[1];        // 单元名称,不包含路径.(可变长度)
};
    采用“上下左右”四个单元指针的目的是为了提高插入的效率和使单元访问更容易,Numen引擎中用了一个小技巧,就是每个单元的第一个子单元(包括根单元),它的pPrevItem指向的是本级最后一个单元,这样做是为了使插入操作能进行后序插入,普通情况下,要实现这个目的,需要提供一个额外的pLastItem指针。
    设计目录树时的难点在于指针的正确处理,如果说单向链表很简单,双向链表并不难,那第一次设计这种属性链表时一定不会一帆风顺的。另外还需要注意一些操作影响的不仅仅是一个单元,还将影响所有的子单元。
    使用可变长度的单元名称将节约大量内存空间。
    为了使各种操作有更多的可定制性,我们还需要在对目录树的各种操作中加入回调函数,如下所示:
// 文件操作参数
#define FILETREEOP_RENAMEITEM                0x00000001        // 重命名单元
#define FILETREEOP_COPYITEM                0x00000002        // 复制单元
#define FILETREEOP_MOVEITEM                0x00000004        // 移动单元
#define FILETREEOP_REMOVEITEM                0x00000008        // 删除单元
#define FILETREEOP_OVERWRITE                0x00000010        // 覆盖操作
#define FILETREEOP_INTERNALCREATE        0x00000020        // 内部创建

// 文件树操作回调函数
// 参数: -strSrcName 源单元名称
//       -strDstName 目标单元名称
//       -dwFlags 文件操作参数,参考FILETREEOP宏定义.
//       -lpUserData 用户自定义数据
// 返回: 0: 执行单元操作
//    其他: 放弃单元操作
typedef LRESULT (CALLBACK *LPCBFILETREEOP)( LPCWSTR strSrcName, LPCWSTR strDstName, DWORD dwFlags, LPVOID lpUserData );
    有了这些回调函数,这样我们就可以非常灵活的实现我们的资源管理器了。
    最后值得一提的是,目录树也是实现采用检验值方式的索引表进行重构的基础,因为没有存储索引信息的索引表,是不可能改变自身大小的,必须通过外部记录的索引信息,将索引表扩容后,进行索引重建。
    (三)存储空间管理
    对操作系统比较熟悉的人都知道,文件系统在长时间使用后会产生大量数据碎片,资源管理器也将面临这个问题。在Numen Game Engine 2.0中,我们只是简单的把任何新增的数据加入到资源包的结尾处,可以定期通过压缩资源包来消除空间浪费。
    在Numen Game Engine 3.0中,我们实现了一个空间管理类,这样任何空间的申请和释放都通过这个类来管理,这个类建立了32个单向链表,每个链表记录一定容量的空闲空间信息,如我们先后释放了4096,89,12589大小的存储空间,然后我们添加大小为70,4000,10000大小的数据,我们将可以申请到重复利用的空间,而不需要增加资源包的大小,同时未用完的空间将被加入到链表中等待分配。
    正是由于存储空间管理器的引入,大大降低了数据的删除和修改操作对资源包大小产生的影响,在配合资源压缩功能,完全不必担心数据碎片带来的空间浪费。
    (四)数据加密/解密
    这个方面的内容可参考的教程和代码较多,算法的强度和效率也各有不同,所以开发商可以根据引擎定位和项目需求进行具体定制。
    资源包中的加密分为文件头加密、索引表加密、目录树加密、数据块信息加密和数据加密,这些加密方式为资源包提供了完整和灵活的保护方案,用户可以根据数据的重要程度和读取效率需求自由定制加密方案。
    (五)数据压缩
    通过提供各种压缩算法,如Huffman,RLE,ZIP,LZW等,为引擎数据提供压缩服务,目前Numen Game Engine 3.0的资源包支持Huffman,RLE和LZW三种压缩算法选择,用户可以自己选择使用他们中的一种或多种对数据进行压缩。

三、资源编辑器功能
    这里将对Numen Pack Editor 2.0中的部分功能进行图文说明:
    (一)压缩数据包
    这里为用户提供了改变索引表大小和资源包加密的机会,资源包索引将会对这个资源包进行重构,花费的时间根据资源包的数据大小,加密强度和压缩等级而定。
    (二)合并资源包
    合并资源包是为了团队配合制作资源而产生,比如美工进行分工后,可以自行制作自己的图形资源,并保存在自己的文件夹中,当资源需要进行整合时,可以统一汇总,而且每个资源包可以保持自己独立的密钥,使团队合作的层次得以体现。
    (三)添加文件
    如果不能批量添加文件到指定文件夹,那添加文件将会变成一件浪费人力的工作,同样我们也需要为一批添加的文件同时指定加密和压缩方案。
    (四)添加文件夹的文件
    这是一个十分重要的功能,当用户有一大批已经制作好的资源需要进行添加时,我们不需要手工建立所有的文件夹,只需要点击几次鼠标,便可将所有资源一次性加入到资源包中。
    (五)更新文件
    如果一个资源编辑器不具备强大和灵活的更新能力,那就不叫资源编辑器了。用户首先选择需要更新的文件,然后选择按序号或者命名更新,就可以一次性完成一批数据的更新操作,而且编辑器提供了更新确认对话框,所以用户不必担心误操作覆盖掉重要的数据。
    (六)文件属性
    如果文件加入资源包后,不能随时查看和编辑它的属性,这将是不可想象的灾难,因为我们根本无法预知未来的需求变化,比如添加时我们希望数据能快速的读取,所以没有给它加入加密和压缩,但是游戏发布后,我们希望保护我们的资源,这是利用文件属性对话框就可以轻易的实现这个目的。
    四、结束语
    Numen Pack Editor 2.0已经比较完善的支持了资源包管理系统应具备的各种功能,而且在用户接口设计上非常注重细节,为用户进行资源处理提供了高效的手段。但是一切技术都是不断发展和完善的,现在的编辑器也需要在更多的项目实践中不断完善和发展,关于Numen Game Engine和Numen Software的更多信息请随时访问www.numenstudio.com进行查询。

由于GameRes发带图片的帖子不方便,要下载程序和图文教程的请访问www.numenstudio.com。
下载地址:http://www.numenstudio.com/media/PackEditor2.0.rar
sf_200842581312.jpg

36

主题

1047

帖子

1147

积分

金牌会员

Rank: 6Rank: 6

积分
1147
发表于 2008-4-26 00:42:00 | 显示全部楼层

Re:高级游戏资源打包技术详解

和我们的打包文件系统的实现方式很像.问两个问题:
1.一般游戏读取资源文件是带有文件路径信息的,按照上面所说的的目录存储结构,是不是在读取文件时要分割路径并进行 hash 查询?
2.存储空间管理中的算法还会存在空间浪费的问题。我们实现是只有一个 free list,存储时查找这个 free list 中的最合适的空间来存储数据,但是也不能彻底解决空间浪费。

41

主题

148

帖子

184

积分

注册会员

Rank: 2

积分
184
QQ
 楼主| 发表于 2008-4-26 11:37:00 | 显示全部楼层

Re:高级游戏资源打包技术详解

congy,我来回答你的两个问题:
1、读取文件时不需要分割路径,直接以全路径进行索引,如World/texture/water.png。顺便提一下,上个版本中我们用的是32位ID索引方式,比这个要方便要快,但是有个缺陷就是需要维护ID的变化(不是经常,但有这种情况),所以每种方式都有它的优缺点。
2、存储空间的浪费是不可能完全解决的,这个不难理解,不然操作系统的文件就永远不需要那些碎片整理程序了。但是好的算法可以最大限度的降低这个浪费,这个版本中我们还没有实现空间合并的功能,就是把一些邻接的空间进行合并,正式版本中会加入这个特性。各种算法的本质都是FREE LIST,只是效率不同而已。

36

主题

1047

帖子

1147

积分

金牌会员

Rank: 6Rank: 6

积分
1147
发表于 2008-4-28 23:57:00 | 显示全部楼层

Re: Re:高级游戏资源打包技术详解

快乐魔导师: Re:高级游戏资源打包技术详解

congy,我来回答你的两个问题:
1、读取文件时不需要分割路径,直接以全路径进行索引,如World/texture/wa...

基本上和我们的实现差不多.大同小异.

36

主题

1047

帖子

1147

积分

金牌会员

Rank: 6Rank: 6

积分
1147
发表于 2008-4-29 00:00:00 | 显示全部楼层

Re:高级游戏资源打包技术详解

再问个问题,你们如何处理资源的引用,是引用资源路径,还是资源 id?

89

主题

4036

帖子

4132

积分

论坛元老

Rank: 8Rank: 8

积分
4132
发表于 2008-4-29 10:22:00 | 显示全部楼层

Re:高级游戏资源打包技术详解

名字输入。
转成hash id查找

41

主题

148

帖子

184

积分

注册会员

Rank: 2

积分
184
QQ
 楼主| 发表于 2008-4-29 12:06:00 | 显示全部楼层

Re:高级游戏资源打包技术详解

技术的实现都依靠差不多的原理,比如3D地形技术,数来数去也就是那几种,游戏看的是可玩性,技术只是很小的一方面.

1

主题

32

帖子

40

积分

注册会员

Rank: 2

积分
40
发表于 2008-4-29 22:03:00 | 显示全部楼层

Re:高级游戏资源打包技术详解

P

0

主题

172

帖子

176

积分

注册会员

Rank: 2

积分
176
发表于 2008-4-29 23:52:00 | 显示全部楼层

Re:高级游戏资源打包技术详解

仔细看了下,基本是很通行的做法。

我最近恰好也是实现了一个类似东西,没有大规模实测(也许写的很烂),就大致说说思路。

采取UNIX式文件系统方式构建,头32Byte为一个超级块,包含包文件的一些基础信息,其后的空间以虚拟磁盘块划分(例如4K),空间管理系统以虚拟磁盘块为单位分配、回收(空闲磁盘块记录在从0#开始的一个磁盘块索引链上,每个节点可记录1023块空闲块,如有下一节点则第1024块为指向下一节点索引,索引链增长即可包空间扩容)

文件系统为索引式,文件(夹)为一个INode,其中对于数据索引
unsigned __int32        m_Idx[16]; //0级索引,0 - 64K
unsigned __int32        m_Idx1;   //1级索引,1024*4K,(目标块可保存1024个块号)
unsigned __int32        m_Idx2;   //2级索引,1024*1024*4K,(目标快可保存1024个保存1024个块号的间接索引块)

文件夹则数据区为子文件INode的索引值(int32,高24位为Page索引,低8位页内Offset)

优势:
文件夹添加、移动可采用更新原父节点、新父节点的数据区所保存的子INode索引信息,不用移动文件数据
一般尺寸文件下,空间的浪费降低至最低水平
文件数据物理上及可能乱序,天生具一定加密效果

缺点:
大量小于4K的文件存在时,反而大大浪费空间
由于盘块式的管理,数据变为物理上不连续,读写时系统内需要额外计算物理地址,并频繁移动OS文件读写指针

0

主题

172

帖子

176

积分

注册会员

Rank: 2

积分
176
发表于 2008-4-30 00:03:00 | 显示全部楼层

Re:高级游戏资源打包技术详解

顺带说一下,实际调试时发现在包文件系统中

加密严重干扰压缩,一旦加密,压缩率大幅下降,还是应该先压缩再加密?
压缩后,流式读写基本成为泡影
开启加密、压缩对效率影响非常大,读写速度可能相差几个数量级

不知道各位兄弟如何解决的?
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2026-5-17 22:08

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表