Cocos2d-js 熱更新學習筆記

轉載至: http://blog.csdn.net/pt_xxj/article/details/68927705javascript

爲何還要再寫一篇關於cocos2d js熱更新的筆記,最單純的想法就是記錄心得,另外也是由於添加了一個記錄熱更新資源大小的小功能,故而想分享一下。java

在cocos2d js引擎中的js是做爲一種特殊的資源文件使用(腳本文件),那麼遊戲運行過程當中經過定製的資源管理器從服務器獲取新的js文件並從新加載運行,那麼也就實現了看似比較玄妙的熱更新功能。固然此資源管理器須要具有資源對比、資源下載、資源處理、資源覆蓋及資源加載等等的一系列功能,幸運的是cocos已經實現了這些功能,咱們須要作的就是學會配置使用而已。json

此熱更新基於cocos2d-js 3.6.1測試使用。設計模式

————————————————樸實無華分割線,以上內容不重要————————————————數組

cocos2d js的熱更新功能主要由jsb.AssetsManager實現,綁定了引擎底層的C++代碼AssetsManager類,具體的邏輯和實現均可以查看此類。具體的熱更新的是經過一個預先配置的project.manifest以及服務器端的project.manifest和version.manifest配合使用來實現的。服務器

一、熱更新系統實現

初始project.manifest:函數

{
    "packageUrl": "http://192.168.0.73:8080/projectName/std/",
    "remoteManifestUrl": "http://192.168.0.73:8080/projectName/std/project.manifest",
    "remoteVersionUrl": "http://192.168.0.73:8080/projectName/std/version.manifest",
    "version": "1.0.0",
    "groupVersions" : 
    { },
    "groupSizes" : 
    { },
    "engineVersion": "3.6.1",
    "assets" : 
    { },
    "searchPaths": 
    [ ] }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

具備兩個版本增量更新的服務器project.manifest:post

{
    "packageUrl": "http://192.168.0.73:8080/projectName/std/",
    "remoteManifestUrl": "http://192.168.0.73:8080/projectName/std/project.manifest",
    "remoteVersionUrl": "http://192.168.0.73:8080/projectName/std/version.manifest",
    "version": "1.1.2",
    "groupVersions" : 
    { "1":"1.1.1", "2":"1.1.2" },
    "groupSizes" : 
    { "1":115.0, "2":115.0 },
    "engineVersion": "3.6.1",
    "assets" : 
    { "update1": { "path":"update1.zip", "md5":"", "compressed" : true, "group":"1" }, "update2": { "path":"update2.zip", "md5":"", "compressed" : true, "group":"2" } },
    "searchPaths": 
    [ "update1/", "update2/" ] }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

具備兩個版本增量更新的服務器version.manifest:測試

{
    "packageUrl": "http://192.168.0.73:8080/projectName/std/",
    "remoteManifestUrl": "http://192.168.0.73:8080/projectName/std/project.manifest",
    "remoteVersionUrl": "http://192.168.0.73:8080/projectName/std/version.manifest",
    "version": "1.1.2",
    "groupVersions" : 
    { "1":"1.1.1", "2":"1.1.2" },
    "groupSizes" : 
    { "1":115.0, "2":115.0 } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

此熱更新配置文件我添加了groupSizes字段,用於展現更新資源文件大小,方便用戶玩家自主選擇更新時機。另外配置的時候按照渠道劃分的,這個是std是標準渠道的熱更新文件配置。至於其餘字段網路上已經有太多的講解,就再也不重複。ui

資源文件配置完畢,那麼就是具體的使用了,我使用了一個js的靜態類來實現熱更新的檢測以及更新過程,我看其餘人的實現通常都是直接強制更新,我的感受不太友好,因此個人熱更新實現是分步進行的。

hotfix-manager.js

/** * 熱更新管理器 */
var HotFixManager = HotFixManager || {

    // 更新資源管理器
    _mAssetsMgr:null,
    _mTryCount:0,

    // 更新基本配置
    _mManiFest:"",
    _mWritePath:"",
    _mTryTimes:1,

    // 更新過程進度回調函數
    _mUpdateCall:null,
    _mUpdatetarget:null,

    // 更新完成回調
    _mFinishCall:null,
    _mFinishtarget:null,

    // 更新已找到標識
    _mHadFind:false,

    /** * 熱更新系統初始化 * @param call 更新完成回調函數 * @param target 回調函數綁定節點 */
    init:function(call,target)
    {
        this._mFinishCall = call;
        this._mFinishtarget = target;

        // 這一部分的配置使用也能夠經過傳遞參數進行實現
        this._mManiFest = "res/static/project.manifest";
        this._mWritePath = jsb.fileUtils?jsb.fileUtils.getWritablePath()+"hotFix/":"./hotFix/";
        this._mTryTimes = 1;

        this._mAssetsMgr = new jsb.AssetsManager(this._mManiFest, this._mWritePath);
        this._mAssetsMgr.retain();

        cc.eventManager.addListener(new jsb.EventListenerAssetsManager(this._mAssetsMgr,this._eventListener.bind(this)),1);
    },
    _eventListener:function(event)
    {
        cc.log("assetsMgr:state = %s;event:id = %s,code = %s,message = %s",
                this._mAssetsMgr.getState(),event.getAssetId(),
                event.getEventCode(),JSON.stringify(event.getMessage()));


        switch(event.getEventCode())
        { 
        case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:    // 0
        case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:    // 1
        case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:       // 2
        case jsb.EventAssetsManager.ERROR_UPDATING:             // 7
        case jsb.EventAssetsManager.ERROR_DECOMPRESS:           // 10
            // 此處能夠另加一個this.onError(event.getEventCode())操做,不過我的感受不必,直接直接作失敗結束處理
            this.onFinish(false);
            break;


        case jsb.EventAssetsManager.UPDATE_FINISHED:            // 8
        case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:         // 4
            this.onFinish(true);
            break;


        // 資源下載完成,進行遊戲內資源更新(包括資源解壓等)
        case jsb.EventAssetsManager.ASSET_UPDATED:              // 6
            break;


            // 此處checkUpdate()和 update()都會觸發新版本發現操做(區別在於前者到此處任務已結束,後者會繼續後續資源下載操做)
        case jsb.EventAssetsManager.NEW_VERSION_FOUND:          // 3
            this._onFind();
            break;


        case jsb.EventAssetsManager.UPDATE_PROGRESSION:         // 5
            // 狀態值8表明熱更新資源下載中,添加此判斷是篩選更新下載進度
            if(this._mAssetsMgr.getState() == 8)
            {
                cc.log(event.getPercent());
                this._onUpdate(event.getPercent());
            }
            break;


        case jsb.EventAssetsManager.UPDATE_FAILED:              // 9
            this._mTryCount++;
            if (this._mTryCount < this._mTryTimes)
            {
                this._mAssetsMgr.downloadFailedAssets();
            }
            else
            {
                this._mTryCount = 0;
                this.onFinish(false);
            }
            break;
        default:break;
        }
    },
    /** * 檢測是否有熱更新資源 */
    checkUpdate:function()
    { 
        this._mAssetsMgr.checkUpdate();
    },
    _onFind:function()
    {
        // 系統在進行正式更新時,在下載project.manifest後仍舊會再次回調此處回調
        if(this._mHadFind)
        {
            return;
        }
        this._mHadFind = true;

        // 計算熱更新資源大小,需在cocos提供的文件配置基礎上自主增長groupSizes字段
        var updateSize = 0.0;
        try 
        {
            var vSize = 0.0;
            var pSize = 0.0;

            var versionString = jsb.fileUtils.getStringFromFile(this._mWritePath+"version.manifest");
            var projectString = jsb.fileUtils.getStringFromFile(this._mWritePath+"project.manifest");

            if(versionString)
            {
                var vJson = JSON.parse(versionString);
                for(var it in vJson.groupSizes)
                {
                    vSize += parseFloat(vJson.groupSizes[it]);
                }
            }

            if(projectString)
            {
                var pJson = JSON.parse(projectString);
                for(var it in pJson.groupSizes)
                {
                    pSize += parseFloat(pJson.groupSizes[it]);
                }
            }

            updateSize = vSize - pSize;
        } 
        catch (e) 
        {
            cc.log(e);
        }

        var layer = new HotFixFindLayer(updateSize);
        cc.director.getRunningScene().addChild(layer);
    },
    /** * 執行熱更新 */
    doUpdate:function()
    { 
        this._mAssetsMgr.update();
    },
    /** * 設置熱更新下載進度回調 * @param call * @param target */
    setUpdateCall:function(call,target)
    {
        this._mUpdateCall = call;
        this._mUpdatetarget = target;
    },
    _onUpdate:function(percent)
    {
        this._mUpdateCall&&this._mFinishtarget&&this._mFinishtarget.runAction(cc.callFunc(this._mUpdateCall, this._mFinishtarget, percent));
    },
    /** * 熱更新結束 * @param success bool更新是否成功 */
    onFinish:function(success)
    {
        this._mFinishCall&&this._mFinishtarget&&this._mFinishtarget.runAction(cc.callFunc(this._mFinishCall, this._mFinishtarget, success));
        this._mAssetsMgr.release();
        this._mHadFind = false;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
/** * 熱更新找到選擇對話框,主要考慮到資源比較大,又不是強制更新的狀況 */
var HotFixFindLayer = cc.Layer.extend({
    ctor:function(updateSize)
    {
        this._super();

        var popBoxBg = new cc.LayerColor(cc.color(0, 0, 0, 200),this.width*0.75, this.height*0.15);
        popBoxBg.setPosition(this.width*0.5-popBoxBg.width/2, this.height*0.5-popBoxBg.height/2);
        this.addChild(popBoxBg);


        var HotUpdateFindDesc = new ccui.Text("從遊戲服務器檢測到有"+updateSize+"M資源更新,是夠當即進行更新操做?", "Arial", 28);
        HotUpdateFindDesc.setContentSize(popBoxBg.width*0.85, popBoxBg.height*0.7);
        HotUpdateFindDesc.setPosition(popBoxBg.width*0.5, popBoxBg.height*0.5);
        HotUpdateFindDesc.ignoreContentAdaptWithSize(false);
        popBoxBg.addChild(HotUpdateFindDesc);


        var sureItem = new cc.MenuItemLabel(ccui.Text("當即更新", "Arial", 28),function(){

            HotFixManager.doUpdate();
            popBoxBg.removeAllChildren(true);

            var percentDesc = new ccui.Text("更新進度:0%", "Arial", 32);
            percentDesc.setAnchorPoint(0,0.5);
            percentDesc.setPosition(popBoxBg.width*0.25, popBoxBg.height*0.5);
            popBoxBg.addChild(percentDesc);

            HotFixManager.setUpdateCall(function(target,data){
                percentDesc.setString(cc.formatStr("更新進度:%s%",parseInt(data)));
                if(parseInt(data) == 100)
                {
                    this.removeFromParent(true);
                    HotFixManager.setUpdateCall();
                }
            },this);

        },this);

        var sureMenu = new cc.Menu(sureItem);
        sureMenu.setPosition(popBoxBg.width*0.25, popBoxBg.height*0.2)
        popBoxBg.addChild(sureMenu);


        var cancelItem = new cc.MenuItemLabel(ccui.Text("下次更新", "Arial", 28),function(){
            this.removeFromParent(true);
            HotFixManager.onFinish(false);
        },this);

        var cancelMenu = new cc.Menu(cancelItem);
        cancelMenu.setPosition(popBoxBg.width*0.75, popBoxBg.height*0.2)
        popBoxBg.addChild(cancelMenu);
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

其實這個更新操做的實現方式不是特別符合設計模式,在HotFixManager的_onFind函數尾部

var layer = new HotFixFindLayer(updateSize);
cc.director.getRunningScene().addChild(layer);
  • 1
  • 2

是不應直接實現界面顯示的,應該經過使用時本身定製一個彈窗界面而後再根據玩家選擇調用HotFixManager對應的函數進行操做,不過個人初始想法就是讓使用盡可能方便,因此我儘可能去掉了資源的使用,當成了一個系統內的控件進行了儘可能的封裝操做。

二、熱更新系統使用

新建一個scene使用便可hotfix-scene.js:

var HotFixScene = cc.Scene.extend({
    ctor:function() {
        this._super();

        var layer = new cc.Layer();
        this.addChild(layer);

        var loadPoster = new cc.Sprite("res/static/poster.png");
        loadPoster.setPosition(layer.width*0.5,layer.height*0.5)
        loadPoster.setScale(layer.height/loadPoster.height);
        layer.addChild(loadPoster);

        // 熱更新系統使用
        HotFixManager.init(this._updateFinish,this);
        HotFixManager.checkUpdate();
    },
    _updateFinish:function() {
        var delay_func = function() {
            // js文件列表文件,此文件在熱更新過程當中應該保持路徑不變
            // 由於此處是寫死的,不太好改變,除非你本身在熱更新配置一個字段,用於保存此路徑
            var jsFile = "src/dynamic/js-list.js";

            // 加載js文件列表
            cc.loader.loadJs(jsFile, function(){ 

                // 加載js文件列表中的js文件
                cc.loader.loadJs(jsList, function(){

                    // 跳轉到正式遊戲scene
                    cc.director.runScene(new GameScene());
                }); 
            });
        }

        // 此操做是爲了避開當前幀跳轉,否則界面會有卡頓感受
        this.scheduleOnce(delay_func, 0.05);
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

熱更新系統的使用是特別簡單的,只須要在main.js的cc.game.onStart函數執行

cc.director.runScene(new HotFixScene());
  • 1

就能夠了。固然這兩個文件你要配置在project.json中。

三、熱更新系統應用於已完成項目

其實我真正想作到的是做爲一個插件使用,對於已經完成的項目,要添加此熱更新插件特別簡單,只須要作幾件事情就能夠了:

一、將原有的project.json中的js文件列表寫進」src/dynamic/js-list.js」文件的var jsList=[]數組中(jsList名字對應於HotFixScene中的jsList)。

二、將上述兩個文件加入遊戲,並將其路徑配置到project.json。

三、配置本身的熱更新文件配置(包括搭建本身的服務器),修改HotFixManager.init()中的讀寫目錄(若跟個人目錄一致則不用改寫)。

四、修改熱更新結束後的scene跳轉爲已實現的文件跳轉。

// 跳轉到正式遊戲scene
cc.director.runScene(new GameScene());
  • 1
  • 2

五、測試運行。

————————————————樸實無華分割線,如下內容不重要————————————————

下面是兩張實際運行圖:(背景圖借用皇室戰爭的海報圖)

選擇界面: 
熱更新資源發現後彈出的操做選擇界面

資源更新中界面: 
資源下載中的進度顯示圖片

結束

爲何還要再寫一篇關於cocos2d js熱更新的筆記,最單純的想法就是記錄心得,另外也是由於添加了一個記錄熱更新資源大小的小功能,故而想分享一下。

在cocos2d js引擎中的js是做爲一種特殊的資源文件使用(腳本文件),那麼遊戲運行過程當中經過定製的資源管理器從服務器獲取新的js文件並從新加載運行,那麼也就實現了看似比較玄妙的熱更新功能。固然此資源管理器須要具有資源對比、資源下載、資源處理、資源覆蓋及資源加載等等的一系列功能,幸運的是cocos已經實現了這些功能,咱們須要作的就是學會配置使用而已。

此熱更新基於cocos2d-js 3.6.1測試使用。

————————————————樸實無華分割線,以上內容不重要————————————————

cocos2d js的熱更新功能主要由jsb.AssetsManager實現,綁定了引擎底層的C++代碼AssetsManager類,具體的邏輯和實現均可以查看此類。具體的熱更新的是經過一個預先配置的project.manifest以及服務器端的project.manifest和version.manifest配合使用來實現的。

一、熱更新系統實現

初始project.manifest:

{
    "packageUrl": "http://192.168.0.73:8080/projectName/std/",
    "remoteManifestUrl": "http://192.168.0.73:8080/projectName/std/project.manifest",
    "remoteVersionUrl": "http://192.168.0.73:8080/projectName/std/version.manifest",
    "version": "1.0.0",
    "groupVersions" : 
    { },
    "groupSizes" : 
    { },
    "engineVersion": "3.6.1",
    "assets" : 
    { },
    "searchPaths": 
    [ ] }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

具備兩個版本增量更新的服務器project.manifest:

{
    "packageUrl": "http://192.168.0.73:8080/projectName/std/",
    "remoteManifestUrl": "http://192.168.0.73:8080/projectName/std/project.manifest",
    "remoteVersionUrl": "http://192.168.0.73:8080/projectName/std/version.manifest",
    "version": "1.1.2",
    "groupVersions" : 
    { "1":"1.1.1", "2":"1.1.2" },
    "groupSizes" : 
    { "1":115.0, "2":115.0 },
    "engineVersion": "3.6.1",
    "assets" : 
    { "update1": { "path":"update1.zip", "md5":"", "compressed" : true, "group":"1" }, "update2": { "path":"update2.zip", "md5":"", "compressed" : true, "group":"2" } },
    "searchPaths": 
    [ "update1/", "update2/" ] }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

具備兩個版本增量更新的服務器version.manifest:

{
    "packageUrl": "http://192.168.0.73:8080/projectName/std/",
    "remoteManifestUrl": "http://192.168.0.73:8080/projectName/std/project.manifest",
    "remoteVersionUrl": "http://192.168.0.73:8080/projectName/std/version.manifest",
    "version": "1.1.2",
    "groupVersions" : 
    { "1":"1.1.1", "2":"1.1.2" },
    "groupSizes" : 
    { "1":115.0, "2":115.0 } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

此熱更新配置文件我添加了groupSizes字段,用於展現更新資源文件大小,方便用戶玩家自主選擇更新時機。另外配置的時候按照渠道劃分的,這個是std是標準渠道的熱更新文件配置。至於其餘字段網路上已經有太多的講解,就再也不重複。

資源文件配置完畢,那麼就是具體的使用了,我使用了一個js的靜態類來實現熱更新的檢測以及更新過程,我看其餘人的實現通常都是直接強制更新,我的感受不太友好,因此個人熱更新實現是分步進行的。

hotfix-manager.js

/** * 熱更新管理器 */
var HotFixManager = HotFixManager || {

    // 更新資源管理器
    _mAssetsMgr:null,
    _mTryCount:0,

    // 更新基本配置
    _mManiFest:"",
    _mWritePath:"",
    _mTryTimes:1,

    // 更新過程進度回調函數
    _mUpdateCall:null,
    _mUpdatetarget:null,

    // 更新完成回調
    _mFinishCall:null,
    _mFinishtarget:null,

    // 更新已找到標識
    _mHadFind:false,

    /** * 熱更新系統初始化 * @param call 更新完成回調函數 * @param target 回調函數綁定節點 */
    init:function(call,target)
    {
        this._mFinishCall = call;
        this._mFinishtarget = target;

        // 這一部分的配置使用也能夠經過傳遞參數進行實現
        this._mManiFest = "res/static/project.manifest";
        this._mWritePath = jsb.fileUtils?jsb.fileUtils.getWritablePath()+"hotFix/":"./hotFix/";
        this._mTryTimes = 1;

        this._mAssetsMgr = new jsb.AssetsManager(this._mManiFest, this._mWritePath);
        this._mAssetsMgr.retain();

        cc.eventManager.addListener(new jsb.EventListenerAssetsManager(this._mAssetsMgr,this._eventListener.bind(this)),1);
    },
    _eventListener:function(event)
    {
        cc.log("assetsMgr:state = %s;event:id = %s,code = %s,message = %s",
                this._mAssetsMgr.getState(),event.getAssetId(),
                event.getEventCode(),JSON.stringify(event.getMessage()));


        switch(event.getEventCode())
        { 
        case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:    // 0
        case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:    // 1
        case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:       // 2
        case jsb.EventAssetsManager.ERROR_UPDATING:             // 7
        case jsb.EventAssetsManager.ERROR_DECOMPRESS:           // 10
            // 此處能夠另加一個this.onError(event.getEventCode())操做,不過我的感受不必,直接直接作失敗結束處理
            this.onFinish(false);
            break;


        case jsb.EventAssetsManager.UPDATE_FINISHED:            // 8
        case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:         // 4
            this.onFinish(true);
            break;


        // 資源下載完成,進行遊戲內資源更新(包括資源解壓等)
        case jsb.EventAssetsManager.ASSET_UPDATED:              // 6
            break;


            // 此處checkUpdate()和 update()都會觸發新版本發現操做(區別在於前者到此處任務已結束,後者會繼續後續資源下載操做)
        case jsb.EventAssetsManager.NEW_VERSION_FOUND:          // 3
            this._onFind();
            break;


        case jsb.EventAssetsManager.UPDATE_PROGRESSION:         // 5
            // 狀態值8表明熱更新資源下載中,添加此判斷是篩選更新下載進度
            if(this._mAssetsMgr.getState() == 8)
            {
                cc.log(event.getPercent());
                this._onUpdate(event.getPercent());
            }
            break;


        case jsb.EventAssetsManager.UPDATE_FAILED:              // 9
            this._mTryCount++;
            if (this._mTryCount < this._mTryTimes)
            {
                this._mAssetsMgr.downloadFailedAssets();
            }
            else
            {
                this._mTryCount = 0;
                this.onFinish(false);
            }
            break;
        default:break;
        }
    },
    /** * 檢測是否有熱更新資源 */
    checkUpdate:function()
    { 
        this._mAssetsMgr.checkUpdate();
    },
    _onFind:function()
    {
        // 系統在進行正式更新時,在下載project.manifest後仍舊會再次回調此處回調
        if(this._mHadFind)
        {
            return;
        }
        this._mHadFind = true;

        // 計算熱更新資源大小,需在cocos提供的文件配置基礎上自主增長groupSizes字段
        var updateSize = 0.0;
        try 
        {
            var vSize = 0.0;
            var pSize = 0.0;

            var versionString = jsb.fileUtils.getStringFromFile(this._mWritePath+"version.manifest");
            var projectString = jsb.fileUtils.getStringFromFile(this._mWritePath+"project.manifest");

            if(versionString)
            {
                var vJson = JSON.parse(versionString);
                for(var it in vJson.groupSizes)
                {
                    vSize += parseFloat(vJson.groupSizes[it]);
                }
            }

            if(projectString)
            {
                var pJson = JSON.parse(projectString);
                for(var it in pJson.groupSizes)
                {
                    pSize += parseFloat(pJson.groupSizes[it]);
                }
            }

            updateSize = vSize - pSize;
        } 
        catch (e) 
        {
            cc.log(e);
        }

        var layer = new HotFixFindLayer(updateSize);
        cc.director.getRunningScene().addChild(layer);
    },
    /** * 執行熱更新 */
    doUpdate:function()
    { 
        this._mAssetsMgr.update();
    },
    /** * 設置熱更新下載進度回調 * @param call * @param target */
    setUpdateCall:function(call,target)
    {
        this._mUpdateCall = call;
        this._mUpdatetarget = target;
    },
    _onUpdate:function(percent)
    {
        this._mUpdateCall&&this._mFinishtarget&&this._mFinishtarget.runAction(cc.callFunc(this._mUpdateCall, this._mFinishtarget, percent));
    },
    /** * 熱更新結束 * @param success bool更新是否成功 */
    onFinish:function(success)
    {
        this._mFinishCall&&this._mFinishtarget&&this._mFinishtarget.runAction(cc.callFunc(this._mFinishCall, this._mFinishtarget, success));
        this._mAssetsMgr.release();
        this._mHadFind = false;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
/** * 熱更新找到選擇對話框,主要考慮到資源比較大,又不是強制更新的狀況 */
var HotFixFindLayer = cc.Layer.extend({
    ctor:function(updateSize)
    {
        this._super();

        var popBoxBg = new cc.LayerColor(cc.color(0, 0, 0, 200),this.width*0.75, this.height*0.15);
        popBoxBg.setPosition(this.width*0.5-popBoxBg.width/2, this.height*0.5-popBoxBg.height/2);
        this.addChild(popBoxBg);


        var HotUpdateFindDesc = new ccui.Text("從遊戲服務器檢測到有"+updateSize+"M資源更新,是夠當即進行更新操做?", "Arial", 28);
        HotUpdateFindDesc.setContentSize(popBoxBg.width*0.85, popBoxBg.height*0.7);
        HotUpdateFindDesc.setPosition(popBoxBg.width*0.5, popBoxBg.height*0.5);
        HotUpdateFindDesc.ignoreContentAdaptWithSize(false);
        popBoxBg.addChild(HotUpdateFindDesc);


        var sureItem = new cc.MenuItemLabel(ccui.Text("當即更新", "Arial", 28),function(){

            HotFixManager.doUpdate();
            popBoxBg.removeAllChildren(true);

            var percentDesc = new ccui.Text("更新進度:0%", "Arial", 32);
            percentDesc.setAnchorPoint(0,0.5);
            percentDesc.setPosition(popBoxBg.width*0.25, popBoxBg.height*0.5);
            popBoxBg.addChild(percentDesc);

            HotFixManager.setUpdateCall(function(target,data){
                percentDesc.setString(cc.formatStr("更新進度:%s%",parseInt(data)));
                if(parseInt(data) == 100)
                {
                    this.removeFromParent(true);
                    HotFixManager.setUpdateCall();
                }
            },this);

        },this);

        var sureMenu = new cc.Menu(sureItem);
        sureMenu.setPosition(popBoxBg.width*0.25, popBoxBg.height*0.2)
        popBoxBg.addChild(sureMenu);


        var cancelItem = new cc.MenuItemLabel(ccui.Text("下次更新", "Arial", 28),function(){
            this.removeFromParent(true);
            HotFixManager.onFinish(false);
        },this);

        var cancelMenu = new cc.Menu(cancelItem);
        cancelMenu.setPosition(popBoxBg.width*0.75, popBoxBg.height*0.2)
        popBoxBg.addChild(cancelMenu);
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

其實這個更新操做的實現方式不是特別符合設計模式,在HotFixManager的_onFind函數尾部

var layer = new HotFixFindLayer(updateSize);
cc.director.getRunningScene().addChild(layer);
  • 1
  • 2

是不應直接實現界面顯示的,應該經過使用時本身定製一個彈窗界面而後再根據玩家選擇調用HotFixManager對應的函數進行操做,不過個人初始想法就是讓使用盡可能方便,因此我儘可能去掉了資源的使用,當成了一個系統內的控件進行了儘可能的封裝操做。

二、熱更新系統使用

新建一個scene使用便可hotfix-scene.js:

var HotFixScene = cc.Scene.extend({
    ctor:function() {
        this._super();

        var layer = new cc.Layer();
        this.addChild(layer);

        var loadPoster = new cc.Sprite("res/static/poster.png");
        loadPoster.setPosition(layer.width*0.5,layer.height*0.5)
        loadPoster.setScale(layer.height/loadPoster.height);
        layer.addChild(loadPoster);

        // 熱更新系統使用
        HotFixManager.init(this._updateFinish,this);
        HotFixManager.checkUpdate();
    },
    _updateFinish:function() {
        var delay_func = function() {
            // js文件列表文件,此文件在熱更新過程當中應該保持路徑不變
            // 由於此處是寫死的,不太好改變,除非你本身在熱更新配置一個字段,用於保存此路徑
            var jsFile = "src/dynamic/js-list.js";

            // 加載js文件列表
            cc.loader.loadJs(jsFile, function(){ 

                // 加載js文件列表中的js文件
                cc.loader.loadJs(jsList, function(){

                    // 跳轉到正式遊戲scene
                    cc.director.runScene(new GameScene());
                }); 
            });
        }

        // 此操做是爲了避開當前幀跳轉,否則界面會有卡頓感受
        this.scheduleOnce(delay_func, 0.05);
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

熱更新系統的使用是特別簡單的,只須要在main.js的cc.game.onStart函數執行

cc.director.runScene(new HotFixScene());
  • 1

就能夠了。固然這兩個文件你要配置在project.json中。

三、熱更新系統應用於已完成項目

其實我真正想作到的是做爲一個插件使用,對於已經完成的項目,要添加此熱更新插件特別簡單,只須要作幾件事情就能夠了:

一、將原有的project.json中的js文件列表寫進」src/dynamic/js-list.js」文件的var jsList=[]數組中(jsList名字對應於HotFixScene中的jsList)。

二、將上述兩個文件加入遊戲,並將其路徑配置到project.json。

三、配置本身的熱更新文件配置(包括搭建本身的服務器),修改HotFixManager.init()中的讀寫目錄(若跟個人目錄一致則不用改寫)。

四、修改熱更新結束後的scene跳轉爲已實現的文件跳轉。

// 跳轉到正式遊戲scene
cc.director.runScene(new GameScene());
  • 1
  • 2

五、測試運行。

————————————————樸實無華分割線,如下內容不重要————————————————

下面是兩張實際運行圖:(背景圖借用皇室戰爭的海報圖)

選擇界面: 
熱更新資源發現後彈出的操做選擇界面

資源更新中界面: 
資源下載中的進度顯示圖片

結束

相關文章
相關標籤/搜索