把游戲中的資源文件(紋理,模型,材質,音樂,配置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初始化代碼里添加資源包管理系統的初始化代碼