【3D技术宅公社】XR数字艺术论坛  XR技术讨论 XR互动电影 定格动画

 找回密码
 立即注册

QQ登录

只需一步,快速开始

调查问卷
论坛即将给大家带来全新的技术服务,面向三围图形学、游戏、动画的全新服务论坛升级为UTF8版本后,中文用户名和用户密码中有中文的都无法登陆,请发邮件到324007255(at)QQ.com联系手动修改密码

3D技术论坛将以计算机图形学为核心,面向教育 推出国内的三维教育引擎该项目在持续研发当中,感谢大家的关注。

查看: 3139|回复: 6

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

[复制链接]
发表于 2009-8-26 10:59:25 | 显示全部楼层 |阅读模式

一个大型的商业游戏包含很多资源,如图像、声音、文本、脚本和其他各种类型的数据,为游戏提供一个完整和高效的资源管理系统(包括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 (154302bytes)

 楼主| 发表于 2009-8-26 11:00:28 | 显示全部楼层
游戏中的资源打包技术  
  打包,很形象的,就是把零散的东西转换为单一的东西。常用的压缩软件就可以说是给文件打包。那么,在游戏中为什么要打包?有什么意义么?个人认为,有以下几个意义:
  1.安全性。
如果你的游戏重要数据以文本文件的形式保存在某些文件中,然而你又不希望玩家随意修改这些数据。(比如某些ini文件之类的)把他们和其他2进制文件全部打包在一起的话,这个问题就可以避免了。
 
2.节约磁盘空间。
文件太多的话,很容易产生“碎片”。比如一个1个字节的文件,占用空间就高达8Kb。(这个是由windows文件管理系统决定的),如果是很多这样的文件,就可能会发生这种情况:xxxxx个文件,实际大小1xxMb,占用空间3xxMB,(这里只是打一个比方,实际相差不会那么多)。这也许会让人感觉不舒服。
 
3.美观
简单的少量文件总比一大堆乱七八糟的东西更让人觉得舒服。
 
4.还没想到......
 
下面说说我的设计思路。
打包后的文件该是怎样一种结构呢?
我想到的有以下几种结构:
 
1.
{
    文件标示信息   //判断是否是正确的打包文件
    文件的个数,文件索引表大小
    各个文件的一个索引表.里面包含每个文件的偏移,大小.类似这种结构:文件名 偏移 大小.
    各个文件内容
}
 
2.
{
    文件标示信息
    第一个文件信息: 文件名长度,文件名,文件长度
    第二个文件信息: 文件名长度,文件名,文件长度
    ......
    第n个文件信息: 文件名长度,文件名,文件长度
    (文件计数)
}
 
3.
{
    打包成两个文件,一个负责方式1的索引表.另外一个只负责文件内容
}
这里第1种和第三种方式必须要得到索引表信息后才能填充文件,不如方式2直截了当.所以我在程序设计的时候采用的是方式2.当然方式1,3也有他们的好处,比如查找文件比2要方便一点.
 
需要压缩么?
  解压缩是要花费时间的.你可以从速度和容量方面做一个折中.我在设计的时候,没有考虑压缩.
 
怎样在游戏中从已经打包了的文件读取需要的文件?
  最简单的方法,得到需要的文件信息,从打包文件中读取出来,放到一个临时文件中.读取这个临时文件即可,
游戏结束之前,从程序中删除这个临时文件即可.这里就带来了一个问题:性能.每次都要进行I/O操作.如果每个文件都不是非常大的文件的话,这个办法还是可以的.或则你需要高性能的东西,那就只有一个办法:把你的程序中所有对文件操作都改到对内存进行操作.这样只要把需要的文件从打包文件中读取到内存中即可.或者还有另外的方法,直接在打包文件中读取(这个我还不知道怎么实现,盼望高手赐教之)
 
  在制作游戏过程中,当然不用打包,只是在正式版发布后,把所有已经做好了的资源(比如图片,一些数据文件,脚本文件等)打包再一起就可以了.类似如下结构
 
  //假设这个是一个打包类的一个成员函数,
    BOOL CPackFile::GetPackFileFromPacker(char*szFindFile,char*szTempFile)
    {
    #ifndef PACKER
        strcpy(y,x);
        return true;
    #else
        在打包文件中查找szFindFile,如果找到,创建文件名为szTempFile的文件,返回true
        否则,返回false
    #endif
    }
 
  程序中应该有如下片断
 
    char szFile[256];
    CPackFile packer;
    packer.OpenPackFile("somefile.pak");
    ................
    if(packer.GetPackFileFromPacker("resource.bmp",szFile))
       do something....
    
  下面看看我的具体程序吧!
  程序下载
  欢迎和我交流
  E-mail:game-diy@163.com
  OICQ:30784290(难得糊涂)
  http://GamePlusPlus.yeah.net
 楼主| 发表于 2009-8-26 11:03:37 | 显示全部楼层
游戏资源的打包【原创】 golden@2009-5-13 00:30   分类:程序 | if(GetCookie("password")){ document.write("编辑| ")} 评论:0 | 浏览:71 strBatchCount+="spn13=13,"          最近这几天一直在忙,忙什么?游戏资源的提取。
         其实商业化的游戏直接提取里面的资源用是不可取的,游戏开发厂商肯定会找麻烦,作为研究用倒是很不错,可以学习商业化的产品是如何建模和贴图的。
          首先说一下打包的格式,如果我要实现资源的打包,肯定会用到如下几种方式
          1 直接用现有压缩技术压缩,如zlib。很多游戏就是用这个打包然后换个扩展名。典型的就是crysis warhead。大家可以把game目录下的资源改名为zip用压缩工具就可以查看资源了。
          2 在文件头部建立一个索引,记录详细的文件信息,比如文件的名称,大小,起始位置,等等。这种打包方式类似于FAT16文件格式,这里可以统一称作类FAT16打包。
          3 在文件尾部建立索引,这种打包技术很不错,核心就是把索引表建立在文件的尾部,一般最后一些数据里面一定有一个计数值,此pak文件中加入的文件的个数。然后对于包内的每个文件都记录里面的位置。
          以上三种都是比较常用的手法,如果我是开发人员,因为思考的时间并不多,思考出的打包结构也就只会有上面几种,我会按照时间要求,从简单到复杂选择1到3的实现方式。
          作为独立开发人,有一个很大的优势就是时间。以上几种方式虽然都很不错,但除第一种外都不适合独立开发人,因为要编码,要调试,这就要花时间了。独立开发要充分利用时间。当然采用第一种比较简单,但是耗费解压要时间。如果是我,我会采用另外一种方式。
          客户端引入数据库,这个看起来很不可能的事情,实现数据库相当复杂,但是世界上总有那么一些伟大的人比如SQLite的作者就是,主动放弃版权将产品作为公共财产发布。正是由于SQLite我们可以采用如下方式完成。
          用SQLite建立索引表,将所有资源扩展名全部不指定或者指令为统一一个名称混淆一下那些想盗取资源的。通过索引表里面查找文件,这个方法看起来不错。 当然也有问题,如果有人想破解资源,只要找到了数据库文件就可以了那不是等于暴露资源,其实去掉扩展名就是为了混淆,面对一大堆资源我估计破解者也要花点 时间判断是哪一种类型。
         以上谈的都是一些梗概的内容,当然可以增加一些加扰的信息在以上的方法中,这里就不详细叙述了。
 看来很多朋友对这个题目比较感兴趣,这里把gameres上找到的一点资源的链接放上
游戏中的资源打包技术技术
http://dev.gameres.com/Program/Control/packer.htm
发表于 2015-1-31 13:51:31 | 显示全部楼层
谢谢分享,支持支持
发表于 2015-1-31 20:52:57 | 显示全部楼层
果然好高级!!!!!!!!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|3D数字艺术论坛 ( 沪ICP备14023054号 )

GMT+8, 2025-2-6 04:08

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

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