学习Cocos2d-x Lua:Lua 资源热更新

2015年03月24日 10:40 0 点赞 0 评论 更新于 2025-11-21 18:22

在这个系列中,我们主要学习Cocos2d-x Lua,总结Lua开发过程中涉及的知识点,以及如何在开发过程中使用Cocos Code IDE。本篇将详细讲解Lua资源热更新。

什么是热更新

热更新指的是客户端的更新。其大致流程为:客户端启动后访问更新的URL接口,根据更新接口的反馈,下载更新资源,然后使用新的资源启动客户端,或者直接使用新资源而不重启客户端。

热更新代码使用场景

  • 活动组织:情人节快到了,若想组织游戏内活动,热更新可确保及时部署活动内容,避免错过时机。
  • Bug修复:当发现严重的bug时,可通过热更新快速修复问题。
  • 内容扩展:想要添加新的场景或关卡来延长游戏的生命周期时,热更新能及时将新内容推送给用户。

在Cocos2d-x引擎中实现热更新

LuaEngine

LuaEngine是一个能实时运行Lua脚本的对象。正是因为有了LuaEngine这个C++类对象,才具备了实现热更新技术的基础。

获取LuaEngine脚本引擎对象

// 获取LuaEngine引擎脚本类的单例对象
LuaEngine *engine = LuaEngine::getInstance();
// 设置该单例对象作为脚本引擎管理器对象的脚本引擎
ScriptEngineManager::getInstance()->setScriptEngine(engine);

使用LuaEngine执行Lua字符串脚本

// 使用Lua脚本引擎执行Lua字符串脚本
engine->executeString("print(\"Hello 蓝鸥\")");

使用LuaEngine执行Lua文件脚本

// 获取储存的文件路径
std::string path = FileUtils::getInstance()->getWritablePath();
path += "hellolanou.lua";

// 创建一个文件指针
// 路径、模式
FILE* file = fopen(path.c_str(), "w");
if (file) {
fputs("print (\"蓝鸥!!!\")", file);
fclose(file);
} else {
CCLOG("save file error.");
}

// 使用Lua脚本引擎执行Lua文件脚本
engine->executeScriptFile(path.c_str());

lua_State

lua_State可被视为“脚本上下文”,主要包含当前Lua脚本环境的运行状态信息以及GC相关信息。

在使用Cocos2d-x引擎开发并需要使用Lua时,需要连接到libcocos2d和libluacocos2d两个静态库。同时,要在lua_State对象中注册对应的功能模块类。若不想使用某些模块,可在luamoduleregister.h中注释掉对应的一行代码。

int lua_module_register(lua_State* L) {
// 注册cocosdenshion模块
register_cocosdenshion_module(L);
// 注册network网络模块
register_network_module(L);
#if CC_USE_CCBUILDER
// 注册cocosbuilder模块
register_cocosbuilder_module(L);
#endif
#if CC_USE_CCSTUDIO
// 注册coccostudio模块
register_cocostudio_module(L);
#endif
// 注册ui模块
register_ui_moudle(L);
// 注册extension模块
register_extension_module(L);
#if CC_USE_SPINE
// 注册spine模块
register_spine_module(L);
#endif
#if CC_USE_3D
// 注册3d模块
register_cocos3d_module(L);
#endif
// 注册音频audio模块
register_audioengine_module(L);
return 1;
}

若在使用Cocos2d-x引擎时需要使用Quick框架,同样需要在lua_State注册Quick框架的对应模块。

static void quick_module_register(lua_State *L) {
luaopen_lua_extensions_more(L);

lua_getglobal(L, "_G");
if (lua_istable(L, -1)) // stack:...,_G,
{
register_all_quick_manual(L);
// extra
luaopen_cocos2dx_extra_luabinding(L);
register_all_cocos2dx_extension_filter(L);
luaopen_HelperFunc_luabinding(L);
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
luaopen_cocos2dx_extra_ios_iap_luabinding(L);
#endif
}
lua_pop(L, 1);
}

LuaStack

通常情况下,每一个lua_State对象对应一个LuaStack。

// 使用LuaEngine对象获取Lua的函数栈
LuaStack* stack = engine->getLuaStack();
#if ANYSDK_DEFINE > 0
lua_getglobal(stack->getLuaState(), "_G");
tolua_anysdk_open(stack->getLuaState());
tolua_anysdk_manual_open(stack->getLuaState());
lua_pop(stack->getLuaState(), 1);
#endif
// 设置加密用的密钥
stack->setXXTEAKeyAndSign("2dxLua", strlen("2dxLua"), "XXTEA", strlen("XXTEA"));

AssetsManager

资源管理器的设计目的是在游戏运行时实现资源热更新技术。这里的资源可以是图片、音频,甚至是游戏的脚本本身。使用资源管理器,可将新的资源上传到服务器,游戏会跟踪远程服务器上的修改,将新资源下载到用户设备上并在游戏中使用。这样,全新的设计、游戏体验和内容可立即推送给用户,且无需针对各个渠道重新打包应用程序和经历痛苦的应用审核,几乎没有成本。

创建AssetsManager对象

static AssetsManager *assetManager = NULL;

if (!assetManager) {
/* 创建AssetsManager对象
* @param 资源包的下载路径
* @param 资源包的当前版本
* @param 资源包下载后的存储路径
*/
assetManager = new AssetsManager(
"http://project.lanou3g.com/game/cocos/teacher/test/src.zip",
"http://project.lanou3g.com/game/cocos/teacher/test/version.php",
_pathToSave.c_str());
// 设置AssetsManager对象的回调对象
assetManager->setDelegate(this);
// 设置AssetsManager对象的timeout时间
assetManager->setConnectionTimeout(3);
}

AssetsManagerDelegateProtocol

AssetsManagerDelegateProtocal是一个类接口,主要用于封装下载过程中的回调接口。

class AssetsManagerDelegateProtocol {
public:
virtual ~AssetsManagerDelegateProtocol() {};
public:
/* @brief Call back function for error
@param errorCode Type of error
* @js NA
* @lua NA
*/
virtual void onError(AssetsManager::ErrorCode errorCode) {};
/** @brief Call back function for recording downloading percent
@param percent How much percent downloaded
@warning    This call back function just for recording downloading percent.
AssetsManager will do some other thing after downloading, you should
write code in onSuccess() after downloading.
* @js NA
* @lua NA
*/
virtual void onProgress(int percent) {};
/** @brief Call back function for success
* @js NA
* @lua NA
*/
virtual void onSuccess() {};
};

创建自动更新类Update

Update.h

//
//  Update.h
//  hello
//
//  Created by 蓝鸥.
//
//

#ifndef __hello__Update__
#define __hello__Update__

#include <stdio.h>
#include "cocos2d.h"
#include "extensions/cocos-ext.h"

class Update : public cocos2d::Layer, public cocos2d::extension::AssetsManagerDelegateProtocol {
public:
Update();
virtual ~Update();

virtual bool init();
void update(cocos2d::Ref *pSender);
void reset(cocos2d::Ref *pSender);

// 继承的回调函数
virtual void onError(cocos2d::extension::AssetsManager::ErrorCode errorCode);
virtual void onProgress(int percent);
virtual void onSuccess();
CREATE_FUNC(Update);

private:
cocos2d::extension::AssetsManager *getAssetsManager();
// 创建下载到的目录路径
void initDownloadDir();

private:
std::string _pathToSave;
// 用来显示下载进度的Label标签
cocos2d::Label *_showDownloadInfo;
};

#endif /* defined(__hello__Update__) */

Update.cpp

//
//  Update.cpp
//  hello
//
//  Created by 蓝鸥.
//
//

#include "Update.h"

#if(CC_TARGET_PLATFORM != CC_PLATFORM_WIN32)

#include <dirent.h>
#include <sys/stat.h>

#endif

USING_NS_CC;
USING_NS_CC_EXT;

#define DOWNLOAD_FILE "download"

#include "CCLuaEngine.h"

Update::Update() :
_pathToSave(""),
_showDownloadInfo(NULL) {

}

Update::~Update() {
AssetsManager *assetManager = getAssetsManager();
CC_SAFE_DELETE(assetManager);
}

bool Update::init() {
if (!Layer::init()) {
return false;
}

Size winSize = Director::getInstance()->getWinSize();

initDownloadDir();

_showDownloadInfo = Label::createWithSystemFont("", "Arial", 20);
_showDownloadInfo->setPosition(Vec2(winSize.width / 2, winSize.height / 2 - 20));
this->addChild(_showDownloadInfo);

auto itemLabel1 = MenuItemLabel::create(
Label::createWithSystemFont("Reset", "Arial", 20), CC_CALLBACK_1(Update::reset, this));
auto itemLabel2 = MenuItemLabel::create(
Label::createWithSystemFont("Update", "Arial", 20), CC_CALLBACK_1(Update::update, this));

auto menu = Menu::create(itemLabel1, itemLabel2, NULL);
this->addChild(menu);

itemLabel1->setPosition(Vec2(winSize.width / 2, winSize.height / 2 + 20));
itemLabel2->setPosition(Vec2(winSize.width / 2, winSize.height / 2));

menu->setPosition(Vec2::ZERO);

return true;
}

void Update::onError(AssetsManager::ErrorCode code) {
switch (code) {
case cocos2d::extension::AssetsManager::ErrorCode::NO_NEW_VERSION:
_showDownloadInfo->setString("no new version");
break;
case cocos2d::extension::AssetsManager::ErrorCode::NETWORK:
_showDownloadInfo->setString("no new version");
break;
case cocos2d::extension::AssetsManager::ErrorCode::CREATE_FILE:
_showDownloadInfo->setString("create file error");
break;
default:
break;
}
}

void Update::onProgress(int percent) {
if (percent < 0) {
return;
}

char progress[20];
snprintf(progress, 20, "download %d%%", percent);

_showDownloadInfo->setString(progress);
}

void Update::onSuccess() {
CCLOG("download success");

_showDownloadInfo->setString("download success");

std::string path = FileUtils::getInstance()->getWritablePath() + DOWNLOAD_FILE;

LuaEngine* pEngine = LuaEngine::getInstance();

// 首先添加下载文件的目录
pEngine->addSearchPath(_pathToSave.c_str());

path += "/src/main.lua";

pEngine->executeScriptFile(path.c_str());
}

AssetsManager* Update::getAssetsManager() {
static AssetsManager *assetManager = NULL;

if (!assetManager) {
/* 创建AssetsManager对象
* @param 资源包的下载路径
* @param 资源包的当前版本
* @param 资源包下载后的存储路径
*/
assetManager = new AssetsManager(
"http://project.lanou3g.com/game/cocos/teacher/test/src.zip",
"http://project.lanou3g.com/game/cocos/teacher/test/version.php",
_pathToSave.c_str());
// 设置AssetsManager对象的回调对象
assetManager->setDelegate(this);
// 设置AssetsManager对象的timeout时间
assetManager->setConnectionTimeout(3);
}

return assetManager;
}

void Update::initDownloadDir() {
CCLOG("initDownloadDir");

_pathToSave = FileUtils::getInstance()->getWritablePath();
_pathToSave += DOWNLOAD_FILE;

CCLOG("Path: %s", _pathToSave.c_str());

#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 ((GetFileAttributes(_pathToSave.c_str())) == INVALID_FILE_ATTRIBUTES) {
CreateDirectoryA(_pathToSave.c_str(), 0);
}
#endif

CCLOG("initDownloadDir end");
}

void Update::reset(Ref *pSender) {
_showDownloadInfo->setString("");

// Remove downloaded files
#if (CC_TARGET_PLATFORM != CC_PLATFORM_WIN32)
std::string command = "rm -r ";
// Path may include space.
command += "\"" + _pathToSave + "\"";
system(command.c_str());
#else
std::string command = "rd /s /q ";
// Path may include space.
command += "\"" + _pathToSave + "\"";
system(command.c_str());
#endif

getAssetsManager()->deleteVersion();
initDownloadDir();
}

void Update::update(Ref *pSender) {
_showDownloadInfo->setString("");
getAssetsManager()->update();
}

作者信息

boke

boke

共发布了 3994 篇文章