轉自:http://www.himigame.com/iphone-cocos2dx/1354.html
首先說明一個問題:
為什么要在線更新資源和腳本文件!?
對於此問題,那要說的太多了,簡單概括,如果你的項目已經在google play 或Apple Store 等平台上架了,那么當你項目需要做一些活動或者修改前端的一些代碼等那么你需要重新提交一個新版本給平台,這時候你的上架時候是個不確定的時候,具體什么時候能上架,主要跟平台有關,你再着急,也沒有用的。
那么如果你的項目是使用腳本語言進行編寫的,例如lua,js等等,那么一旦你有需要更新你的項目,你完全可以通過從服務器下載最新的腳本和資源來實現在線更新,免去很多煩惱,至少更新再也不需要平台的審核來限制了不是么~(有些平台是禁止在線更新資源方式的,但是你懂得)
那么如何在項目中實現在線更新呢?則是本章具體需要跟大家分享的教程啦。
(有童鞋問我,單機怎么辦? 一般自己搭個服務器,專用於在線更新。不過一般單機不這么做,這套下載更新主要用於網游 )
下面進入本章的重要內容:
在cocos2dx 2.x 引擎的擴展包(extensions)中有一個 AssetsManager
AssetsManager 主要功能就是下載資源到本地,並幫你解壓!
如果大家還不知道這個類,那么可以先到cocos2dx引擎的http:///Users/slater/Documents/cocos2d-2.1rc0-x-2.1.2-hotfix/samples/Cpp/AssetsManagerTest 目錄下運行示例。
(注:當前Himi使用的是cocos2dx-2.1.2hotfix版本這個示例在我的mac os無法正常運行)
下面Himi新建個項目來詳細講解AssetsManager:
Himi這里拿lua項目進行,首先創建一個新的cocos2dx-lua 的項目:
第一步:將項目中Resoures目錄下的 hello.lua 刪除!
第二步:在AppDelegate.h 中添加如下代碼:
先導入所需的頭文件:
#include "cocos2d.h" #include "AssetsManager.h" #include "cocos-ext.h" using namespace std; using namespace cocos2d; using namespace extension; #if (CC_TARGET_PLATFORM != CC_PLATFORM_WIN32) #include <dirent.h> #include <sys/stat.h> #endif
繼續添加變量和方法名:
void updateFiles(); void createDownDir(); string pathToSave;
pathToSave 變量用於保存下載的路徑!用於添加到 CCLuaEngine 引擎中,這樣便於CCLuaEngine查找Lua文件!
第三步:在AppDelegate.cpp 中添加如下代碼:
static AssetsManager* pAssetsManager; void AppDelegate::updateFiles(){ createDownDir(); pAssetsManager = new AssetsManager("https://raw.github.com/HimiGame/himigame/master/hello.zip", "https://raw.github.com/HimiGame/himigame/master/version"); if(pAssetsManager->checkUpdate()){ if( pAssetsManager->update() ){//改源碼 CCLuaEngine* pEngine = CCLuaEngine::defaultEngine(); CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine); //首先添加下載文件的目錄 pEngine->addSearchPath(pathToSave.c_str()); //繼續添加本地hello2的路徑到CCLuaEngine中 string path = CCFileUtils::sharedFileUtils()->fullPathForFilename("hello2.lua"); pEngine->addSearchPath(path.substr(0, path.find_last_of("/")).c_str()); //運行下載文件hello.lua string runLua = CCFileUtils::sharedFileUtils()->fullPathForFilename("hello.lua"); pEngine->executeScriptFile(runLua.c_str()); } } } void AppDelegate::createDownDir(){ pathToSave = CCFileUtils::sharedFileUtils()->getWritablePath(); pathToSave += "Himi"; // Create the folder if it doesn't exist #if (CC_TARGET_PLATFORM != CC_PLATFORM_WIN32) DIR *pDir = NULL; pDir = opendir (pathToSave.c_str()); if (! pDir) { mkdir(pathToSave.c_str(), S_IRWXU | S_IRWXG | S_IRWXO); } #else if ((GetFileAttributesA(pathToSave.c_str())) == INVALID_FILE_ATTRIBUTES) { CreateDirectoryA(pathToSave.c_str(), 0); } #endif }
首先介紹createDwomDir函數:
(注:所有連接都是Hmi在GitHub服務器中的,大家可以所以訪問)!
此函數主要用於在項目目錄下新建一個文件夾,到底創建到哪里,你不用管,交給如下函數:
CCFileUtils::sharedFileUtils()->getWritablePath();
上面這個函數能從ios、android平台自動找到可寫入的路徑!
createDwomDir 函數中 pathToSave += “Himi”; 主要作用是在getWritablePath()路徑后自定義一個目錄名!需要不需要都可以的,如果想創建個,那就自定義即可,名字無所謂思密達。
繼續介紹 updateFiles 函數:
此函數中,首先我們調用 createDwomDir 函數用於創建我們新的寫入目錄,並且將目錄保存到pathToSave變量中。
然后我們創建了一個 AssetsManager 實例,這里要靜態。AssetsManager創建函數有兩種,如下:
1. AssetsManager::AssetsManager(const char* packageUrl, const char* versionFileUrl) 2. AssetsManager::AssetsManager(const char* packageUrl, const char* versionFileUrl, const char* storagePath)
首先看第一種創建函數:
參數1 : packgeUrl: 表示需要下載更新的zip包的url地址
參數2 : versionFileUrl :表示獲取當前服務器版本號的rul,用於匹配客戶端是否需要更新!
第二種創建方式多了一個參數: storagePath 表示我們的自定義包名,如createDwomDir函數中的pathToSave += “Himi” 一句功能一樣。
而在AssetsManager類中封裝了很多方法,例如檢查是否需要更新、更新下載文件、獲取packageUrl等。具體方法可看AssetsManager源碼!
pAssetsManager->checkUpdate() :通過得到服務器返回的版本號與本地版本號進行匹配如不一致則返回true,反之返回false。
(注:大家可以通過版本號對比,做其他功能,比如更新提示等)
一旦通過判斷checkUpdate函數返回true,我們即可調用AssetsManager中的update進行文件更新!
這里要注意:由於當前AssetsManager的源碼中並沒有給予我們判斷文件下載成功的函數!因此Himi與AssetsManager作者聯系,我們可以更改update函數讓其返回bool類型即可!
(注:update 函數中對版本、下載文件、解壓、存儲最新版本號等做了判斷,因此當此函數返回true,則完成一切操作)
修改方式如下:首先我們到源碼AssetsManager.h中將如下upate函數修改:
virtual void update(); 修改成如下: virtual bool update();
繼續到 AssetsManager.cpp 中update函數進行修改成如下:
bool AssetsManager::update() { // 1. Urls of package and version should be valid; // 2. Package should be a zip file. if (_versionFileUrl.size() == 0 || _packageUrl.size() == 0 || std::string::npos == _packageUrl.find(".zip")) { CCLOG("no version file url, or no package url, or the package is not a zip file"); return false; } // Check if there is a new version. if (! checkUpdate()) return false; // Is package already downloaded? string downloadedVersion = CCUserDefault::sharedUserDefault()->getStringForKey(KEY_OF_DOWNLOADED_VERSION); if (downloadedVersion != _version) { if (! downLoad()) return false; // Record downloaded version. CCUserDefault::sharedUserDefault()->setStringForKey(KEY_OF_DOWNLOADED_VERSION, _version.c_str()); CCUserDefault::sharedUserDefault()->flush(); } // Uncompress zip file. if (! uncompress()) return false; // Record new version code. CCUserDefault::sharedUserDefault()->setStringForKey(KEY_OF_VERSION, _version.c_str()); // Unrecord downloaded version code. CCUserDefault::sharedUserDefault()->setStringForKey(KEY_OF_DOWNLOADED_VERSION, ""); CCUserDefault::sharedUserDefault()->flush(); // Set resource search path. setSearchPath(); // Delete unloaded zip file. string zipfileName = _storagePath + TEMP_PACKAGE_FILE_NAME; if (remove(zipfileName.c_str()) != 0) { CCLOG("can not remove downloaded zip file"); } return true; }
當我們做了如此的修改后,那么當文件下載完成后則會返回true!
最后我們來看如下代碼:
CCLuaEngine* pEngine = CCLuaEngine::defaultEngine(); CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine); //首先添加下載文件的目錄 pEngine->addSearchPath(pathToSave.c_str()); //繼續添加本地hello2的路徑到CCLuaEngine中 string path = CCFileUtils::sharedFileUtils()->fullPathForFilename("hello2.lua"); pEngine->addSearchPath(path.substr(0, path.find_last_of("/")).c_str()); //運行下載文件hello.lua string runLua = CCFileUtils::sharedFileUtils()->fullPathForFilename("hello.lua"); pEngine->executeScriptFile(runLua.c_str());
首先我們將文件更新下來的路徑通過setScriptEngine添加到 CCLuaEngine中,然后將hello2.lua 的路徑也添加到CCLuaEngine的搜索途徑中,這樣一來 CCLuaEngine 會從我們設置的這兩個路徑中去找我們在lua中require的對應lua文件!這一步設置必須設置!因為CCLuaEngine不像cocos2dx那樣自動幫我們找文件路徑!CCLuaEngine 是不存在路徑的,所以我們要手動設置CCLuaEngine搜索路徑,以便找到對應的lua文件!
也正是因為CCLuaEngine不會自動幫我們找文件路徑,因此我們運行lua腳本時,必須將運行的腳本lua文件完整的路徑傳入,如下:
//運行下載文件hello.lua string runLua = CCFileUtils::sharedFileUtils()->fullPathForFilename("hello.lua"); pEngine->executeScriptFile(runLua.c_str());
下面我們開始書寫測試代碼:
在AppDelegate.cpp中的 applicationDidFinishLaunching 函數中注釋一些代碼並且添加測試代碼,修改后的 applicationDidFinishLaunching 函數內容如下:
bool AppDelegate::applicationDidFinishLaunching() { // initialize director CCDirector *pDirector = CCDirector::sharedDirector(); pDirector->setOpenGLView(CCEGLView::sharedOpenGLView()); // turn on display FPS pDirector->setDisplayStats(true); // set FPS. the default value is 1.0/60 if you don't call this pDirector->setAnimationInterval(1.0 / 60); // register lua engine // CCLuaEngine* pEngine = CCLuaEngine::defaultEngine(); // CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine); // //#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) // CCString* pstrFileContent = CCString::createWithContentsOfFile("hello.lua"); // if (pstrFileContent) // { // pEngine->executeString(pstrFileContent->getCString()); // } //#else // std::string path = CCFileUtils::sharedFileUtils()->fullPathForFilename("hello.lua"); // pEngine->addSearchPath(path.substr(0, path.find_last_of("/")).c_str()); // pEngine->executeScriptFile(path.c_str()); //#endif //刪除hello.lua pathToSave=""; updateFiles(); return true; }
下面開始運行!需要注意的是我們本地是完全不存在hello.lua文件的,所以一旦我們運行成功出現畫面說明已經利用AssetsManager成功的在線下載了hello.lua文件!
運行截圖如下:
從如上的運行截圖中可以看出,首先我們得到服務器傳來的版本號2.1.1,然后進行checkUpdate函數,此函數是從本地的存儲文件找是否有版本號,如果沒有那么就默認為可下載,如果有則會對比,如不一致則進行更新。
那么當文件完整下載下來之后update函數則自動會我們在本地保存最新從服務器拿到的版本號!緊接着update函數還為我們進行了對zip文件的解壓,解壓成功后會自動刪除zip包!
因此如果大家運行過自己的這個項目成功下載運行了,那么下載運行請刪除項目后再運行,因為第一次的成功運行已經將最新版本號記錄保存下來了,你也可以通過修改服務器版本號或者刪除項目的存儲文件。
總結本文的教程:
第一: CCLuaEngine 引擎是不會自動幫我們找文件的,所以你一旦有一個新的運行腳本的路徑,一定要通過 CCLuaEngine的 addSearchPath函數告知!這樣的話,當你的一個lua文件采用require其他腳本文件,CCLuaEngine就會在你 addSearchPath的路徑中進行查找!
第二: 如果你想讓自己項目自帶的腳本與下載腳本同時使用,例如自己項目有a.lua 其中a.lua 中包含一句代碼: requireb ”b” ,而b.lua是你通過在線更新下載下來的。那么a.lua 和 b.lua的路徑都要通過 addSearchPath 設置下各自的路徑。
第三: lua engine應該也是支持搜索路徑的優先級的,所以你可以通過控制pEngine->addSearchPath()的調用順序,從而控制當你本地項目與下載更新同時擁有同一個名字的腳本等資源,可以優先選擇使用哪個!
第四: 在AppStore 規定不允許在主游戲線程中進行聯網,然后我們使用的AssetsManager的下載更新卻是在聯網下載,所以大家要使用異步來做!另外及時沒有這條規定我想童鞋們也不會讓聯網放主游戲線程中吧!
第五:AssetsManager 中還有其他的功能,更多的功能請大家自己看cocos2dx引擎的示例項目!
附:http://blog.csdn.net/sonikk/article/details/8871403
源碼相關文件路徑:
C:\dev\cocos2d-2.1rc0-x-2.1.2-hotfix\extensions\cocos-ext.h
C:\dev\cocos2d-2.1rc0-x-2.1.2-hotfix\extensions\AssetsManager\AssetsManager.h
C:\dev\cocos2d-2.1rc0-x-2.1.2-hotfix\extensions\AssetsManager\AssetsManager.cpp
引用方法:
#include "cocos-ext.h"
#include "AssetsManager.h"
using namespace cocos2d::extension;
Additional Include Directories:
C:\dev\cocos2d-2.1rc0-x-2.1.2-hotfix\extensions
C:\dev\cocos2d-2.1rc0-x-2.1.2-hotfix\extensions\AssetsManager
Additional Library Directories:
C:\dev\cocos2d-2.1rc0-x-2.1.2-hotfix\Debug.win32
Additinal Dependencies:
libcurl_imp.lib
libExtensions.lib
該AssetsManager的基本流程:
1. 配置需要更新的zip的URL,更新版本號的URL,更新存放的相對路徑
2. 從Server獲取該zip文件的版本號
3. 對比Client中的UserDefault.xml中current-version-code鍵的值(當前版本號)是否過期
4. 若Server的版本比Client的新,則通過http請求下載該zip
5. 解壓縮該zip文件
6. 下載后通過CCFileUtils的fullPathForFilename方法來獲取文件的引用
下載流程:
update():
1. 配置的zip的URL和version的URL必須合法,且非空
2. 檢驗Server是否存在新版本
3. 讀取UserDefault.xml的downloaded-version-code,對比當前版本號,若不相等則進行zip包下載
4. 若下載完成,記錄最新的版本號於UserDefault.xml的downloaded-version-code中,並flush刷新
5. 解壓縮zip包
6. 若解壓成功,記錄最新的版本號於UserDefault.xml的current-version-code中,並把downloaded-version-code刪除,並flush刷新
7. 設置搜索路徑,(先獲取搜索路徑vector,然后將新的搜索路徑插入到該vector中,將該vector重新放入CCFileUtils中)
8. 刪除未加載的cocos2dx-update-temp-package.zip文件