关键字 游戏打包 文件系统 作者:董含君
转载请注明来自 http://blog.csdn.net/a11s
游戏文件系统的分析与建立
I 目标:
实现一个 高度集成的,操作方便的文件管理系统,可以紧密的与其他引擎相互协作.
II 问题的出现:
建立工程的时候往往会用到大量的 wav bmp jpg mp3 x 等资源文件,以及script脚本文件,配置文件等等.我们面对如此多的文件已经不在乎工程的文件管理的复杂程度(或者说已经习惯了).但是考虑到用户以及其中的安全机制,不希望其他人轻易的进行更改获取其中的某些资源.(比如场景配置,任务脚本).或者说大量的资源摆在硬盘里面会耗费很多空间(小的,零散的).以及其他问题(版权等).所以迫切需要一种打包的工具对资源进行有效的管理,而且尽量地降低使用时的复杂程度.绝大多数的游戏都是这么做的.
III 解决办法:
使用文件打包程序,需要的时候,判断所需文件在资源文件包(以后假设为pak文件),然后转换成MemoryStream提供给DirectX或者其他引擎使用.
IV 具体实现: 上面说得似乎很简单,但是要达到预期的目标,真的很困难.如果适应Game的要求至少要满足下列条件.
1 基本的打包,提取功能
2 结构目录的管理,要明确了解那个文件在那个包里面
3 使用难度不能太复杂,接近直接读取的复杂程度
4 考虑到网络Game的更新问题
5 由于是自己创建的文件系统,还要自己做管理工具
6 既然有了更新,那么版本控制也要考虑
一个比较大的工程….
V 目前有两种解决方案.
1 简单的打包封装文件的方式(以下简称A-Type)
优点:简单,代码量小.效率高,架构简单,扩充性很强,地球人都这么办.
2 用小型数据库实现(以下简称B-Type)
优点:更多丰富的控制,以及比较健全的安全机制,兼容性,安全性都很强.
以上两种机制都是为了模拟磁盘的物理操作,避免以后直接操作文件本身.仅仅是最终实现的时候读写的媒体是文件跟数据库表的操作不同而已.如果有兴趣的话可以做抽象类然后实现这两种机制.(但是我没有兴趣)
VI 先说一下操作步骤
读操作:
1 创建ClassDataCenter
2 将所需要的包loadpak进DataCenter
{类的内部实现: 每添加一个pak都将他的文件列表加载到内存
将每个文件列表拼凑成一个完整的文件列表,此时由版本控制机制筛选真正需要的文件
根据完整列表,实现目录树的生成.}
3 用ClassDirectory.getFile(namespace) 的方法返回数据流
{内部实现机制:
如果namespace没有路径,而且找到了文件则直接返回
如果namespace带有路径,则去掉当前目录,递归实现getFile(namespace)知道找到返回
找到文件之后返回的是ClassFile对象,利用该对象的ClassFile.getStream得到数据流
}
由此可见,除了一开始的初始化之外,使用起来跟一般的文件操作复杂程度相当.
写操作:
1 创建Pak对象(ClassLibrary)
2 创建一个ClassFile对象,设置对象属性
3 ClassLibrary.CreateFile(ClassFile)
最后dispose
除了一开始的初始化,写操作与直接写文件的复杂程度相当.
VII 使用数据库操作
1使用数据库可以满足目标提出的大部分条件,经过实际测试,插入大文件的二进制数据时,会给系统造成相当大的负担,但是处理大批量小规模数据表现出色.可以简单完成修改以及删除工作(相对于Atype而言,插入跟删除的确简单)就是写操作效率不如Atype高
2 决定使用数据库进行操作依然由很多问题需要解决.比如
a用数据库存取全部配置信息,所以必须自己制作响应的工具来读写配置,文本文件只要用notepad就可以了.
B 升级控制不方便,如果全部插入到主库,这个库会变得非常庞大,或者一张CD装不下,而且不能降级(需要专门的降级工具)扩充性很差,变得难以维护,如果有玩家自定义任务,用这种频繁改写数据库的方法不现实.
解决办法就是每个包一个库,类似模拟器的ROMS机制,除了主要库,以及公共库之外,其他的都放在Roms目录下面,需要的时候再读取
C 这样做也有缺点,每当游戏开始之前要先扫描全部注册的库,这意味着会有很多的连接以及适配器,并且随着库的个数的增加,完全随机存取消耗的代价更是不可估量.同时可以发现,除了第一次为了保证完整性的扫描之外,游戏进行之后的文件读取一般仅仅需要两三个库的支持就足够了(否则说明你的资源组织结构不合理).而且某些库(比如主库),读取完成之后完全可以立刻释放掉,节约资源,将配置信息全部加载到内存.
D 总之,不要试图加载全部的库,无法想象会使对方系统慢到什么程度.
3 如果开销这么大,有必要建立适配器缓冲池(也就是连接缓冲池)
VIII文件系统需要完成的任务
1 文件列表汇总.
使用文件系统的时候要像用windows文件一样简单,而且必须包含版本控制,最终需要转换成Stream与Windows文件系统交互
2 多库的”类分布式”系统
也许这个帽子有点大,但是这的确开始靠近数据仓库了.同时管理多个库以及他们的交互,需要了解每一个库的内容(否则无法实现像存取本地文件那样的效果)
在主库或者内存内置一张表用于描述所有库文件的索引(也许就是一个集合或者是Dataset)然后根据索引,去响应的库进行查找二进制文件流.
这样物理层参盘文件结构与逻辑层目录结构就完全分开了(天哪!!我们做了一套驱动)
3 视图
用户读取场景的时候可以使用视图来得到数据,由于数据库的特性,可以比较容易的实现某些安全机制,比如视图的访问权限等等.这里使用视图的目的是为了减少数据的查找工作,安全其次,毕竟这方面不是很重要.
IX 结构关系图
![](/images/load.gif)
X 对象说明
类 XDataCenter
继承hashtable
用于管理库文件以及完成原始文件列表的汇聚
.addLibrary(key as object, FileName as string)
.addLibrary(key as object, targetLibrary as XLibrary)
添加一个库,同时汇聚他的所有文件列表
.removeLibrary(key)
移除一个库,同时移除他所包含的文件列表
.items 库列表.
FileList as XFileList 当前所有库的文件列表.
CreateDirectory() as XDirectory
类 XFileList
文件列表,每个成员都是XFile 对象
继承HashTable
addFile(key,TargetFile as XFile) 允许动态添加XFile对象
removeFile(Key) 允许动态删除XFile对象
item XFile集合
getFile(namespace as string) as XFile
根据namespace返回XFile
getStream(namespace as string) as MemoryStream
根据namespace 返回流
类 XLibrary
用于表示库文件,可以是pak文件,也可以用来表示数据库.可以用来对文件进行物理操作.其他对象的文件操作实际上都是由它来最终执行.
继承Object
CreateFile (tarfilestream as FileStream, tarFile as XFile)
在该库写入一个磁盘上存在的文件,文件扩展信息来自tarFile
DeleteFile (namespace) //数据库支持,文件操作比较复杂
删除该库namespace位置的文件
getFile(namespace as string) as XFile
根据namespace返回XFile
getStream(namespace as string) as MemoryStream
根据namespace 返回流
getList() as XFileList
返回当前库的所有文件列表
类 XDirectory
用于表示目录结构,方便枚举使用,当然,这个类不是必要的,使用它可以方便的用来枚举
继承Object
new(XDateCenter)
new(XFileList)
new(XLibrary)
将列表转化为树
parent 父目录
name 自己的名字
items(namespace as string ) 返回XFile
dirs(namespace as string) 返回XDirectory
path 当前的完整路径
dispose 析构,释放旗下的所有directory以及file
类 XFile
用于表示文件
继承Object
方法
getStream 得到文件的流
update 更新记录 //如果是直接文件存取,那么这个还是算了
delete 删除记录 //如果是文件存取,这个就不必要了
CreateNew(namespace,filestream)
CreateNew(namespace,filename)
建立新的文件
extract(filename as string ) 导出文件
属性:
Library 指明坐在的Library,最终还是调用XLibrary来返回数据流
Author 作者
namespace 文件在库中的位置
version 文件的版本
filename 原始文件名称
….. 其他数据表里面的内容
XI 使用方式说明
可以使用XLibrary的直接namespace的修改方式,护着使用XDirectory的方式进行存储.
对于AType
可以分配两个文件,一个是index文件专门用来存放索引,另外一个是data文件用于存放数据
一个index文件包含一个结构,进行随机存取
比如 struct sFile
{
ssize 结构体长度
ver 版本
beginpos 开始位置
endpos 结束位置
info 其他信息
}
然后根据ssize的大小一次读取一块,遍历全部区块到内存汇总
对于数据库,建议表结构
分三张表
configure表 用于存储该库的信息,对于主库则是游戏配置信息等等,游戏不同,存放的内容不同.
Files表 用于存放文件信息,相当于Atype的info字段的全部内容,由于数据库处理比较方便,可以完全分开然后映射到XFile类(继承之后添加他的属性)
Data表 用于存放二进制数据,与files的namespace或者其他键关联,确定使用的时候,在使用XLibrary.getStream()方法得到流