[Cocos2d-x]Lua 資源熱更新


什么是熱更新

所謂的熱更新,指的是客戶端的更新。

大致的流程是,客戶端在啟動后訪問更新的URL接口,根據更新接口的反饋,下載更新資源,然后使用新的資源啟動客戶端,或者直接使用新資源不重啟客戶端。

 

熱更新代碼使用到的場景

  • 情人節快到了,你想要組織一個游戲內活動,錯過時機肯定是你最不想要看到的結果。
  • 當你發現一個嚴重的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() {};
};

 

那么接下來,我們使用AssetsManager來創建一個自動更新的類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", "Arail", 20), CC_CALLBACK_1(Update::reset, this));
    auto itemLabel2 = MenuItemLabel::create(
                                            Label::createWithSystemFont("Update", "Arail", 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();
}

 可以點擊下載下來網絡資源壓縮包,看下壓縮包內包含的內容。

 可以點擊下載下來源代碼壓縮包,對應一起的。

 

大家最好可以在本地搭建一個apache服務器,做一下練習。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM