Hybrid模式-利用AssetsManager實現在線更新腳本文件lua、js、圖片等資源(免去平臺審覈週期)

首先說明一個問題:

爲什麼要在線更新資源和腳本文件!?

對於此問題,那要說的太多了,簡單概括,如果你的項目已經在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 中添加如下代碼:

先導入所需的頭文件:

1
2
3
4
5
6
7
8
9
10
11
#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

繼續添加變量和方法名:

1
2
3
void updateFiles();
void createDownDir();
string pathToSave;

pathToSave 變量用於保存下載的路徑!用於添加到  CCLuaEngine 引擎中,這樣便於CCLuaEngine查找Lua文件!

 第三步:在AppDelegate.cpp 中添加如下代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
static AssetsManager* pAssetsManager;
 
void AppDelegate::updateFiles(){
    createDownDir();
 
 
    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
2
3
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函數修改:

1
2
3
4
5
virtual void update();
 
修改成如下:
 
virtual bool update();

繼續到 AssetsManager.cpp 中update函數進行修改成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
修改爲:
 
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!

最後我們來看如下代碼:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
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文件完整的路徑傳入,如下:

1
2
3
//運行下載文件hello.lua
            string runLua = CCFileUtils::sharedFileUtils()->fullPathForFilename("hello.lua");
            pEngine->executeScriptFile(runLua.c_str());

      下面我們開始書寫測試代碼:

在AppDelegate.cpp中的  applicationDidFinishLaunching 函數中註釋一些代碼並且添加測試代碼,修改後的 applicationDidFinishLaunching 函數內容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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文件!

運行截圖如下:

QQ20130419-1

 

 

從如上的運行截圖中可以看出,首先我們得到服務器傳來的版本號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引擎的示例項目!

 

最後給出Himi的示例項目下載地址:

           OLUpdateFilesByHimi   :    http://vdisk.weibo.com/s/ycZU1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章