能够进行热更新,是Lua脚本的最大优势,通过热更新能够解决诸多问题。例如App Store的审核,不用每次都提交版本,等待审核了,直接通过热更新更新游戏逻辑和素材即可。只有在进行大版本更新(修改底层C++部分)时候才需要重新提交审核。
官方的LuaTest中提供了一个热更新的简单例程,但是实际运行却没有效果。具体原因出在官方更新的连接上,官方的写法“貌似”不支持https的链接,但是他却用了一个 https://raw.github.com/samuele3hu/AssetsManagerTest/master/version 这样的链接,自然就导致更新失败了。
这里我们对lua的这个热更新的代码进行一下简单的分析,并提供一些我们在更新时候遇见的问题的解决方法,也许非常笨的方法,希望大家不要耻笑。
首先AssetsManager.cpp文件存储在,extensions目录下的Assets-Manager目录中,在cpp文件中,封装了整套热更新的功能。
首先在:
checkUpdate 成员方法中,进行了版本的比对
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运算,然后拼接到关键字后边。代码如下:
// 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
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;
}
以上都是废话,这里开始才是重点!
说了那么多,到底应该如何使用呢?
使用起来很简单,几句话
<span >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 > </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 > </span>self.assetsManager:retain()
<span > </span>self.assetsManager:setDelegate(onError, cc.ASSETSMANAGER_PROTOCOL_ERROR )
<span > </span>self.assetsManager:setDelegate(onProgress, cc.ASSETSMANAGER_PROTOCOL_PROGRESS)
<span > </span>self.assetsManager:setDelegate(onSuccess, cc.ASSETSMANAGER_PROTOCOL_SUCCESS )
<span > </span>self.assetsManager:setConnectionTimeout(3)
<span > </span>self.assetsManager:update()
但是仅仅是这样,是不足以满足商业项目需求的,我们需要对其进行一些简单的修改。
首先程序一上来,我们需要先判断一下版本是否需要更新,版本跨度有多大,我所用的方法是一上来,先访问服务器,提交当前本地版本号。由服务器根据我当前版本号判断版本跨度,同时返回一个更新包的下载地址。我们订的是3个以上版本下载全部数据,1-3个版本跨度,则提供不同的更新包进行下载。