Ogre的全称是面向对象的图形渲染引擎(Object-oriented Graphics Rendering Engine),它直接支持从文件系统或者zip档案中加载资源,同时也支持用户自定义档案格式。我基于StormLib为Ogre添加了MPQ格式支持。如图1所示,在Ogre中新增档案格式需要扩展ArchiveFactory、Archive和DataStream等几个类,具体来说,我新增了MpqArchiveFactory、MpqArchive和MpqDataStream三个类,其中工厂类(MpqArchiveFactory)用于向档案管理器注册(未画出),同时告诉管理器新增档案的类型("Mpq"),并提供相应档案类的创建、销毁函数;档案类(MpqArchive)代表实际的MPQ档案,它提供了档案内的文件查找、元信息查询以及文件打开的功能;文件打开后得到的是DataStream的派生对象(MpqDataStream),负责文件内的数据读取工作。 这三个新增类的具体定义如图2所示。
图1 Ogre中的档案模块理结构
图2 Mpq档案相关类的类图
由于MPQ档案内不包含文件名和路径名,我目前的实现要求所打开的档案内必须包含"(listfile)"文件,并从中读取文件名全集,并排序以便于后续检索。由于Archive中的find(), findFileInfo(), list()以及listFileInfo()函数的功能较为相似,我实现了一个filter()函数,用于从文件名全集中按需过滤出指定子集,同时还实现了一个details()函数,用于添加文件详细信息。这两个函数的适当组合便能实现上述四个函数的功能。由于MpqDataStream类功能较为简单,基本上是直接调用StormLib中的相应功能,完全实现只用了20余行代码。
Ogre自带了一个读取Quake3地图(zip档案)的例子。我用现成工具将pk3档案转换为MPQ格式,并修改该例配置文件,读取MPQ文档成功。有截图为证(其加载过程和地图漫游):
小花絮:Stream中的陷阱
MpqDataStream的实现过程让我走了许多冤枉路。当我快速实现这个类之后,实际运行却会导致一个访问异常的错误。这个错误发生在Ogre内部,我没有装Ogre源代码,无法精确定位错误,只能猜测可能的原因。由于Stream类、Factory类都很简单,所有函数都不超过3行,在目测“无误”后我就紧盯MpqArchive类的实现。由于MPQ文件是平板模式,在物理上没有目录树的结构,而Archive类假定档案是有结构的,我起初怀疑问题出在文件名和路径名的处理上,然而经过仔细编写并多次重构之后问题依旧,一筹莫展。尝试了debug、release两套配置,都报告错误。
在无奈之际,我又尝试自己编写一个针对文件系统的档案读取套装,实际上就是重新实现了FileSystemArchive系列类。由于这个套装十分简单并且与MPQ档案无关,这个尝试可以快速验证我对Ogre档案格式扩展机制的理解是否正确、全面。结果证明问题果然在这里——我的文件系统档案套装引发了完全一样的错误。
虽然明白错误在这个区域,但仍然无法知道究竟是那里疏忽了。我明白我需要查看故障处的源代码,然而SDK不但没有源代码,而且没有提供PDB(程序的debug数据库),只能重新编译,自己生成。在一通下载、编译、调试和分析之后,终于发现了原因:BSP场景管理器打开了BSP地图,然而却没有从我的MpqDataStream中读出数据,并在没有检查是否读出的情况就使用了“数据”,最终导致访问违例。而之所以未能从MpqDataStream读出数据,是因为DataStream父类中的保护成员mSize没有赋值(默认为0),导致DataStream误认为数据流的长度为0。最终,在MpqDataStream类的构造函数中设值正确的mSize值,解决问题。
一般来说,我在继承类的时候,会比较注意其中的虚函数,尤其是纯虚函数,认为这些是留给我用的钩子。但我以为保护型(proected)成员(尤其是保护型数据成员)一般是可选钩子,或者是留给我的福利,而不应为强加给我的责任。这种角色最好以纯虚函数的方式表达(模板方法模式),这样我一眼就看出我必须做这件事。更为要命的是,我再次查阅Ogre API Reference的时候,发现它并没有说这个mSize必须要设值,只说默认为0。