转载至: http://blog.csdn.net/pt_xxj/article/details/68927705

为什么还要再写一篇关于cocos2d js热更新的笔记,最单纯的想法就是记录心得,另外也是因为添加了一个记录热更新资源大小的小功能,故而想分享一下。

在cocos2d js引擎中的js是作为一种特殊的资源文件使用(脚本文件),那么游戏运行过程中通过定制的资源管理器从服务器获取新的js文件并重新加载运行,那么也就实现了看似比较玄妙的热更新功能。当然此资源管理器需要具备资源对比、资源下载、资源处理、资源覆盖及资源加载等等的一系列功能,幸运的是cocos已经实现了这些功能,我们需要做的就是学会配置使用而已。

此热更新基于cocos2d-js 3.6.1测试使用。

————————————————朴实无华分割线,以上内容不重要————————————————

cocos2d js的热更新功能主要由jsb.AssetsManager实现,绑定了引擎底层的C++代码AssetsManager类,具体的逻辑和实现都可以查看此类。具体的热更新的是通过一个预先配置的project.manifest以及服务器端的project.manifest和version.manifest配合使用来实现的。

1、热更新系统实现

初始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对应的函数进行操作,不过我的初始想法就是让使用尽量方便,所以我尽量去掉了资源的使用,当成了一个系统内的控件进行了尽量的封装操作。

2、热更新系统使用

新建一个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中。

3、热更新系统应用于已完成项目

其实我真正想做到的是作为一个插件使用,对于已经完成的项目,要添加此热更新插件特别简单,只需要做几件事情就可以了:

1、将原有的project.json中的js文件列表写进”src/dynamic/js-list.js”文件的var jsList=[]数组中(jsList名字对应于HotFixScene中的jsList)。

2、将上述两个文件加入游戏,并将其路径配置到project.json。

3、配置自己的热更新文件配置(包括搭建自己的服务器),修改HotFixManager.init()中的读写目录(若跟我的目录一致则不用改写)。

4、修改热更新结束后的scene跳转为已实现的文件跳转。

// 跳转到正式游戏scene
cc.director.runScene(new GameScene());
  • 1
  • 2

5、测试运行。

————————————————朴实无华分割线,以下内容不重要————————————————

下面是两张实际运行图:(背景图借用皇室战争的海报图)

选择界面: 
热更新资源发现后弹出的操作选择界面

资源更新中界面: 
资源下载中的进度显示图片

结束

为什么还要再写一篇关于cocos2d js热更新的笔记,最单纯的想法就是记录心得,另外也是因为添加了一个记录热更新资源大小的小功能,故而想分享一下。

在cocos2d js引擎中的js是作为一种特殊的资源文件使用(脚本文件),那么游戏运行过程中通过定制的资源管理器从服务器获取新的js文件并重新加载运行,那么也就实现了看似比较玄妙的热更新功能。当然此资源管理器需要具备资源对比、资源下载、资源处理、资源覆盖及资源加载等等的一系列功能,幸运的是cocos已经实现了这些功能,我们需要做的就是学会配置使用而已。

此热更新基于cocos2d-js 3.6.1测试使用。

————————————————朴实无华分割线,以上内容不重要————————————————

cocos2d js的热更新功能主要由jsb.AssetsManager实现,绑定了引擎底层的C++代码AssetsManager类,具体的逻辑和实现都可以查看此类。具体的热更新的是通过一个预先配置的project.manifest以及服务器端的project.manifest和version.manifest配合使用来实现的。

1、热更新系统实现

初始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对应的函数进行操作,不过我的初始想法就是让使用尽量方便,所以我尽量去掉了资源的使用,当成了一个系统内的控件进行了尽量的封装操作。

2、热更新系统使用

新建一个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中。

3、热更新系统应用于已完成项目

其实我真正想做到的是作为一个插件使用,对于已经完成的项目,要添加此热更新插件特别简单,只需要做几件事情就可以了:

1、将原有的project.json中的js文件列表写进”src/dynamic/js-list.js”文件的var jsList=[]数组中(jsList名字对应于HotFixScene中的jsList)。

2、将上述两个文件加入游戏,并将其路径配置到project.json。

3、配置自己的热更新文件配置(包括搭建自己的服务器),修改HotFixManager.init()中的读写目录(若跟我的目录一致则不用改写)。

4、修改热更新结束后的scene跳转为已实现的文件跳转。

// 跳转到正式游戏scene
cc.director.runScene(new GameScene());
  • 1
  • 2

5、测试运行。

————————————————朴实无华分割线,以下内容不重要————————————————

下面是两张实际运行图:(背景图借用皇室战争的海报图)

选择界面: 
热更新资源发现后弹出的操作选择界面

资源更新中界面: 
资源下载中的进度显示图片

结束