Cocos3.15 js 热更新实践

没想到我上篇博文还在写作中,cocos就出了新版3.15,好像对音频播放做了优化,挺好的,但是热更新也修改了点,好吧,其实我之前修改了它的热更新机制,实现序列化更新,就是1.0.0 更新到1.0.1然后再更新到1.0.2。好吧,这个方法的缺陷就是不能删除以前不要的文件。但是cocos自己的新的更新机制也没有去删除不要的文件啊,-_-,看下面的代码:

if (diff.type != Manifest::DiffType::DELETED)
{
        std::string path = diff.asset.path;
        DownloadUnit unit;
        unit.customId = it->first;
        unit.srcUrl = packageUrl + path;
        unit.storagePath = _tempStoragePath + path;
        unit.size = diff.asset.size;
        _downloadUnits.emplace(unit.customId, unit);
        _tempManifest->setAssetDownloadState(it->first,Manifest::DownloadState::UNSTARTED);
 }

唯一用到Manifest::DiffType::DELETED的地方。就是如果我们的asset资源如果不是删除状态,那么我们记录需要下载。

3.15下载文件如果需要解压缩,那么直接解压缩,3.14.1还是添加到一个待解压缩列表,于是我在3.14.1中做了个修改,让他们安装顺序填入map中,然后依次解压缩,后面解压的覆盖前面,这样我们就实现了跨版本序列化更新。可是3.15的改动导致我得重新修改很多代码。

cocos的本意是所有的src和res的每个文件都是单独的assets资源,这样可以改动,或添加的文件就会下载更新,但是这样的话,打包更新包麻烦,每个文件都需要单独打包,就算写脚本处理也觉得不大靠谱,更何况之前遇到的游戏更新,大家都是一个版本一个版本的更新上去的,并不是直接跳到某个版本,亦或是我觉得这样更新不爽吧,我觉得还是一个版本一个版本更新舒服一点。哈哈。。。

好,那我们先开始修改源码。

找到 AssetsManagerEx.cpp:

改动1

添加代码:

#define MAX_DOWNLOAD_TASK 1
改动2
network::DownloaderHints hints =
    {
        static_cast<uint32_t>(_maxConcurrentTaskK),
        DEFAULT_CONNECTION_TIMEOUT,
        ".tmp"
    };

改成

network::DownloaderHints hints =
    {
        static_cast<uint32_t>(/*_maxConcurrentTask*/MAX_DOWNLOAD_TASK),
        DEFAULT_CONNECTION_TIMEOUT,
        ".tmp"
    };
改动3
while (_currConcurrentTask < _maxConcurrentTask && _queue.size() > 0)

改成

while (_currConcurrentTask < /*_maxConcurrentTask*/MAX_DOWNLOAD_TASK && _queue.size() > 0)

意思很简单,我们下载任务只执行一个!!!

c++代码修改完成。
那我们先下载Tomcat,安装完成后,打开开始菜单的

开始菜单

如果你能看到下面的图片:

Apache欢迎页

说明你已经安装成功了。好先放一边。。。

创建一个cocos js工程,

  1. 下载安装cocos3.15
  2. 下载安装python2.7
  3. 打开cocos3.15环境,我的是:


    cocos3.15安装环境
  4. 执行setup.py,根据提示,部署环境。
  5. 运行cmd,执行脚本cocos new -p com.hangzhou.test -d D:\xxx -l js ,最好事先创建目录xxx目录,xxx是你希望项目存放的目录
  6. 下载安装webstorm
  7. 用webstorm打开我们的xxx目录。

修改我们的代码

在src目录新建两个文件:updateList.js和assetsManagerScene.js文件

文件内容是:

updateList.js 用于保存我们的更新js列表
var jsUpdateList = [
    "src/app.js",
    "src/resource.js"
];
assetsManagerScene.js 热更新场景
/**
 * 热更新
 */

var failCount = 0;

var maxFailCount = 0;   //最大错误重试次数

/**

 * 自动更新js和资源

 */

var AssetsManagerLoaderScene = cc.Scene.extend({

    _am: null,

    _progress: null,

    _percent: 0,

    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", 12);

        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/Manifests/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;

            //cc.EventListenerAssetsManager

            var listener = new jsb.EventListenerAssetsManager(this._am, function (event) {

                switch (event.getEventCode()) {

                    case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:

                        cc.log("assetsManagerScene : ERROR_NO_LOCAL_MANIFEST  " + event.getMessage());
                        that.loadGame();

                        break;
                    case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
                        cc.log("assetsManagerScene : ERROR_DOWNLOAD_MANIFEST  " + event.getMessage());

                        that.loadGame();

                        break;
                    case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:

                        cc.log("assetsManagerScene : ERROR_PARSE_MANIFEST  " + event.getMessage());

                        that.loadGame();

                        break;
                    case jsb.EventAssetsManager.NEW_VERSION_FOUND:
                        cc.log("assetsManagerScene : NEW_VERSION_FOUND " + event.getMessage());

                        //我们需要更新。。。
                        break;
                    case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:

                        cc.log("assetsManagerScene : ALREADY_UP_TO_DATE " + event.getMessage());
                        //我们不需要更新。。。
                        that.loadGame();

                        break;
                    case jsb.EventAssetsManager.UPDATE_PROGRESSION://更新进度条

                        cc.log("assetsManagerScene : UPDATE_PROGRESSION " + event.getPercent() + "," + event.getMessage());
                        that._percent = event.getPercent();
                        var msg = event.getMessage();
                        if (msg) {
                            cc.log(msg);
                        }
                        that.updateProgress(event.getPercent());

                        break;
                    case jsb.EventAssetsManager.ASSET_UPDATED://资源更新完毕,可能还需要解压缩
                        cc.log("assetsManagerScene : ASSET_UPDATED " + event.getAssetId() + "," + event.getMessage());
                        break;
                    case jsb.EventAssetsManager.ERROR_UPDATING://更新发生错误

                        cc.log("assetsManagerScene : ERROR_UPDATING " + event.getAssetId() + ", " + event.getMessage());

                        that.loadGame();

                        break;
                    case jsb.EventAssetsManager.UPDATE_FINISHED://资源更新完毕

                        cc.log("assetsManagerScene : UPDATE_FINISHED " + event.getMessage());

                        that.loadGame();

                        break;
                    case jsb.EventAssetsManager.UPDATE_FAILED://更新失败

                        cc.log("assetsManagerScene : 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_DECOMPRESS://解压错误

                        cc.log("assetsManagerScene : ERROR_DECOMPRESS " + event.getMessage());

                        that.loadGame();

                        break;

                    default:
                        cc.log("assetsManagerScene : unknown Event" + event.getMessage());
                        that.loadGame();
                        break;

                }

            });

            cc.eventManager.addListener(listener, 1);

            this._am.update();

            cc.director.runScene(this);

        }
    },

    loadGame: function () {
        //jsList是jsList.js的变量,记录全部js。
        if (cc.sys.isNative) {

            cc.loader.loadJs("", ["src/updateList.js"], function (error) {
                if (error)
                    console.log("load js src/updateList.js error=" + error);
                else
                    console.log("load js src/updateList.js success");

                // cc.loader.loadImg() // 图片放后边再说
                cc.loader.loadJs("", jsUpdateList, function (error) {
                    if (error)
                        console.log("load js error=" + error);
                    else
                        console.log("load js success");

                    cc.director.runScene(new HelloWorldScene());
                });

            });
        }
    },

    updateProgress: function (percent) {
        this._progress.string = "update__" + percent + "__%";
    },

    onExit: function () {

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

        this._am.release();

        this._super();

    }

});

修改我们的main.js文件:

    cc.LoaderScene.preload(g_resources, function () {
        cc.director.runScene(new HelloWorldScene());

    }, this);

改成:

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

    var scene = new AssetsManagerLoaderScene();

    scene.run();

修改project.json,改成这样

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

在res/Manifests目录下添加一个版本文件:project.manifest

{
    "packageUrl": "http://192.168.1.148:8080/cocos/test/",
    "remoteManifestUrl": "http://192.168.1.148:8080/cocos/test/project.manifest",
    "remoteVersionUrl": "http://192.168.1.148:8080/cocos/test/version.manifest",
    "engineVersion": "3.15",
    "version": "1.0.0",

    "assets": {
    },
    "searchPaths": [
        "update"
    ]
}

最终webstorm的目录结构是:

目录结构

打开vs项目


vs解决方案

运行项目,我们可以看到我们的更新场景:

更新场景

大约4秒后会呈现我们的游戏场景

游戏场景

由于我们还没配置我们的热更新服务器,所以有4秒的超时等待时间。

下面配置我们的热更新服务器:
打开Apache安装目录,我的是在C:\Program Files\Apache Software Foundation\Tomcat 9.0\webapps,新建cocos目录,再在里面创建test目录。
得到这样一个目录:C:\Program Files\Apache Software Foundation\Tomcat 9.0\webapps\cocos\test
创建目录update,和project.manifest文件和version.manifest文件
结果图:

test目录

project.manifest就是我上面讲的文件,内容一样的。
version.manifest内容如下:

{
    "forceUpdate": true
    
    "packageUrl": "http://192.168.1.148:8080/cocos/test/",
    "remoteManifestUrl": "http://192.168.1.148:8080/cocos/test/project.manifest",
    "remoteVersionUrl": "http://192.168.1.148:8080/cocos/test/version.manifest",
    "engineVersion": "3.15",
    "version": "1.0.0"
}

再运行我们的程序,发现我们很快就从更新场景到游戏场景了!!!

下面进行我们的服务器配置热更新

拷贝一份app.js到test,修改一行代码:

var helloLabel = new cc.LabelTTF("Hello World", "Arial", 38);

改成

var helloLabel = new cc.LabelTTF("Hello World 1", "Arial", 38);

新建src目录拷贝app.js到里面,然后添加到压缩包。起名app_js_1.0.1.zip:

app_js_1.0.1.zip

修改我们在test目录的project.manifest和version.manifest:

project.manifest
version.manifest

再次运行我们的程序,OK,见到我们的Hello World 1了!!!

Hello World 1

下面,我们重复操作,把上面那段显示文字改成Hello World 2,Hello World 3,每个都创建一个压缩包:

资源更新包

压缩包都一样都是包含一个src文件夹,里面存一个app.js文件。
继续修改我们的project.manifest和version.manifest文件:

project.manifest
version.manifest

重新运行我们的程序!如愿以偿看到我们的Hello World 3

Hello World 3

最后要说的话

这里cocos有一个坑,

"assets": {
        "update3": {
            "path": "update/app.js_1.0.3.zip",
            "md5": "9521D6B2176D667E22DC4F8345AE96E5",
            "compressed": true
        },
        "update2": {
            "path": "update/app.js_1.0.2.zip",
            "md5": "940002A15EA29516E3AC45AE073BE66C",
            "compressed": true
        },
        "update1": {
            "path": "update/app.js_1.0.1.zip",
            "md5": "88AA0BC4F6D9FE04A8532859674DE321",
            "compressed": true
        }
        
    }

我们所有的assets更新文件必须是倒序的,因为cocos执行下载的时候是倒着取的,

while (_currConcurrentTask < /*_maxConcurrentTask*/MAX_DOWNLOAD_TASK && _queue.size() > 0)
    {
        std::string key = _queue.back();//这里从back取下载的key,也就是我们的update1,update2,update3
        _queue.pop_back();
        
        _currConcurrentTask++;
        DownloadUnit& unit = _downloadUnits[key];
        _fileUtils->createDirectory(basename(unit.storagePath));
        _downloader->createDownloadFileTask(unit.srcUrl, unit.storagePath, unit.customId);
        
        _tempManifest->setAssetDownloadState(key, Manifest::DownloadState::DOWNLOADING);
    }

限制单个下载也是这个原因,我们如果多个任务同时下载,可能会导致覆盖文件出现问题,可能导致我们的Hello World 3不能显示,可能最后只显示Hello World 1

后续问题

后面发现文件多了,更新的下来的zip包也是多线程解压缩的,,然后可能会存在旧的压缩包解压出来的东西替换最新的压缩包解压出来的,,尴尬。。。可能还是只能支持整包更新吧。。。看了官方github上提供的manifest生成工具,它的思路就是把你所有的文件都记录在案,没有的,自己去下载。可能这样是目前最好的一种方法了。附上官方的manifest生成工具,使用之前需要安装nodejs

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,421评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 有点懒得把图片传上来了,请移步这里看 Cocos官方论坛-wiki CocoaChina论坛帖子 上面两个是一样的...
    椒盐老蛏阅读 5,199评论 1 6
  • 官方有提供demo。demo链接:https://github.com/cocos-creator/tutoria...
    紫荆逸阅读 4,817评论 7 8
  • 第二个专题是树,今天才刷完,上周被实习郁闷崩了TAT104. Maximum Depth of Binary Tr...
    codingXue阅读 193评论 0 0