cocoscreator热更新-小白教程

// cocos creator

首先是官方文档

http://docs.cocos.com/creator/manual/zh/advanced-topics/hot-update.html
http://docs.cocos.com/creator/manual/zh/advanced-topics/assets-manager.html

热更新的原理

客户端存在一个project.manifest文件,该文件包含几个信息:

  • packageUrl:url,服务器更新数据包根目录;
  • remoteManifestUrl:url, [可选项]服务器上project.manifest文件的url,
  • remoteVersionUrl:url,服务器上version.mainifest文件的url;
  • version:x.x.x,项目版本;
  • assets:{},资源列表;
    key : 资源的相对路径(相对于资源根目录)
    md5 : md5 值代表资源文件的版本信息
    compressed: [可选项] 如果值为 true,文件被下载后会自动被解压,目前仅支持 zip 压缩格式
    size: [可选项] 文件的字节尺寸,用于快速获取进度信息
  • searchPaths:" ",搜索路径

客户端通过本地的project.manifest中url,可以获取服务器上project.manifest文件,比较两者的version属性,如果客户端的version比服务器低,则启动更新。
更新的内容:assets是文件列表,里面列出了项目中的完整资源,每个资源都有md5表示,客户端根据本地project.manifest中的assets列表和服务器的assets列表对比,下载不同的资源到临时文件夹,如果最后所有资源都正常,则把临时文件夹的内容替换到本地缓存文件夹中,并且修改优先搜索路径为该文件夹。所以重启游戏之后的使用的资源优先从缓存文件夹中搜索。

需要环境

  • nodejs
  • cocoscreator

官方案例

  • 下载官方范例,解压。该案例已经把客户端和服务器的资源都打包好了,更新包在remote-assets文件夹中。

    image.png

  • 服务器--我使用的是nodejs。

1 .新建一个文件夹nodejs,在nodejs中新建hotUpdate文件夹,在把官方案例中的remote-assets复制到hotUpdate文件夹中。


image.png

2 .在nodejs中新建一个js脚本,脚本内容如下

var express = require('express');
var path = require('path');
var app = express();
app.use(express.static(path.join(__dirname, 'hotUpdate')));
app.listen(80);

3.在nodejs文件夹下执行node app.js命令,启动服务器,可以访问http://127.0.0.1/remote-assets/project.manifest,如果成功访问则服务器启动成功。

image.png

接下来修改manifest文件里面的url,有三个文件需要修改,服务器remote-assets中的两个manifest后缀文件,官方项目assets文件夹下的project.manifest

image.png

image.png

只修改三个url
image.png

修改完成之后,打开项目,用模拟器运行看看效果。
点击检查更新按钮
image.png

点击立即更新按钮
image.png

因为某些原因,模拟器更新完成,重启不能使用更新的资源,所以需要编译成原生,我的电脑不能编译windows,就不上图了。打包成apk自测可以成功更新。

从零开始

  • 制作新版本
    该项目有两个场景,作为是新版本,资源存放在服务器上。删除一个场景作为旧版本,编译安装在手机上。目标是使用旧版本热更新成新版本,并成功切换场景。
    新建一个项目,新建两个场景,helloworld场景是测试热更行是否成功的场景(旧版本不存在helloworld,通过更新可以跳转到该场景)。hotUpdate场景,该场景中添加两个进度条,对应字节和文件个数两种进度,三个label,对应两种进度以及提示信息,三个按钮,分别为,检查更新,更新,切换场景。

    image.png

  • 脚本 --copy官方示例
    新建一个hotUpdate脚本,添加在Canvas上。脚本中增加五个变量,把场景中对应的节点拖拽上去。,增加三个回调方法,把他绑定在按钮上。

    image.png

    cc.Class({
    extends: cc.Component,

    properties: {
        byteLabel: cc.Label,
        fileLabel: cc.Label,
        byteProgress: cc.ProgressBar,
        fileProgress: cc.ProgressBar,
        label: cc.Label,
        manifestUrl: cc.RawAsset,
    },

    onLoad() {
        var self = this;
        this._storagePath = jsb.fileUtils.getWritablePath();
        this._am = new jsb.AssetsManager('', this._storagePath, this.versionCompareHandle);
        if (!cc.sys.ENABLE_GC_FOR_NATIVE_OBJECTS) {
            this._am.retain();
        }
        this._am.setVerifyCallback(function (path, asset) {
            var compressed = asset.compressed;
            var expectedMD5 = asset.md5;
            var relativePath = asset.path;
            var size = asset.size;
            if (compressed) {
                self .label.string = "Verification passed : " + relativePath;
                return true;
            }
            else {
                self .label.string = "Verification passed : " + relativePath + ' (' + expectedMD5 + ')';
                return true;
            }
        });
        this.byteProgress.progress = 0;
        this.fileProgress.progress = 0;
    },

    hotUpdate() {
        if (this._am && !this._updating) {
            this._updateListener = new jsb.EventListenerAssetsManager(this._am, this.updateCb.bind(this));
            cc.eventManager.addListener(this._updateListener, 1);

            if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
                this._am.loadLocalManifest(this.manifestUrl);
            }

            this._am.update();
            this._updating = true;
        }
    },

    checkUpdata() {
        if (this._updating) {
            this.label.string = 'Checking or updating ...';
            return;
        }
        if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
            this._am.loadLocalManifest(this.manifestUrl);
        }
        if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) {
            this.label.string = 'Failed to load local manifest ...';
            return;
        }
        this._checkListener = new jsb.EventListenerAssetsManager(this._am, this.checkCb.bind(this));
        cc.eventManager.addListener(this._checkListener, 1);

        this._am.checkUpdate();
        this._updating = true;
    },

    changeScene() {
        cc.director.loadScene('helloworld');
    },

    checkCb: function (event) {
        switch (event.getEventCode()) {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                this.label.string = "No local manifest file found, hot update skipped.";
                break;
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                this.label.string = "Fail to download manifest file, hot update skipped.";
                break;
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                this.label.string = "Already up to date with the latest remote version.";
                break;
            case jsb.EventAssetsManager.NEW_VERSION_FOUND:
                this.label.string = 'New version found, please try to update.';
                this.fileProgress.progress = 0;
                this.byteProgress.progress = 0;
                break;
            default:
                return;
        }

        cc.eventManager.removeListener(this._checkListener);
        this._checkListener = null;
        this._updating = false;
    },

    updateCb: function (event) {
        var needRestart = false;
        var failed = false;
        switch (event.getEventCode()) {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                this.label.string = 'No local manifest file found, hot update skipped.';
                failed = true;
                break;
            case jsb.EventAssetsManager.UPDATE_PROGRESSION:
                this.byteProgress.progress = event.getPercent();
                this.fileProgress.progress = event.getPercentByFile();

                this.fileLabel.string = event.getDownloadedFiles() + ' / ' + event.getTotalFiles();
                this.byteLabel.string = event.getDownloadedBytes() + ' / ' + event.getTotalBytes();

                var msg = event.getMessage();
                if (msg) {
                    this.label.string = 'Updated file: ' + msg;
                }
                break;
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                this.label.string = 'Fail to download manifest file, hot update skipped.';
                failed = true;
                break;
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                this.label.string = 'Already up to date with the latest remote version.';
                failed = true;
                break;
            case jsb.EventAssetsManager.UPDATE_FINISHED:
                this.label.string = 'Update finished. ' + event.getMessage();
                needRestart = true;
                break;
            case jsb.EventAssetsManager.UPDATE_FAILED:
                this.label.string = 'Update failed. ' + event.getMessage();
                this._updating = false;
                this._canRetry = true;
                break;
            case jsb.EventAssetsManager.ERROR_UPDATING:
                this.label.string = 'Asset update error: ' + event.getAssetId() + ', ' + event.getMessage();
                break;
            case jsb.EventAssetsManager.ERROR_DECOMPRESS:
                this.label.string = event.getMessage();
                break;
            default:
                break;
        }

        if (failed) {
            cc.eventManager.removeListener(this._updateListener);
            this._updateListener = null;
            this._updating = false;
        }

        if (needRestart) {
            cc.eventManager.removeListener(this._updateListener);
            this._updateListener = null;
            var searchPaths = jsb.fileUtils.getSearchPaths();
            var newPaths = this._am.getLocalManifest().getSearchPaths();

            Array.prototype.unshift(searchPaths, newPaths);
            cc.sys.localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths));
            jsb.fileUtils.setSearchPaths(searchPaths);

            cc.game.restart();
        }
    },
});

  • 打包资源
    首先构建原生
    image.png

    构建完成在项目中会生成原生项目,热更新需要的资源是res,src两个文件夹以及里面的内容。
    把nodejs--hotUpdate--remote-assets文件夹下所有东西都删除,把res,src复制到nodejs--hotUpdate--remote-assets文件夹下。
    image.png

构建完原生项目,打开官方项目文件夹,拷贝出 version_generator.js 文件到helloworld项目的根目录(如下图),并在helloworld根目录打开命令窗口,执行命令,node version_generator.js -v 1.7.0 -u http://127.0.0.1/remote-assets/ -s build/jsb-default/ -d assets/

image.png

在assets文件夹下会多出两个文件,把他们复制到nodejs--hotUpdate--remote-assets文件夹下。


image.png

最终远程资源如图


image.png

在nodejs文件夹下执行node app.js命令,启动服务器,可以访问http://127.0.0.1/remote-assets/project.manifest,如果成功访问则服务器启动成功。

  • 制作旧版本

删除helloworld场景,脚本,texture文件夹,以及之前生成的manifest文件。保存之后构建项目,构建选项不要变动

image.png

在helloworld根目录打开命令窗口,执行命令,node version_generator.js -v 1.0.0 -u http://127.0.0.1/remote-assets/ -s build/jsb-default/ -d assets/注意这里的-v 1.0.0,之前是1.7.0,旧版本版本号要小于新版本

在assets生成两个manifest文件。把project.manifest拖到属性检查器上面。


image.png

重新构建原生平台,在main.js加下面这段代码,然后编译

if (cc.sys.isNative) {
   var hotUpdateSearchPaths = cc.sys.localStorage.getItem('HotUpdateSearchPaths');
    if (hotUpdateSearchPaths) {
         jsb.fileUtils.setSearchPaths(JSON.parse(hotUpdateSearchPaths));
    }
}
image.png

现在已经完成了旧版本的制作。这是我在安卓手机上的效果。


image.png

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容