1. 程式人生 > >Cocos2d-x 3.2版本以上LUA指令碼熱更新(動態更新)解決方案

Cocos2d-x 3.2版本以上LUA指令碼熱更新(動態更新)解決方案

 部落格地址:http://blog.csdn.net/qq446569365

能夠進行熱更新,是Lua指令碼的最大優勢,通過熱更新能夠解決諸多問題。例如App Store的稽核,不用每次都提交版本,等待稽核了,直接通過熱更新更新遊戲邏輯和素材即可。只有在進行大版本更新(修改底層C++部分)時候才需要重新提交稽核。

官方的LuaTest中提供了一個熱更新的簡單例程,但是實際執行卻沒有效果。具體原因出在官方更新的連線上,官方的寫法“貌似”不支援https的連結,但是他卻用了一個 https://raw.github.com/samuele3hu/AssetsManagerTest/master/version 這樣的連結,自然就導致更新失敗了。

這裡我們對lua的這個熱更新的程式碼進行一下簡單的分析,並提供一些我們在更新時候遇見的問題的解決方法,也許非常笨的方法,希望大家不要恥笑。

首先AssetsManager.cpp檔案儲存在,extensions目錄下的Assets-Manager目錄中,在cpp檔案中,封裝了整套熱更新的功能。

首先在:

checkUpdate 成員方法中,進行了版本的比對

[cpp] view plain copy print?
  1. bool AssetsManager::checkUpdate()  
  2. {  
  3.     if (_versionFileUrl.size() == 0) returnfalse;  
  4.     _curl = curl_easy_init();  
  5.     if (! _curl)  
  6.     {  
  7.         CCLOG("can not init curl"
    );  
  8.         returnfalse;  
  9.     }  
  10.     // Clear _version before assign new value.
  11.     _version.clear();  
  12.     CURLcode res;  
  13.     curl_easy_setopt(_curl, CURLOPT_URL, _versionFileUrl.c_str());  
  14.     curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, 0L);  
  15.     curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, getVersionCode);  
  16.     curl_easy_setopt(_curl, CURLOPT_WRITEDATA, &_version);  
  17.     if (_connectionTimeout) curl_easy_setopt(_curl, CURLOPT_CONNECTTIMEOUT, _connectionTimeout);  
  18.     curl_easy_setopt(_curl, CURLOPT_NOSIGNAL, 1L);  
  19.     curl_easy_setopt(_curl, CURLOPT_LOW_SPEED_LIMIT, LOW_SPEED_LIMIT);  
  20.     curl_easy_setopt(_curl, CURLOPT_LOW_SPEED_TIME, LOW_SPEED_TIME);  
  21.     res = curl_easy_perform(_curl);  
  22.     if (res != 0)  
  23.     {  
  24.         Director::getInstance()->getScheduler()->performFunctionInCocosThread([&, this]{  
  25.             if (this->_delegate)  
  26.                 this->_delegate->onError(ErrorCode::NETWORK);  
  27.         });  
  28.         CCLOG("can not get version file content, error code is %d", res);  
  29.         curl_easy_cleanup(_curl);  
  30.         returnfalse;  
  31.     }  
  32.     string recordedVersion = UserDefault::getInstance()->getStringForKey(keyOfVersion().c_str());//首先獲取當前版本
  33.     if (recordedVersion == _version)//將當前版本和伺服器版本進行比對 如果不一樣
  34.     {  
  35.         Director::getInstance()->getScheduler()->performFunctionInCocosThread([&, this]{  
  36.             if (this->_delegate)  
  37.                 this->_delegate->onError(ErrorCode::NO_NEW_VERSION);  
  38.         });  
  39.         CCLOG("there is not new version");  
  40.         // Set resource search path.
  41.         setSearchPath();//設定檔案的搜尋路徑,會優先搜尋熱更新的目錄,以達到執行最新更新下來的程式碼
  42.         returnfalse;  
  43.     }  
  44.     CCLOG("there is a new version: %s", _version.c_str());  
  45.     returntrue;  
  46. }<span style="font-size:18px;"><span style="font-family:Arial;color:#333333;"><span style="line-height: 26px;">  
  47. </span></span></span>  
bool AssetsManager::checkUpdate()
{
    if (_versionFileUrl.size() == 0) return false;
    
    _curl = curl_easy_init();
    if (! _curl)
    {
        CCLOG("can not init curl");
        return false;
    }
    
    // Clear _version before assign new value.
    _version.clear();
    
    CURLcode res;
    curl_easy_setopt(_curl, CURLOPT_URL, _versionFileUrl.c_str());
    curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, 0L);
    curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, getVersionCode);
    curl_easy_setopt(_curl, CURLOPT_WRITEDATA, &_version);
    if (_connectionTimeout) curl_easy_setopt(_curl, CURLOPT_CONNECTTIMEOUT, _connectionTimeout);
    curl_easy_setopt(_curl, CURLOPT_NOSIGNAL, 1L);
    curl_easy_setopt(_curl, CURLOPT_LOW_SPEED_LIMIT, LOW_SPEED_LIMIT);
    curl_easy_setopt(_curl, CURLOPT_LOW_SPEED_TIME, LOW_SPEED_TIME);
    res = curl_easy_perform(_curl);
    
    if (res != 0)
    {
        Director::getInstance()->getScheduler()->performFunctionInCocosThread([&, this]{
            if (this->_delegate)
                this->_delegate->onError(ErrorCode::NETWORK);
        });
        CCLOG("can not get version file content, error code is %d", res);
        curl_easy_cleanup(_curl);
        return false;
    }
    
    string recordedVersion = UserDefault::getInstance()->getStringForKey(keyOfVersion().c_str());//首先獲取當前版本
    if (recordedVersion == _version)//將當前版本和伺服器版本進行比對 如果不一樣
    {
        Director::getInstance()->getScheduler()->performFunctionInCocosThread([&, this]{
            if (this->_delegate)
                this->_delegate->onError(ErrorCode::NO_NEW_VERSION);
        });
        CCLOG("there is not new version");
        // Set resource search path.
        setSearchPath();//設定檔案的搜尋路徑,會優先搜尋熱更新的目錄,以達到執行最新更新下來的程式碼
        return false;
    }
    
    CCLOG("there is a new version: %s", _version.c_str());
    
    return true;
}<span style="font-size:18px;"><span style="font-family:Arial;color:#333333;"><span style="line-height: 26px;">
</span></span></span>
遊戲版本通過UserDefault進行儲存。他的key是通過將URL進行hash運算,然後拼接到關鍵字後邊。程式碼如下: [cpp] view plain copy print?
  1. // Multiple key names
  2. static std::string keyWithHash( constchar* prefix, const std::string& url )//將更新網址轉化為hash並和更新 當前版本的 標識文字連線到一起
  3. {  
  4.     char buf[256];  
  5.     sprintf(buf,"%s%zd",prefix,std::hash<std::string>()(url));  
  6.     return buf;  
  7. }  
  8. // hashed version
  9. std::string AssetsManager::keyOfVersion() const//獲取用於儲存當前版本的key
  10. {  
  11.     return keyWithHash(KEY_OF_VERSION,_packageUrl);  
  12. }  
  13. // hashed version
  14. std::string AssetsManager::keyOfDownloadedVersion() const//獲取用於儲存當前已經下載的版本的key
  15. {  
  16.     return keyWithHash(KEY_OF_DOWNLOADED_VERSION,_packageUrl);  
  17. }  
// Multiple key names
static std::string keyWithHash( const char* prefix, const std::string& url )//將更新網址轉化為hash並和更新 當前版本的 標識文字連線到一起
{
    char buf[256];
    sprintf(buf,"%s%zd",prefix,std::hash<std::string>()(url));
    return buf;
}

// hashed version
std::string AssetsManager::keyOfVersion() const//獲取用於儲存當前版本的key
{
    return keyWithHash(KEY_OF_VERSION,_packageUrl);
}

// hashed version
std::string AssetsManager::keyOfDownloadedVersion() const//獲取用於儲存當前已經下載的版本的key
{
    return keyWithHash(KEY_OF_DOWNLOADED_VERSION,_packageUrl);
}
通過這種方式可以根據下載地址分別儲存版本號。

版本檢車結束後,就開始進行下載了。下載和解壓縮的方法是:downloadAndUncompress

[cpp] view plain copy print?
  1. void AssetsManager::downloadAndUncompress()  
  2. {  
  3.     do
  4.     {  
  5.         if (_downloadedVersion != _version)//判斷當前下載的資料包的版本是否和最新版一樣
  6.         {  
  7.             if (! downLoad()) break;  
  8.             Director::getInstance()->getScheduler()->performFunctionInCocosThread([&, this]{  
  9.                 UserDefault::getInstance()->setStringForKey(this->keyOfDownloadedVersion().c_str(),  
  10.                                                             this->_version.c_str());  
  11.                 UserDefault::getInstance()->flush();  
  12.             });  
  13.         }  
  14.         // Uncompress zip file.
  15.         if (! uncompress())//對下載的更新壓縮包進行解壓縮
  16.         {  
  17.             Director::getInstance()->getScheduler()->performFunctionInCocosThread([&, this]{  
  18.                 if (this->_delegate)  
  19.                     this->_delegate->onError(ErrorCode::UNCOMPRESS);  
  20.             });  
  21.             break;  
  22.         }  
  23.         Director::getInstance()->getScheduler()->performFunctionInCocosThread([&, this] {  
  24.             // Record new version code.
  25.             UserDefault::getInstance()->setStringForKey(this->keyOfVersion().c_str(), this->_version.c_str());  
  26.             UserDefault::getInstance()->setStringForKey("GameVersionStr"this->_version.c_str());//這行是我自己新增的。由於當前遊戲版本的儲存key 是通過hash等一些列運算所得出的,所以比較難獲取,想了個比較方便的辦法就是單獨儲存一下…………這樣就可以方便的在遊戲上顯示當前版本了
  27.             // Unrecord downloaded version code.
  28.             UserDefault::getInstance()->setStringForKey(this->keyOfDownloadedVersion().c_str(), "");  
  29.             UserDefault::getInstance()->flush();  
  30.             // Set resource search path.
  31.             this->setSearchPath();  
  32.             // Delete unloaded zip file.
  33.             string zipfileName = this->_storagePath + TEMP_PACKAGE_FILE_NAME;  
  34.             if (remove(zipfileName.c_str()) != 0)  
  35.             {  
  36.                 CCLOG("can not remove downloaded zip file %s", zipfileName.c_str());  
  37.             }  
  38.             if (this->_delegate) this->_delegate->onSuccess();  
  39.         });  
  40.     } while (0);  
  41.     _isDownloading = false;  
  42. }  
void AssetsManager::downloadAndUncompress()
{
    do
    {
        if (_downloadedVersion != _version)//判斷當前下載的資料包的版本是否和最新版一樣
        {
            if (! downLoad()) break;
            
            Director::getInstance()->getScheduler()->performFunctionInCocosThread([&, this]{
                UserDefault::getInstance()->setStringForKey(this->keyOfDownloadedVersion().c_str(),
                                                            this->_version.c_str());
                UserDefault::getInstance()->flush();
            });
        }
        
        // Uncompress zip file.
        if (! uncompress())//對下載的更新壓縮包進行解壓縮
        {
            Director::getInstance()->getScheduler()->performFunctionInCocosThread([&, this]{
                if (this->_delegate)
                    this->_delegate->onError(ErrorCode::UNCOMPRESS);
            });
            break;
        }
        
        Director::getInstance()->getScheduler()->performFunctionInCocosThread([&, this] {
            
            // Record new version code.
            UserDefault::getInstance()->setStringForKey(this->keyOfVersion().c_str(), this->_version.c_str());
            UserDefault::getInstance()->setStringForKey("GameVersionStr", this->_version.c_str());//這行是我自己新增的。由於當前遊戲版本的儲存key 是通過hash等一些列運算所得出的,所以比較難獲取,想了個比較方便的辦法就是單獨儲存一下…………這樣就可以方便的在遊戲上顯示當前版本了
            
            // Unrecord downloaded version code.
            UserDefault::getInstance()->setStringForKey(this->keyOfDownloadedVersion().c_str(), "");
            UserDefault::getInstance()->flush();
            
            // Set resource search path.
            this->setSearchPath();
            
            // Delete unloaded zip file.
            string zipfileName = this->_storagePath + TEMP_PACKAGE_FILE_NAME;
            if (remove(zipfileName.c_str()) != 0)
            {
                CCLOG("can not remove downloaded zip file %s", zipfileName.c_str());
            }
            
            if (this->_delegate) this->_delegate->onSuccess();
        });
       
    } while (0);
    
    _isDownloading = false;
}


以上都是廢話,這裡開始才是重點!

說了那麼多,到底應該如何使用呢?

使用起來很簡單,幾句話

[cpp] view plain copy print?
  1.     <span style="white-space:pre">local function onError(errorCode)  
  2.     end  
  3.     local function onProgress( percent )  
  4.     end  
  5.     local function onSuccess()  
  6.     end  
  7.     self.assetsManager =cc.AssetsManager:new(FileURL,  
  8.                                    "http://dzpk.57wan.cn/dzpk_logic/version.do",  
  9.                                    pathToSave)  
  10.     self.assetsManager:retain()  
  11.     self.assetsManager:setDelegate(onError, cc.ASSETSMANAGER_PROTOCOL_ERROR )  
  12.     self.assetsManager:setDelegate(onProgress, cc.ASSETSMANAGER_PROTOCOL_PROGRESS)  
  13.     self.assetsManager:setDelegate(onSuccess, cc.ASSETSMANAGER_PROTOCOL_SUCCESS )  
  14.     self.assetsManager:setConnectionTimeout(3)  
  15. <span style="white-space:pre">    </span>self.assetsManager:update()</span>self.assetsManager =cc.AssetsManager:new("http://www.xxx.com/updata.zip",  
  16.                                    "http://dzpk.xxx.com/version.php",  
  17.                                    cc.FileUtils:getInstance():getWritablePath())  
  18.     <span style="white-space:pre">    </span>self.assetsManager:retain()  
  19.     <span style="white-space:pre">    </span>self.assetsManager:setDelegate(onError, cc.ASSETSMANAGER_PROTOCOL_ERROR )  
  20.     <span style="white-space:pre">    </span>self.assetsManager:setDelegate(onProgress, cc.ASSETSMANAGER_PROTOCOL_PROGRESS)  
  21.     <span style="white-space:pre">    </span>self.assetsManager:setDelegate(onSuccess, cc.ASSETSMANAGER_PROTOCOL_SUCCESS )  
  22.     <span style="white-space:pre">    </span>self.assetsManager:setConnectionTimeout(3)  
  23. <span style="white-space:pre">    </span>self.assetsManager:update()  
    <span style="white-space:pre">local function onError(errorCode)

    end


    local function onProgress( percent )
    end


    local function onSuccess()
    end

    self.assetsManager =cc.AssetsManager:new(FileURL,
                                   "http://dzpk.57wan.cn/dzpk_logic/version.do",
                                   pathToSave)


    self.assetsManager:retain()
    self.assetsManager:setDelegate(onError, cc.ASSETSMANAGER_PROTOCOL_ERROR )
    self.assetsManager:setDelegate(onProgress, cc.ASSETSMANAGER_PROTOCOL_PROGRESS)
    self.assetsManager:setDelegate(onSuccess, cc.ASSETSMANAGER_PROTOCOL_SUCCESS )
    self.assetsManager:setConnectionTimeout(3)
<span style="white-space:pre">	</span>self.assetsManager:update()</span>self.assetsManager =cc.AssetsManager:new("http://www.xxx.com/updata.zip",
                                   "http://dzpk.xxx.com/version.php",
                                   cc.FileUtils:getInstance():getWritablePath())

    <span style="white-space:pre">	</span>self.assetsManager:retain()
    <span style="white-space:pre">	</span>self.assetsManager:setDelegate(onError, cc.ASSETSMANAGER_PROTOCOL_ERROR )
    <span style="white-space:pre">	</span>self.assetsManager:setDelegate(onProgress, cc.ASSETSMANAGER_PROTOCOL_PROGRESS)
    <span style="white-space:pre">	</span>self.assetsManager:setDelegate(onSuccess, cc.ASSETSMANAGER_PROTOCOL_SUCCESS )
    <span style="white-space:pre">	</span>self.assetsManager:setConnectionTimeout(3)
<span style="white-space:pre">	</span>self.assetsManager:update()
但是僅僅是這樣,是不足以滿足商業專案需求的,我們需要對其進行一些簡單的修改。

首先程式一上來,我們需要先判斷一下版本是否需要更新,版本跨度有多大,我所用的方法是一上來,先訪問伺服器,提交當前本地版本號。由伺服器根據我當前版本號判斷版本跨度,同時返回一個更新包的下載地址。我們訂的是3個以上版本下載全部資料,1-3個版本跨度,則提供不同的更新包進行下載。