Cocos2d-Js热更新(最完整版本,包括自己做的过程中遇到的坑都在里面)

最近主要进行游戏脚本化相关工作,脚本化的目的就是为了热更新,所以就写个demo研究下热更新。


cocos版本: 3.12


###1、基本思路

  • cocos的热更新主要采用其自带的AssetsManager,执行AssetsManager后,搜索路径增加了jsb.fileUtils.getWritablePath()目录,并且搜索级别最优;
  • 需要热更新js不放在project.json中定义,等AssetsManager更新完了,用cc.loader.loadJs动态加载;
  • 所以在jsb.fileUtils.getWritablePath()目录下载的资源和js文件,与项目目录保持一致,那么优先加载新下载的资源和js文件,再进入游戏,从而实现热更新的目的。

###2、特性
此为官方说明的特性~~~~

  • 多线程并行下载支持
  • 两层进度统计信息:文件级以及字节级
  • Zip压缩文件支持
  • 断点续传
  • 详细的错误报告
  • 文件下载失败重试支持

###3、我的流程

  • 新建空的cocos2d-js工程并进行相应的修改用作demo
    在res资源目录下,新建project.manifest文件,填入初始信息,内容如下:
{
    "packageUrl" : "http://192.168.3.139:8080/hotUpdateService/res/",
    "remoteManifestUrl" : "http://192.168.3.139:8080/hotUpdateService/res/project.manifest",
    "remoteVersionUrl" : "http://192.168.3.139:8080/hotUpdateService/res/version.manifest",
    "version" : "1.0.0",
    "engineVersion" : "3.12",
    "groupVersions" : {
        "1" : "1.0.0"
    },
    "assets" : {

    },

    "searchPaths" : [
    ]
}

其中地址为我自己的测试地址,各位看官自己进行相应的替换(这里有个坑,详见文末

src目录下新建jsList.js文件,需要动态加载的js文件都写在jsFiles这个数组里,这样js文件有增加变化,这个files.js一并更新,方便动态加载,内容如下:

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

src目录下新建assetsManagerScene.js文件,内容如下:

/**
 * Created by MartinYing on 2016/12/28.
 */
var failCount = 0;
var maxFailCount = 1;   //最大错误重试次数

/**
 * 自动更新js和资源
 */
var AssetsManagerLoaderScene = cc.Scene.extend({
    _am:null,
    _progress:null,
    _percent:0,
    run:function(){
        cc.log("enter  run   function ..... ");
        if (!cc.sys.isNative) {
            this.loadGame();
            return;
        }

        var layer = new cc.Layer();
        this.addChild(layer);
        this._progress = new cc.LabelTTF.create("update 0%", "Arial", 38);
        this._progress.x = cc.winSize.width / 2;
        this._progress.y = cc.winSize.height / 2 + 50;
        layer.addChild(this._progress);

        var storagePath = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : "./");
        cc.log("storagePath is " + storagePath);
        this._am = new jsb.AssetsManager("res/project.manifest", storagePath);
        this._am.retain();

        if (!this._am.getLocalManifest().isLoaded())
        //if (true)
        {
            cc.log("Fail to update assets, step skipped.");
            this.loadGame();
        }
        else
        {
            var that = this;
            var listener = new jsb.EventListenerAssetsManager(this._am, function(event) {
                switch (event.getEventCode()){
                    case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                        cc.log("enter  ERROR_NO_LOCAL_MANIFEST ..... ");
                        cc.log("No local manifest file found, skip assets update.");
                        that.loadGame();
                        break;
                    case jsb.EventAssetsManager.UPDATE_PROGRESSION:
                        cc.log("enter  UPDATE_PROGRESSION ..... ");
                        that._percent = event.getPercent();
                        cc.log(that._percent + "%");
                        var msg = event.getMessage();
                        if (msg) {
                            cc.log(msg);
                        }
                        break;
                    case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
                        cc.log("enter  ERROR_DOWNLOAD_MANIFEST ..... ");
                        cc.log("Fail to download manifest file, update skipped.");
                        that.loadGame();
                        break;
                    case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                        cc.log("enter  ERROR_PARSE_MANIFEST ..... ");
                        cc.log("Fail to download manifest file, update skipped.");
                        that.loadGame();
                        break;
                    case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                        cc.log("enter  ALREADY_UP_TO_DATE ..... ");
                        cc.log("ALREADY_UP_TO_DATE.");
                        that.loadGame();
                        break;
                    case jsb.EventAssetsManager.UPDATE_FINISHED:
                        cc.log("enter  UPDATE_FINISHED ..... ");
                        cc.log("Update finished.");
                        that.loadGame();
                        break;
                    case jsb.EventAssetsManager.UPDATE_FAILED:
                        cc.log("enter  UPDATE_FAILED ..... ");
                        cc.log("Update failed. " + event.getMessage());
                        failCount++;
                        if (failCount < maxFailCount)
                        {
                            that._am.downloadFailedAssets();
                        }
                        else
                        {
                            cc.log("Reach maximum fail count, exit update process");
                            failCount = 0;
                            that.loadGame();
                        }
                        break;
                    case jsb.EventAssetsManager.ERROR_UPDATING:
                        cc.log("enter  ERROR_UPDATING ..... ");
                        cc.log("Asset update error: " + event.getAssetId() + ", " + event.getMessage());
                        that.loadGame();
                        break;
                    case jsb.EventAssetsManager.ERROR_DECOMPRESS:
                        cc.log("enter  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.log("enter  loadGame   function ..... ");
        //jsList是jsList.js的变量,记录全部js。
        cc.loader.loadJs(["src/jsList.js"], function(){
            cc.loader.loadJs(jsList, function(){
                cc.director.runScene(new HelloWorldScene());
            });
        });
    },

    updateProgress:function(dt){
        cc.log("enter  updateProgress   function ..... ");
        this._progress.string = "update " + this._percent + "%";
    },

    onExit:function(){
        cc.log("AssetsManager::onExit");

        this._am.release();
        this._super();
    }
});

这里用到了上一步创建的res/project.manifest文件,目的是用于进行版本对比。
因为下载的文件是保存在可写目录下,加载的顺序发生了变化,所以需要修改根目录下的main.js和“project.json”两个文件。

main.js :

cc.game.onStart = function(){
    if(!cc.sys.isNative && document.getElementById("cocosLoading")) //If referenced loading.js, please remove it
        document.body.removeChild(document.getElementById("cocosLoading"));

    // Pass true to enable retina display, on Android disabled by default to improve performance
    cc.view.enableRetina(cc.sys.os === cc.sys.OS_IOS ? true : false);

    // Adjust viewport meta
    cc.view.adjustViewPort(true);

    // Uncomment the following line to set a fixed orientation for your game
    // cc.view.setOrientation(cc.ORIENTATION_PORTRAIT);

    // Setup the resolution policy and design resolution size
    cc.view.setDesignResolutionSize(960, 640, cc.ResolutionPolicy.SHOW_ALL);

    // The game will be resized when browser size change
    cc.view.resizeWithBrowserSize(true);

    var scene = new AssetsManagerLoaderScene();
    scene.run()

    // //load resources
    // cc.LoaderScene.preload(g_resources, function () {
    //     cc.director.runScene(new HelloWorldScene());
    // }, this);
};
cc.game.run();

project.json :

{
    "project_type": "javascript",

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

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

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

到这一步客户端基本配置已经完成,这个时候客户端是可以运行的。

  • 新建web工程,用作测试服务器(我这里使用Myeclipse新建的web工程,搭建web服务器这里不做赘述)
    在WebRoot目录下,添加res文件夹,在其内部分别建立res和src用于对应cocos工程的资源文件夹和源文件夹目录,拷贝app.js到src目录下,随便修改一点东西,作为更新内容。
    res文件夹目录下添加配置文件version.manifest和project.manifest文件
    version.manifest :
{ 
    "packageUrl" : "http://192.168.3.139:8080/hotUpdateService/res/", 
    "remoteManifestUrl" : "http://192.168.3.139:8080/hotUpdateService/res/project.manifest", 
    "remoteVersionUrl" : "http://192.168.3.139:8080/hotUpdateService/res/version.manifest", 
    "version" : "1.0.0",
    "groupVersions" : {
        "1" : "1.0.1"
    }, 
    "engineVersion" : "3.12" 
}

project.manifest :

{ 
    "packageUrl" : "http://192.168.3.139:8080/hotUpdateService/res/", 
    "remoteManifestUrl" : "http://192.168.3.139:8080/hotUpdateService/res/project.manifest", 
    "remoteVersionUrl" : "http://192.168.3.139:8080/hotUpdateService/res/version.manifest", 
    "version" : "1.0.0", 
    "groupVersions" : {
        "1" : "1.0.1"
    },
    "engineVersion" : "3.12", 
    "assets" : { 
    	"update1" : {
        	"path" : "src/app.zip",
           	"md5" : "D7698389FD1CA121DCD896035D67687C", 
            "compressed" : true ,
            "group" : "1" 
        }
    },

    "searchPaths" : [ 
    ] 
}

packageUrl : 远程资源的下载根路径。

remoteVersionUrl : 远程版本文件的路径,用来判断服务器端是否有新版本的资源。

remoteManifestUrl : 远程配置文件的路径,包含版本信息以及所有资源信息。

version : 配置文件对应的版本。

groupVersions : 是新增的功能字段,用于做增量更新很方便。

engineVersion : 配置文件对应的引擎版本。

assets : 所有资源信息。

key : 升级名称

path : 键代表资源的相对路径(相对于packageUrl)。

md5 : md5值代表资源文件的版本信息。

compressed : [可选项] 如果值为true,文件被下载后会自动被解压,目前仅支持zip压缩格式。

searchPaths : 需要添加到Cocos2d引擎中的搜索路径列表。

通过学习底层逻辑,发现更新的逻辑顺序是这样的:

先通过本地的version.manifest和服务端的version.manifest比较,如果本地没有version.manifest,则会先进行下载,如果本地version低于服务端,那么就会再去获取project.manifest。

如果version相同,那么会比较groupVersions。

如果本地没有下载过groupVersions中的任何更新,那么会依次下载升级包。

如果本地下载过1.0.1版本的升级包,那么就会跳过1.0.1下载属于1.0.2版本的升级内容。

如果下载失败,或者没有网络导致更新失败的,会继续使用未更新前的版本。并且下次启动会继续尝试更新。
这是我的web工程目录
我的web工程目录如图
至此,基本的更新逻辑已经完成。

增量更新只是在服务端version.manifest和project.manifest文件增加相应的描述即可。


我在做的过程中主要遇到了一下几个问题:

  1. 由于公司内部网络比较多,大家接入的都是内网,导致测试手机和web服务器不在一个网络,这个是个人原因
  2. web工程目录问题,导致文件下载失败,错误描述如图:
    这里写图片描述

这个主要原因是,manifest文件中的packageUrl和path拼接后的地址与web工程的目录不符合,建议各位看官在web工程搭建完成之后先在网页访问下需要下载的文件,确保拼接后的地址能过够正常访问。


点我获取源码

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值