Cocos2d-x实现资源热更新详解
热更新介绍
什么是热更新
游戏客户端启动时,会主动向服务端请求检查版本号,并将最新资源更新到本地。
应用场景
- 情况一:修复严重Bug:当游戏客户端发布后,突然发现存在比较严重的Bug需要修复,此时可通过更新游戏的代码(如Lua代码)来解决问题。
- 情况二:开展节日活动:例如情人节到来时,需要在游戏中营造节日氛围,这时就需要更新游戏资源或增加一些相关功能。
好处
采用热更新方式,无需重新打包游戏并提交到应用市场等待审核,能够更快速地将更新内容推送给玩家。
热更新流程
在Cocos2d - x中,已经封装了用于实现热更新功能的类AssetsManager。以下是具体的实现代码及详细解释:
#include "UpdateLayer.h"
#include "HelloWorldScene.h"
#if (CC_TARGET_PLATFORM != CC_PLATFORM_WIN32)
#include <dirent.h>
#include <sys/stat.h>
#endif
// UpdateLayer类的实现
class UpdateLayer : public CCLayer, public AssetsManagerDelegateProtocol {
public:
static CCScene* scene() {
CCScene* scene = CCScene::create();
scene->addChild(UpdateLayer::create());
return scene;
}
static UpdateLayer* create() {
UpdateLayer* pLayer = new UpdateLayer;
if (pLayer && pLayer->init()) {
pLayer->autorelease();
return pLayer;
}
delete pLayer;
return NULL;
}
// 初始化函数
bool init() {
if (CCLayer::init()) {
// 设置资源包下载目录
m_downloadDir = CCFileUtils::sharedFileUtils()->getWritablePath();
m_downloadDir += "download";
// 添加资源包下载路径到搜索路径,优先搜索更新的资源
std::vector<std::string> searchPaths = CCFileUtils::sharedFileUtils()->getSearchPaths();
searchPaths.insert(searchPaths.begin(), m_downloadDir);
CCFileUtils::sharedFileUtils()->setSearchPaths(searchPaths);
// 提示标签
m_label = CCLabelTTF::create("", "Arial", 18);
m_label->setAnchorPoint(ccp(1, 0.5));
m_label->setPosition(ccp(465, 20));
addChild(m_label);
// 菜单
CCMenu* menu = CCMenu::create();
menu->setPosition(CCPointZero);
addChild(menu);
CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
// 重置按钮
CCMenuItemFont* itemReset = CCMenuItemFont::create("reset", this, menu_selector(UpdateLayer::reset));
itemReset->setPosition(ccp(visibleSize.width / 2, 50));
menu->addChild(itemReset);
// 获取当前版本号按钮
CCMenuItemFont* itemGetClientVersion = CCMenuItemFont::create("getClientVersion", this, menu_selector(UpdateLayer::getClientVersion));
itemGetClientVersion->setPosition(ccp(visibleSize.width / 2, 100));
menu->addChild(itemGetClientVersion);
// 获取服务器最新版本按钮
CCMenuItemFont* itemGetServerVersion = CCMenuItemFont::create("checkUpdate", this, menu_selector(UpdateLayer::checkUpdate));
itemGetServerVersion->setPosition(ccp(visibleSize.width / 2, 150));
menu->addChild(itemGetServerVersion);
// 更新版本按钮
CCMenuItemFont* itemUpdateVersion = CCMenuItemFont::create("updateVersion", this, menu_selector(UpdateLayer::update));
itemUpdateVersion->setPosition(ccp(visibleSize.width / 2, 200));
menu->addChild(itemUpdateVersion);
// 进入场景按钮
CCMenuItemFont* itemEnterScene = CCMenuItemFont::create("enterScene", this, menu_selector(UpdateLayer::enterScene));
itemEnterScene->setPosition(ccp(visibleSize.width / 2, 250));
menu->addChild(itemEnterScene);
return true;
}
return false;
}
// 获取AssetsManager实例
AssetsManager* getAssetsManager() {
static AssetsManager* s_assetsManager = NULL;
if (s_assetsManager == NULL) {
s_assetsManager = new AssetsManager("http://is.muni.cz/do/rect/el/ikony/sady/ikonky-128×128-png.zip",
"https://coding.net/u/linchaolong/p/Test/git/raw/master/README.md",
m_downloadDir.c_str());
s_assetsManager->setDelegate(this);
s_assetsManager->setConnectionTimeout(3);
}
return s_assetsManager;
}
// 初始化下载目录
void initDownloadDir() {
// 如果下载目录不存在,则创建下载目录
#if (CC_TARGET_PLATFORM != CC_PLATFORM_WIN32)
DIR *pDir = NULL;
pDir = opendir(m_downloadDir.c_str());
if (!pDir) {
mkdir(m_downloadDir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
}
#else
if ((GetFileAttributesA(m_downloadDir.c_str())) == INVALID_FILE_ATTRIBUTES) {
CreateDirectoryA(m_downloadDir.c_str(), 0);
}
#endif
}
// 删除目录
void deleteDir(std::string dir) {
// Remove downloaded files
#if (CC_TARGET_PLATFORM != CC_PLATFORM_WIN32)
std::string command = "rm -r ";
// Path may include space.
command += "\"" + dir + "\"";
system(command.c_str());
#else
std::string command = "rd /s /q ";
// Path may include space.
command += "\"" + dir + "\"";
system(command.c_str());
#endif
}
// 错误处理回调函数
void onError(cocos2d::extension::AssetsManager::ErrorCode errorCode) {
switch (errorCode) {
case cocos2d::extension::AssetsManager::kCreateFile:
CCLOG("error : create file failure");
m_label->setString("error : create file failure");
break;
case cocos2d::extension::AssetsManager::kNetwork:
CCLOG("error : no network");
m_label->setString("error : no network");
break;
case cocos2d::extension::AssetsManager::kNoNewVersion:
CCLOG("error : no new version");
m_label->setString("error : no new version");
break;
case cocos2d::extension::AssetsManager::kUncompress:
CCLOG("error : uncompress file error");
m_label->setString("error : uncompress file error");
break;
default:
break;
}
}
// 下载进度回调函数
void onProgress(int percent) {
char progress[20];
snprintf(progress, 20, "downloading %d%%", percent);
CCLOG("%s", progress);
m_label->setString(progress);
}
// 下载成功回调函数
void onSuccess() {
CCLOG("download success.");
m_label->setString("download success.");
}
// 更新版本函数
void update(CCObject* pSender) {
// 初始化下载目录
initDownloadDir();
// 下载更新包
getAssetsManager()->update();
}
// 重置函数
void reset(CCObject* pSender) {
if ("" != m_downloadDir) {
// 删除下载目录
deleteDir(m_downloadDir);
}
// 删除版本号
getAssetsManager()->deleteVersion();
}
// 获取当前客户端版本号函数
void getClientVersion(CCObject* pSender) {
CCString* msg = CCString::createWithFormat("current client version : %s", getAssetsManager()->getVersion().c_str());
CCLOG("%s", msg->getCString());
m_label->setString(msg->getCString());
}
// 检查是否有版本更新函数
void checkUpdate(CCObject* pSender) {
if (getAssetsManager()->checkUpdate()) {
CCLOG("has new version");
m_label->setString("has new version");
} else {
CCLOG("has not new version");
m_label->setString("has not new version");
}
}
// 进入场景函数
void enterScene(CCObject* pSender) {
CCDirector::sharedDirector()->replaceScene(HelloWorld::scene());
}
protected:
// 初始化下载目录
void initDownloadDir();
// 删除目录
void deleteDir(std::string dir);
private:
CCLabelTTF* m_label;
std::string m_downloadDir;
AssetsManager* getAssetsManager();
};
// 头文件定义
#ifndef __HOTUPDATER_H__
#define __HOTUPDATER_H__
#include "cocos2d.h"
USING_NS_CC;
#include "cocos-ext.h"
USING_NS_CC_EXT;
#include "AssetsManager/AssetsManager.h"
// 热更新实现示例
class UpdateLayer : public CCLayer, public AssetsManagerDelegateProtocol {
public:
static CCScene* scene();
static UpdateLayer* create();
// 初始化
bool init();
// 下载回调函数
virtual void onError(cocos2d::extension::AssetsManager::ErrorCode errorCode);
virtual void onProgress(int percent);
virtual void onSuccess();
// 菜单回调函数
void reset(CCObject* pSender); // 重置版本
void getClientVersion(CCObject* pSender); // 获取当前客户端版本号
void checkUpdate(CCObject* pSender); // 检查是否有版本更新
void update(CCObject* pSender); // 更新版本
void enterScene(CCObject* pSender); // 进入场景,如果未更新屏幕中间会显示叹号的图片,更新后会显示另一张图片
protected:
// 初始化下载目录
void initDownloadDir();
// 删除目录
void deleteDir(std::string dir);
private:
CCLabelTTF* m_label;
std::string m_downloadDir;
AssetsManager* getAssetsManager();
};
#endif
代码解释
- 初始化下载目录:在
initDownloadDir函数中,会检查下载目录是否存在,如果不存在则创建该目录。 - 获取
AssetsManager实例:getAssetsManager函数用于获取AssetsManager的单例对象,并设置其代理和连接超时时间。 - 错误处理:
onError函数根据不同的错误码进行相应的日志输出和提示信息显示。 - 下载进度监控:
onProgress函数会实时显示下载进度。 - 下载成功处理:
onSuccess函数在下载成功后输出日志并显示提示信息。 - 更新操作:
update函数会先初始化下载目录,然后调用AssetsManager的update方法开始下载更新包。 - 重置操作:
reset函数会删除下载目录和版本号。 - 版本检查:
checkUpdate函数用于检查是否有新版本。 - 进入场景:
enterScene函数用于切换到HelloWorld场景。
通过以上步骤,我们可以在Cocos2d - x中实现游戏资源的热更新功能。