Cocos2d-x 3.2版本以上LUA指令碼熱更新(動態更新)解決方案
阿新 • • 發佈:2018-12-31
部落格地址: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?- bool AssetsManager::checkUpdate()
- {
- if (_versionFileUrl.size() == 0) returnfalse;
- _curl = curl_easy_init();
- if (! _curl)
- {
- CCLOG("can not init curl"
- returnfalse;
- }
- // 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);
- returnfalse;
- }
- 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();//設定檔案的搜尋路徑,會優先搜尋熱更新的目錄,以達到執行最新更新下來的程式碼
- returnfalse;
- }
- CCLOG("there is a new version: %s", _version.c_str());
- returntrue;
- }<span style="font-size:18px;"><span style="font-family:Arial;color:#333333;"><span style="line-height: 26px;">
- </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?
- // Multiple key names
- static std::string keyWithHash( constchar* 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);
- }
// 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?- 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;
- }
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?- <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()
<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個版本跨度,則提供不同的更新包進行下載。