把游戏中的资源文件(纹理,模型,材质,音乐,配置xml,json,脚本)打包有很多好处,也成为MMO开发的基本常识.对资源进行打包可以带来以下好处:
. 增加游戏资源和脚本破解的难度。大多游戏制作公司都不希望自己花高昂代价制作的资料被人全盘爬过去使用,更不忍受逻辑脚本完全暴露在同行的面前。
. 自定义的资源包比访问散文件资源有更快的查找和读取速度,消耗更少的系统资源,如文件句柄。
. 自定义资源包可以提供更简单可用的文件存取API、加解密和压缩方案。
. 一般来说,打包的资源也会比散文件形式的资源占用更小的磁盘存储空间。
但Cocos2dx引擎没有提供资源包的支持,所以我自己实现了一个。
一、Cocos2dx资源存取分析
Cocos2dx的文件操作使用的是一个极简单的封装--CCFileUtils ,CCFileUtils代理了基本的文件和路径操作,包括查找文件,读取文件,获取文件路径等。查看CCFileUtils的实现,我们可以发现它是夸平台的,它的夸平台是通过其子类来实现的。
. 在IOS下它的实现是CCFileUtilsIOS ,使用Cocoa 的相关API来获取app路径和拼凑资源完整路径,它的文件访问则是继承自CFileUtil使用CRT标准流函数进行访问
. 在Android它的实现为CCFileUtilsAndroid,其使用JNI从Android sdk里读取app的资源和可写路径,使用ZipFile从资源apk(zip文件)里访问资源文件
. 在Windows下它的实现为CCFileUitlsWin32,除了路径操作,和IOS一样使用CRT标准流进行文件访问
其基本结构如下所示:
CCFileUtil的主要接口有4个,但getFileDataFromZip从未被使用过。主要接口及功能如下:
//获取文件的全路径 virtual std::string fullPathForFilename(const char* pszFileName); //查找文件是否存在 virtual bool isFileExist(const std::string& strFilePath) = 0; //读取文件,引擎的资源加载均使用此函数 virtual unsigned char* getFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize); //从zip里读取文件,在引擎里从未被使用 virtual unsigned char* getFileDataFromZip(const char* pszZipFilePath, const char* pszFileName, unsigned long * pSize);
二、各平台上的文件操作权限和目录分析
操作系统 | Android | IOS | PC |
可写目录 | /data/data/package name/files 目录为android上的app私有目录,具体目录可以 通过android sdk的Activity的getFilesDir来获取 | app安装目录下的Documents子目录 | 任何有权限的目录 |
安装包资源存放目录 | 只有位于resource\raw和assets下面的资源会被原封不动地打包到apk,游戏内一般使用 assets,原因是raw不允许有目录层次结构。assets在apk被安装成功后,仍然是以资源 apk(zip压缩)的方式存在于存储设备的只读目录中,具体路径可以通过android sdk的 assetmanager获取。使用ZipFile进行访问和解压。 | 原封不动地把Xcode工程中的资源包括目录存放在app安装home目录下, 为只读目录,数据不可修改 | 由安装包定义 |
额外存储器可访问性 | SD卡可读写 | 不可显示访问 | 可访问 |
权限申请 | 需要申请读,写和SD卡读写权限 | 不需要 | 不需要 |
三、自定义资源包系统的实现
在考虑了Cocos2dx的文件操作实现和各系统平台的限制之后,我的自定义包系统基本实现思路如下:
1. 在获取自定义资源包的好处的时候,尽可能地提供与Cocos2dx引擎其它部分的兼容性。
2. 在Android平台上优先把资源包创建在SD卡上--大部分低端Android手机的自带存储非常有限
3. 在Android平台上做资源冗余,以提高资源读取速度,具体的为安装后的第一次运行时,从只读的资源APk里把资源包解压到可写目录或SD卡一份,这样在资源创建的时候就不用每次从zip里解压文件了
4. 包文件的读取API提供线程安全支持,为异步资源管理器的资源加载提供基础设施支持
5. 提供可订制的加密和压缩接口,以方便外部配置压缩和加密方式
6. 为后续的资源升级做准备,在提供资源读取API的同时提求资源更新,删除和校验方法
7. 提供打包、解包和包查看工具为制作流程和自动化构建提供支持
8. 修改cocos2dx以全路径读取文件的方式改为相对路径--打包只需要把相对Resource目录下的子路径截下来就可以做为唯一路径使用。
资源包系统架构图如下所示
对游戏资源加载来说,主要使用的接口为IPackageSystem定义的:
//获取文件描述信息,如文件长度,md5等 virtual bool getFileDesc(BlockDesc& desc,const std::string& filepath) = 0 ; //查找文件是否存在 virtual bool isFileExist(const std::string& filepath) const = 0 ; //读取文件到buffer里,返回值为读取到的文件长度 virtual size_t readFile(const std::string& filepath ,u8* buffer ,size_t bufferLen) = 0 ;
这些接口PackageSystem通过扫描它加载的所有资源包,并调用资源包的相应接口来代理实现。
四、集成资源包系统到Cocos2dx
要把资源包集成到cocos2dx,还需要做如下改动:
.Hack CCFileUtils的getFileData函数,使之转而从资源包内读取数据
. 提供另一个函数来实现getFileData原来实现的从apk读取文件的功能,我这儿实现为readFileFromInstallPackage
这两个修改应用之后的CCFileUtils::geFileData实现如下:
unsigned char* CCFileUtils::getFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize) { string lowerFileName = pszFileName; std::transform(lowerFileName.begin(),lowerFileName.end(),lowerFileName.begin(),::tolower) ; if(m_packageSystem && m_packageSystem->isFileExist(lowerFileName)) { ext::u8* buffer = null; ext::BlockDesc desc ; if(m_packageSystem->getFileDesc(desc,lowerFileName) && desc.length>0) { buffer = new ext::u8[desc.length] ; if(buffer) { if(m_packageSystem->readFile(lowerFileName,buffer,desc.length)==desc.length) { *pSize = desc.length ; } else { delete[] buffer ; buffer = null ; } } } return buffer ; } return readFileFromInstallPackage(pszFileName,pszMode,pSize) ; }
. cocos2dx对zip文件的读取(具体在ZipUtil.cpp里)也依赖了CCFileUtils::getFileData,这儿需要把getFileData修改为readFileFromInstallPackage
. 修改CCFileUtil中的fullPathForFilename和isFileExist,使其用PackageSytem代理实现
. app初始化代码里添加资源包管理系统的初始化代码