1. 程式人生 > >cocos2d-js熱更新

cocos2d-js熱更新

1. 熱更新基本思路

得到cocoachina論壇上fysp和akira_cn的幫助,理清了遊戲熱更新的思路:

  • 執行AssetsManager後,搜尋路徑增加了jsb.fileUtils.getWritablePath()目錄,並且是優先搜尋;
  • 需要熱更新js不放在project.json中定義,等AssetsManager更新完了,用cc.loader.load動態載入;
  • 所以在jsb.fileUtils.getWritablePath()目錄下載的資源和js檔案,與專案目錄保持一致,那麼優先載入新下載的資源和js檔案,再進入遊戲,從而實現熱更新。

2. AssetsManager

cocos2d-js 3.0 rc0對AssetsManager功能進行了完善增強,支援多執行緒下載、斷點續傳、檔案壓縮、更好的進度資訊以及錯誤重試機制,實現遊戲資原始檔和指令碼檔案的熱更新變的更加方便。

cocos new MyGame -l js -d /directory/to/project方式新建一個測試專案,參考sample寫的src/AssetsManager.js:

var __failCount = 0;

var AssetsManagerLoaderScene = cc.Scene.extend({
    _am:null,
    _progress:null,
    _percent:0,
    _percentByFile:0,
    run:function(){
        if (!cc.sys.isNative) {
            this.loadGame
(); return; } var layer = new cc.Layer(); this.addChild(layer); this._progress = new cc.LabelTTF.create("0%", "Arial", 12); this._progress.x = cc.winSize.width / 2; this._progress.y = cc.winSize.height / 2 + 50; layer.addChild(this._progress
); // android: /data/data/com.huanle.magic/files/ var storagePath = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : "./"); this._am = new jsb.AssetsManager("res/project.manifest", storagePath); this._am.retain(); if (!this._am.getLocalManifest().isLoaded()) { cc.log("Fail to update assets, step skipped."); this.loadGame(); } else { var that = this; var listener = new cc.EventListenerAssetsManager(this._am, function(event) { switch (event.getEventCode()){ case cc.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: cc.log("No local manifest file found, skip assets update."); that.loadGame(); break; case cc.EventAssetsManager.UPDATE_PROGRESSION: that._percent = event.getPercent(); that._percentByFile = event.getPercentByFile(); cc.log(that._percent + "%"); var msg = event.getMessage(); if (msg) { cc.log(msg); } break; case cc.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: case cc.EventAssetsManager.ERROR_PARSE_MANIFEST: cc.log("Fail to download manifest file, update skipped."); that.loadGame(); break; case cc.EventAssetsManager.ALREADY_UP_TO_DATE: case cc.EventAssetsManager.UPDATE_FINISHED: cc.log("Update finished."); that.loadGame(); break; case cc.EventAssetsManager.UPDATE_FAILED: cc.log("Update failed. " + event.getMessage()); __failCount ++; if (__failCount < 5) { that._am.downloadFailedAssets(); } else { cc.log("Reach maximum fail count, exit update process"); __failCount = 0; that.loadGame(); } break; case cc.EventAssetsManager.ERROR_UPDATING: cc.log("Asset update error: " + event.getAssetId() + ", " + event.getMessage()); that.loadGame(); break; case cc.EventAssetsManager.ERROR_DECOMPRESS: cc.log(event.getMessage()); that.loadGame(); break; default: break; } }); cc.eventManager.addListener(listener, 1); this._am.update(); cc.director.runScene(this); } this.schedule(this.updateProgress, 0.5); }, loadGame:function(){ cc.loader.loadJs(["src/files.js"], function(err){ cc.loader.loadJs(jsFiles, function(err){ cc.director.runScene(new HelloWorldScene()); }); }); }, updateProgress:function(dt){ this._progress.string = "" + this._percent; }, onExit:function(){ cc.log("AssetsManager::onExit"); this._am.release(); this._super(); } });

修改專案目錄下的main.js:

cc.game.onStart = function(){
    cc.view.setDesignResolutionSize(800, 450, cc.ResolutionPolicy.SHOW_ALL);
        cc.view.resizeWithBrowserSize(true);

        var scene = new AssetsManagerLoaderScene();
        scene.run();
};
cc.game.run();

修改專案目錄下的project.json:

{
    "project_type": "javascript",

    "debugMode" : 1,
    "showFPS" : true,
    "frameRate" : 60,
    "id" : "gameCanvas",
    "renderMode" : 0,
    "engineDir":"frameworks/cocos2d-html5",

    "modules" : ["cocos2d", "extensions"],

    "jsList" : [
        "src/AssetsManager.js"
    ]
}

就留一個AssetsManager.js,其他的js都通過它來載入。

增加一個src/files.js,需要動態載入的js檔案都寫在jsFiles這個數組裡,這樣js檔案有增加變化,這個files.js一併更新,方便動態載入:

var jsFiles = [
    "src/app.js",
    "src/resource.js"
];

專案res目錄增加一個project.manifest檔案,AssetsManager.js裡會用到:

{
    "packageUrl" : "http://10.0.128.219/res",
    "remoteManifestUrl" : "http://10.0.128.219/res/project.manifest",
    "remoteVersionUrl" : "http://10.0.128.219/res/version.manifest",
    "version" : "1.0.0",
    "groupVersions" : {
        "1" : "1.0.0"
    },
    "engineVersion" : "3.0 rc0",
    "searchPaths" : [
    ]
}

這裡主要配置服務端資源下載地址,具體欄位說明,在下面服務端配置裡說明。然後用cocos compile -p android編譯打包成一個apk安裝包,等配置好服務端更新資源安裝測試。

3. 服務端配置

需要建一個WEB伺服器做下載用,在其WEB目錄http://10.0.128.219/res(我的測試機),增加version.manifest檔案:

{
    "packageUrl" : "http://10.0.128.219/res",
    "remoteManifestUrl" : "http://10.0.128.219/res/project.manifest",
    "remoteVersionUrl" : "http://10.0.128.219/res/version.manifest",
    "version" : "1.0.0",
    "groupVersions" : {
        "1" : "1.0.1"
    },
    "engineVersion" : "3.0 rc0"
}

測試發現,AssetsManager首先會下載version.manifest檔案,如果有更新的版本,那麼才會去下載project.manifest,然後下載其中描述的資原始檔。

project.manifest如下:

{
    "packageUrl" : "http://10.0.128.219/res",
    "remoteManifestUrl" : "http://10.0.128.219/res/project.manifest",
    "remoteVersionUrl" : "http://10.0.128.219/res/version.manifest",
    "version" : "1.0.0",
    "groupVersions" : {
        "1" : "1.0.1"
    },
    "engineVersion" : "3.0 rc0",
    "assets" : {
        "update1" : {
            "path" : "src/app.zip",
            "md5" : "f6bf54e5a0d42c963cc5ae81bf9dc6c6",
            "compressed" : true,
            "group" : "1"
        }
    },
    "searchPaths" : [
    ]
}

寫法和官方文件裡不太一樣,特別是有個groupVersions欄位,這個欄位來自fysp在cocoachina論壇回答其他網友問題寫的示例,測試發現用來做增量更新很方便,後面再說明。其他欄位的說明官方文件已經很詳細了。

由於客戶端本地project.manifest裡groupVersions的版本資訊比伺服器端的低,所以AssetsManager會下載http://10.0.128.219/res/src/app.zip到手機的/data/data/org.cocos2dx.hellojavascript/files/src/app.zip,並且會自動解壓,但不會刪除壓縮包本身。

建議用root過的android手機測試,否則/data/data是沒有許可權檢視。執行客戶端測試程式後用adb連線檢視:

e:\>adb shell
[email protected]:/ $ su
su
[email protected]:/ # ls -l /data/data/org.cocos2dx.hellojavascript/files/src/
ls -l /data/data/org.cocos2dx.hellojavascript/files/src/
-rw-rw-rw- app_65   app_65       2228 2014-07-08 14:23 app.js
-rw-rw-rw- app_65   app_65       1552 2014-07-08 14:23 app.zip
[email protected]:/ # ls -l /data/data/org.cocos2dx.hellojavascript/files/
ls -l /data/data/org.cocos2dx.hellojavascript/files/
-rw-rw-rw- app_65   app_65        553 2014-07-08 14:23 project.manifest
drwxrwxrwx app_65   app_65            2014-07-08 14:23 src
-rw-rw-rw- app_65   app_65        307 2014-07-08 14:23 version.manifest

用firefox除錯連上手機,發現app.js資源地址是/data/data/org.cocos2dx.hellojavascript/files/src/app.js,而不是assets/src/app.js,實現了熱更新:

debug

4. 增量更新

修改服務端version.manifest:

{
    "packageUrl" : "http://10.0.128.219/res",
    "remoteManifestUrl" : "http://10.0.128.219/res/project.manifest",
    "remoteVersionUrl" : "http://10.0.128.219/res/version.manifest",
    "version" : "1.0.0",
    "groupVersions" : {
        "1" : "1.0.1",
        "2" : "1.0.2"
    },
    "engineVersion" : "3.0 rc0"
}

修改服務端project.manifest:

{
    "packageUrl" : "http://10.0.128.219/res",
    "remoteManifestUrl" : "http://10.0.128.219/res/project.manifest",
    "remoteVersionUrl" : "http://10.0.128.219/res/version.manifest",
    "version" : "1.0.0",
    "groupVersions" : {
        "1" : "1.0.1",
        "2" : "1.0.2"
    },
    "engineVersion" : "3.0 rc0",
    "assets" : {
        "update1" : {
            "path" : "src/app.zip",
            "md5" : "f6bf54e5a0d42c963cc5ae81bf9dc6c6",
            "compressed" : true,
            "group" : "1"
        },
        "update2" : {
            "path" : "src/config.zip",
            "md5" : "5d59789090e4143166430b2cf7b313ff",
            "compressed" : true,
            "group" : "2"
        }
    },
    "searchPaths" : [
    ]
}

這時在android客戶端測試,已經更新到update1的,只會下載update2的更新,而沒有更新過的,會把update1和update2都下載下來。