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

熱更新的原理

客戶端存在一個(gè)project.manifest文件忿墅,該文件包含幾個(gè)信息:

  • packageUrl:url,服務(wù)器更新數(shù)據(jù)包根目錄城舞;
  • remoteManifestUrl:url钧唐, [可選項(xiàng)]服務(wù)器上project.manifest文件的url忙灼,
  • remoteVersionUrl:url,服務(wù)器上version.mainifest文件的url钝侠;
  • version:x.x.x,項(xiàng)目版本该园;
  • assets:{},資源列表;
    key : 資源的相對路徑(相對于資源根目錄)
    md5 : md5 值代表資源文件的版本信息
    compressed: [可選項(xiàng)] 如果值為 true帅韧,文件被下載后會自動被解壓里初,目前僅支持 zip 壓縮格式
    size: [可選項(xiàng)] 文件的字節(jié)尺寸,用于快速獲取進(jìn)度信息
  • searchPaths:" ",搜索路徑

客戶端通過本地的project.manifest中url忽舟,可以獲取服務(wù)器上project.manifest文件双妨,比較兩者的version屬性,如果客戶端的version比服務(wù)器低叮阅,則啟動更新刁品。
更新的內(nèi)容:assets是文件列表,里面列出了項(xiàng)目中的完整資源浩姥,每個(gè)資源都有md5表示挑随,客戶端根據(jù)本地project.manifest中的assets列表和服務(wù)器的assets列表對比,下載不同的資源到臨時(shí)文件夾勒叠,如果最后所有資源都正常兜挨,則把臨時(shí)文件夾的內(nèi)容替換到本地緩存文件夾中竞阐,并且修改優(yōu)先搜索路徑為該文件夾。所以重啟游戲之后的使用的資源優(yōu)先從緩存文件夾中搜索暑劝。

需要環(huán)境

  • nodejs
  • cocoscreator

官方案例

  • 下載官方范例,解壓颗搂。該案例已經(jīng)把客戶端和服務(wù)器的資源都打包好了担猛,更新包在remote-assets文件夾中。

    image.png

  • 服務(wù)器--我使用的是nodejs丢氢。

1 .新建一個(gè)文件夾nodejs傅联,在nodejs中新建hotUpdate文件夾,在把官方案例中的remote-assets復(fù)制到hotUpdate文件夾中疚察。


image.png

2 .在nodejs中新建一個(gè)js腳本蒸走,腳本內(nèi)容如下

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

3.在nodejs文件夾下執(zhí)行node app.js命令,啟動服務(wù)器貌嫡,可以訪問http://127.0.0.1/remote-assets/project.manifest比驻,如果成功訪問則服務(wù)器啟動成功。

image.png

接下來修改manifest文件里面的url岛抄,有三個(gè)文件需要修改别惦,服務(wù)器remote-assets中的兩個(gè)manifest后綴文件,官方項(xiàng)目assets文件夾下的project.manifest

image.png

image.png

只修改三個(gè)url
image.png

修改完成之后夫椭,打開項(xiàng)目掸掸,用模擬器運(yùn)行看看效果。
點(diǎn)擊檢查更新按鈕
image.png

點(diǎn)擊立即更新按鈕
image.png

因?yàn)槟承┰虿淝铮M器更新完成扰付,重啟不能使用更新的資源,所以需要編譯成原生仁讨,我的電腦不能編譯windows羽莺,就不上圖了。打包成apk自測可以成功更新洞豁。

從零開始

  • 制作新版本
    該項(xiàng)目有兩個(gè)場景禽翼,作為是新版本,資源存放在服務(wù)器上族跛。刪除一個(gè)場景作為舊版本闰挡,編譯安裝在手機(jī)上。目標(biāo)是使用舊版本熱更新成新版本礁哄,并成功切換場景长酗。
    新建一個(gè)項(xiàng)目,新建兩個(gè)場景桐绒,helloworld場景是測試熱更行是否成功的場景(舊版本不存在helloworld夺脾,通過更新可以跳轉(zhuǎn)到該場景)之拨。hotUpdate場景,該場景中添加兩個(gè)進(jìn)度條咧叭,對應(yīng)字節(jié)和文件個(gè)數(shù)兩種進(jìn)度蚀乔,三個(gè)label,對應(yīng)兩種進(jìn)度以及提示信息菲茬,三個(gè)按鈕吉挣,分別為,檢查更新婉弹,更新睬魂,切換場景。

    image.png

  • 腳本 --copy官方示例
    新建一個(gè)hotUpdate腳本镀赌,添加在Canvas上氯哮。腳本中增加五個(gè)變量,把場景中對應(yīng)的節(jié)點(diǎn)拖拽上去商佛。喉钢,增加三個(gè)回調(diào)方法,把他綁定在按鈕上良姆。

    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();
        }
    },
});

  • 打包資源
    首先構(gòu)建原生
    image.png

    構(gòu)建完成在項(xiàng)目中會生成原生項(xiàng)目出牧,熱更新需要的資源是res,src兩個(gè)文件夾以及里面的內(nèi)容歇盼。
    把nodejs--hotUpdate--remote-assets文件夾下所有東西都刪除舔痕,把res,src復(fù)制到nodejs--hotUpdate--remote-assets文件夾下豹缀。
    image.png

構(gòu)建完原生項(xiàng)目伯复,打開官方項(xiàng)目文件夾,拷貝出 version_generator.js 文件到helloworld項(xiàng)目的根目錄(如下圖)邢笙,并在helloworld根目錄打開命令窗口啸如,執(zhí)行命令,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文件夾下會多出兩個(gè)文件氮惯,把他們復(fù)制到nodejs--hotUpdate--remote-assets文件夾下叮雳。


image.png

最終遠(yuǎn)程資源如圖


image.png

在nodejs文件夾下執(zhí)行node app.js命令,啟動服務(wù)器妇汗,可以訪問http://127.0.0.1/remote-assets/project.manifest帘不,如果成功訪問則服務(wù)器啟動成功。

  • 制作舊版本

刪除helloworld場景杨箭,腳本寞焙,texture文件夾,以及之前生成的manifest文件。保存之后構(gòu)建項(xiàng)目捣郊,構(gòu)建選項(xiàng)不要變動

image.png

在helloworld根目錄打開命令窗口辽狈,執(zhí)行命令,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生成兩個(gè)manifest文件。把project.manifest拖到屬性檢查器上面娘扩。


image.png

重新構(gòu)建原生平臺着茸,在main.js加下面這段代碼,然后編譯

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

現(xiàn)在已經(jīng)完成了舊版本的制作畜侦。這是我在安卓手機(jī)上的效果。


image.png

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末躯保,一起剝皮案震驚了整個(gè)濱河市旋膳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌途事,老刑警劉巖验懊,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異尸变,居然都是意外死亡义图,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門召烂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碱工,“玉大人,你說我怎么就攤上這事奏夫∨屡瘢” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵酗昼,是天一觀的道長廊谓。 經(jīng)常有香客問我,道長麻削,這世上最難降的妖魔是什么蒸痹? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮呛哟,結(jié)果婚禮上叠荠,老公的妹妹穿的比我還像新娘。我一直安慰自己扫责,他們只是感情好蝙叛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著公给,像睡著了一般借帘。 火紅的嫁衣襯著肌膚如雪蜘渣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天肺然,我揣著相機(jī)與錄音蔫缸,去河邊找鬼。 笑死际起,一個(gè)胖子當(dāng)著我的面吹牛拾碌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播街望,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼校翔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了灾前?” 一聲冷哼從身側(cè)響起防症,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哎甲,沒想到半個(gè)月后蔫敲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡炭玫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年奈嘿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吞加。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡裙犹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出衔憨,到底是詐尸還是另有隱情伯诬,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布巫财,位于F島的核電站盗似,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏平项。R本人自食惡果不足惜赫舒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望闽瓢。 院中可真熱鬧接癌,春花似錦、人聲如沸扣讼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至荔燎,卻和暖如春耻姥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背有咨。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工琐簇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人座享。 一個(gè)月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓婉商,卻偏偏與公主長得像,于是被迫代替她去往敵國和親渣叛。 傳聞我的和親對象是個(gè)殘疾皇子丈秩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容