cocos2d-x+LUA 游戏热更新

cocos2d-x+LUA开发 代码热更新

注意事项:游戏全部逻辑均为lua实现(可以通过热更新更新游戏全部内容),lua所需底层接口为C++及平台实现(java/OC/win32)。

1.LUA脚本目录设置,我把脚本文件夹命名为Luascript
我在app包里、apk包里都打包了一份Luascript文件夹。

appdelegate里面设置LUA文件搜索路径,优先搜索下载目录(ios放置在Library/Caches下,android在手机存储里面创建一个目录例如:渠道_游戏名/Caches)

程序启动时设置lua的搜索路劲:
a.设置LUA优先搜索路径为上述设置的Caches路径下的Luascript文件夹,其次再搜索app、apk里面事先打包进去的Luascript文件夹。
b.

//kt_开头的方法为平台方法,需要分平台实现

    void initLua() {
        
        CCLuaEngine* pEngine = CCLuaEngine::defaultEngine();
        CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine);
        
        //library cache path
        //分平台实现不同平台下的library目录路径的获取
        std::string cachePath = kt_library_path() + "/Caches/";
        
        //app、apk path
        std::string bundleLuaPath = CCFileUtils::sharedFileUtils()->fullPathFromRelativePath(LUA_DIR_NAME);
        
        std::string cacheLuaPath = cachePath + "LuaScript";
        std::string cacheXMLPath = cachePath + "XML";
        
        //check
        std::string oldVersion = CCUserDefault::sharedUserDefault()->getStringForKey("AppVersion");
        //分平台实现了getVersion方法以获取不同平台下的bundle 版本号
        std::string newVersion = KTChannel_Help::sharedInstance()->getVersion();
        
        if (oldVersion.empty()) {
            CCUserDefault::sharedUserDefault()->setStringForKey("AppVersion", newVersion);
            CCUserDefault::sharedUserDefault()->flush();
            kt_remove_dir(cacheLuaPath.c_str());
            kt_remove_dir(cacheXMLPath.c_str());
        }
        else {
            if (kt_compareVersion(oldVersion, newVersion)) {
                //newVersion > oldVersion
                CCUserDefault::sharedUserDefault()->setStringForKey("AppVersion", newVersion);
                CCUserDefault::sharedUserDefault()->flush();
                kt_remove_dir(cacheLuaPath.c_str());
                kt_remove_dir(cacheXMLPath.c_str());
            }
            else {
            }
        }
        
        pEngine->addSearchPath(cacheLuaPath.c_str(), bundleLuaPath.c_str());
        
        std::string tempPath = cacheLuaPath + "/version.lua";
        
        if (kt_isFileExist(tempPath.c_str())) {
            luaFilePathPrefix = cacheLuaPath + "/";
        }
        else {
            luaFilePathPrefix = bundleLuaPath + "/";
        }
    }

void kt_setLuaSearchPath(std::string firstPath, std::string secondPath) {
    CCLuaEngine* pEngine = CCLuaEngine::defaultEngine();
    pEngine->addSearchPath(firstPath.c_str(), secondPath.c_str());
}
//修改了一下引擎里面的搜索路劲代码
void CCLuaEngine::addSearchPath(const char* path1, const char* path2)
{
    lua_getglobal(m_state, "package");                                  /* stack: package */
    lua_getfield(m_state, -1, "path");                /* get package.path, stack: package path */
    const char* cur_path =  lua_tostring(m_state, -1);
    lua_pop(m_state, 1);                                                /* stack: package */
    lua_pushfstring(m_state, "%s;%s/?.lua;%s/?.lua", cur_path, path1, path2);            /* stack: package newpath */
    lua_setfield(m_state, -2, "path");          /* package.path = newpath, stack: package */
    lua_pop(m_state, 1);                                                /* stack: - */
}

```

获取游戏在userdefault.xml里面存储的版本号AppVersion,若此值为空,那么用户第一次下载次游戏,此时为了安全起见删除Caches里面的Luascript文件夹。
若此值非空,则和程序包的bundle版本号比较BundleVersion, 若AppVersion 大于BundleVersion,则不用处理,
若AppVersion小于BundleVersion则需要删除Caches下的Luascript文件夹来保证本地版本的最新.
此处这样做的目的是为了解决用户在1.0版本时热更新过Lua,之后用户在商店里更新了2.0版本的游戏包(只会更新.app Library等其余三个目录不会变),此时2.0版本里面的lua版本号可能低于
本地lua版本号,虽然游戏版本号比较新但是游戏包中事先打包的lua版本号比较旧。

c.设定了搜索路径后,我们需要读取本地该渠道的lua的版本号来对比服务端的该渠道(例如appstore)lua版本号,以此来决定是否需要下载更新包,需要的话就下载加密的zip包到本地解压到Caches里来覆盖之前的Luascript,当然我们程序里面的所有lua文件都是RSA加密的,放心吧!

本地的一份版本配置文件

--INT.lua
require "Language/language_zh-Hans"
g_platform = {
    id                 = 1,    --平台id
    name             = "INT",
    type                     = "-a",
    typeID            = 2,

    flag                    = "XXXXXX",
    agent             = "XXXXXX",


    autoUpdate        = true,
    
    updatePath        = "/Update/",
    version         = {     = 10001,
                         = 3,
                         = 0,
                         = 6,
                      },
    
    isAutoLogin        = false,
    isSingleUser     = true,                 --是否单账号
    isHaveGuest     = false,                 --是否支持游客登陆
    isHaveLoginSDK     = false,         --是否介入第三方SDK

    serverHost        = "http://www.XXXX.com",
    fcm                = 0,
    game            = "XXXX",

    ftpPath            = "http://10.0.9.14",
    battlePath        = ":9110/battle_report/query?report_id=",
    
    gameURL            = "http://k.XXXXX.com",
    giftUrl         = "http://www.XXXXX.com/gamecard/show/id/2",

    recharge        = function( ) 
                        -- KTTipLayer:sharedTip():showText("内网测试服不支持充值!", ccc3(255, 255, 0))
                        require "Layer/IAPLayer"
                        IAPLayer:show()
                      end,
}
```



有些小伙伴不方便搭建PHP服务端接口,那么你可以做一个版本文件放置在FTP上.
   
update.lua文件内容至少需要两行:
 
第一行为该渠道最新版本的lua的版本号,第二行为该最新lua版本号所兼容的最低客户端版本号(此处兼容可以控制客户端强制更新,例如程序员犯了致命错误BUG,急需修复C++/java/OC代码)
第二行的版本号的作用很关键

2.最后就需要实现一个下载实现以及更新等待界面.

3.更新完成后delete LUA虚拟机然后再重新运行第一个scene,就可以在线更新后进入游戏、、
2赞

支持一下 :2:

:10:好长啊。。。。。。。。。。

没代码啊,包含了一堆lua程序

感觉有点复杂

好东西,多谢楼主分享

哥,你的文章2段怎么了?最后就需要实现一个下载实现以及更新等待界面??

楼主上下示例源码,让我们学习学习吧。。。感谢

:2::2::2:

恩恩,不错,支持!

请教楼主,更新完成后如何delete LUA虚拟机

LUA_API void lua_close (lua_State *L) ;

这就是lua关闭虚拟机的方法,在2.2.0版本里面的CCLuaEngine是个单例,你销毁这个单例就可以了,CCLuaEngine的析构方法里面会调用 lua_close()方法。
CCLuaEngine又是CCScriptEngineManager得成员。。。。。
所以你只需要销毁CCScriptEngineManager即可,然后重新运行你的第一个Scene.切换Scene的操作写在一个update方法里面,
检测到m_isRestart变量为true后切换场景(注意此处这样切换场景是为了在下一帧里面执行切换操作),切换后将m_isRestart再赋值位false.
更新完成后设置appdelete里面的一个成员变量m_isRestart为true即可。

首先谢谢兄弟的耐心解答,不过我还是有些疑问,ScriptEngineManager::removeScriptEngine(void) 可以达到 删除 lua 虚拟机的功能,然后从先 注册 lua module ,
auto engine = LuaEngine::getInstance();
ScriptEngineManager::getInstance()->setScriptEngine(engine);
lua_State* L = engine->getLuaStack()->getLuaState();
lua_module_register(L);
register_all_packages();

但是如果更新模块是纯lua 实现的, 如何做到删除虚拟机,从先注册 lua module 的功能。

我之前写得游戏所有模块都是Lua写得包括更新模块、、、、、、

    void initLua() {
        
 //注意只要这两句就好了。。。。。
        CCLuaEngine* pEngine = CCLuaEngine::defaultEngine();
        CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine);
。。。。。。
}

void KTChannel_Help::update(float delta) {
    if (m_isNeedRestartGame) {
        m_isNeedRestartGame = false;
        CCScene *scene = CCDirector::sharedDirector()->getRunningScene();
        if (scene) {
            scene->removeAllChildrenWithCleanup(true);
        }
        
        CCScriptEngineManager::purgeSharedManager();
        
        initLua();
    }
}



```


你得请我吃饭、、、、
1赞

受教了兄弟,留个联系方式,有机会请你吃饭,我qq58064747

支持
紫薯补丁

mark一下:2:

:2: :2: :2: :2: :2: :2:

:2: :2: :2: :2: :2: :2:

码一手。 感谢分享。