官方文檔:https://docs.cocos.com/creator/manual/zh/advanced-topics/hot-update.html
官方熱更新Github 倉庫獲取地址
官方的示例及文檔腻暮,這里就不累贅說明了苫拍。這里直接從零開始制作實現(xiàn)一個簡單的熱更新示例惭蟋。這里分享下本人在這過程中遇到的問題。費話不多說,直接進入正題邻邮。
開發(fā)環(huán)境:
nodejs(nodejs的安裝請參考http://www.reibang.com/p/d9dac85846b3)
創(chuàng)建熱更新工程
1,創(chuàng)建HotUpate項目
直接默認(rèn)就好,這個場景呆會我們會直接用來做為1.2.0版本的升級使用
2征绎,創(chuàng)建新更新場景hotupdate
基本內(nèi)容為三個label 三個按鈕 兩個進度條,如下圖
filelabel fileprogressBar 文件總數(shù)下載進度
bytelabel byteLabel 文件下載進度
info 顯示信息
check 檢查更新
gotoscene 場景跳轉(zhuǎn)
update 更新
3,腳本編寫
創(chuàng)建HotUpdate.ts腳本并掛載到hotupdate場景的Canvas上
腳本如下:
const {ccclass, property} = cc._decorator;
@ccclass
export default class HotUpdate extends cc.Component {
? ? @property(cc.Label)
? ? fileLabel : cc.Label = null;
? ? @property(cc.Label)
? ? byteLabel : cc.Label = null;
? ? @property(cc.Label)
? ? info: cc.Label = null;
? ? @property(cc.Node)
? ? fileProgressNode : cc.Node = null;
? ? @property(cc.Node)
? ? byteProgressNode : cc.Node = null;
? ? @property(cc.RawAsset)
? ? mainifestUrl: cc.RawAsset= null;
? ? private fileProgressBar : cc.ProgressBar = null;
? ? private byteProgressBar : cc.ProgressBar = null;
? ? private _storagePath = "";
? ? private _am = null;
? ? private _updating = false;
? ? checkUpdate( ){
? ? ? ? cc.log(`--checkUpdate--`);
? ? ? ? if ( this._updating ){
? ? ? ? ? ? this.info.string = "Checking or updating...";
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? if ( this._am.getState() == jsb.AssetsManager.State.UNINITED){
? ? ? ? ? ? let url = this.mainifestUrl;
? ? ? ? ? ? cc.log(`mainifestUrl : ${this.mainifestUrl}`);
? ? ? ? ? ? this._am.loadLocalManifest(url);
? ? ? ? }
? ? ? ? if ( !this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()){
? ? ? ? ? ? this.info.string = "Failed to load local manifest ....";
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? this._updating = true;
? ? ? ? this._am.setEventCallback(this.checkCb.bind(this));
? ? ? ? this._am.checkUpdate();
? ? }
? ? checkCb( event ){
? ? ? ? let needRestart = false;
? ? ? ? let failed = false;
? ? ? ? cc.log(`checkCb event code : ${event.getEventCode()}`);
? ? ? ? switch (event.getEventCode())
? ? ? ? {
? ? ? ? ? ? case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
? ? ? ? ? ? ? ? this.info.string = "No local manifest file found, hot update skipped.";
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
? ? ? ? ? ? case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
? ? ? ? ? ? ? ? this.info.string = "Fail to download manifest file, hot update skipped.";
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
? ? ? ? ? ? ? ? this.info.string = "Already up to date with the latest remote version.";
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case jsb.EventAssetsManager.NEW_VERSION_FOUND:
? ? ? ? ? ? ? ? this.info.string = 'New version found, please try to update.';
? ? ? ? ? ? ? ? //this.checkBtn.active = false;
? ? ? ? ? ? ? ? this.fileProgressBar.progress = 0;
? ? ? ? ? ? ? ? this.byteProgressBar.progress = 0;
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? default:
? ? ? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? this._am.setEventCallback(null);
? ? ? ? this._updating = false;
? ? }
? ? hotUpdate( ){
? ? ? ? if ( this._am && !this._updating ){
? ? ? ? ? ? this._am.setEventCallback(this.updateCb.bind(this));
? ? ? ? ? ? if ( this._am.getState() === jsb.AssetsManager.State.UNINITED){
? ? ? ? ? ? ? ? this._am.loadLocalManifest(this.mainifestUrl);
? ? ? ? ? ? }
? ? ? ? ? ? //this._updating = true;
? ? ? ? ? ? this._am.update();
? ? ? ? }
? ? }
? ? private updateCb( event ){
? ? ? ? var needRestart = false;
? ? ? ? var failed = false;
? ? ? ? cc.log( `--update cb code : ${event.getEventCode()}`)
? ? ? ? switch (event.getEventCode())
? ? ? ? {
? ? ? ? ? ? case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
? ? ? ? ? ? ? ? this.info.string = 'No local manifest file found, hot update skipped.';
? ? ? ? ? ? ? ? failed = true;
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case jsb.EventAssetsManager.UPDATE_PROGRESSION:
? ? ? ? ? ? ? ? this.byteProgressBar.progress = event.getPercent();
? ? ? ? ? ? ? ? this.fileProgressBar.progress = event.getPercentByFile();
? ? ? ? ? ? ? ? this.fileLabel.string = event.getDownloadedFiles() + ' / ' + event.getTotalFiles();
? ? ? ? ? ? ? ? this.byteLabel.string = event.getDownloadedBytes() + ' / ' + event.getTotalBytes();
? ? ? ? ? ? ? ? var msg = event.getMessage();
? ? ? ? ? ? ? ? if (msg) {
? ? ? ? ? ? ? ? ? ? this.info.string = 'Updated file: ' + msg;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
? ? ? ? ? ? case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
? ? ? ? ? ? ? ? this.info.string = 'Fail to download manifest file, hot update skipped.';
? ? ? ? ? ? ? ? failed = true;
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
? ? ? ? ? ? ? ? this.info.string = 'Already up to date with the latest remote version.';
? ? ? ? ? ? ? ? failed = true;
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case jsb.EventAssetsManager.UPDATE_FINISHED:
? ? ? ? ? ? ? ? this.info.string = 'Update finished. ' + event.getMessage();
? ? ? ? ? ? ? ? needRestart = true;
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case jsb.EventAssetsManager.UPDATE_FAILED:
? ? ? ? ? ? ? ? this.info.string = 'Update failed. ' + event.getMessage();
? ? ? ? ? ? ? ? this._updating = false;
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case jsb.EventAssetsManager.ERROR_UPDATING:
? ? ? ? ? ? ? ? this.info.string = 'Asset update error: ' + event.getAssetId() + ', ' + event.getMessage();
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case jsb.EventAssetsManager.ERROR_DECOMPRESS:
? ? ? ? ? ? ? ? this.info.string = event.getMessage();
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? default:
? ? ? ? ? ? ? ? break;
? ? ? ? }
? ? ? ? if (failed) {
? ? ? ? ? ? this._am.setEventCallback(null);
? ? ? ? ? ? this._updating = false;
? ? ? ? }
? ? ? ? if (needRestart) {
? ? ? ? ? ? this._am.setEventCallback(null);
? ? ? ? ? ? // Prepend the manifest's search path
? ? ? ? ? ? var searchPaths = jsb.fileUtils.getSearchPaths();
? ? ? ? ? ? var newPaths = this._am.getLocalManifest().getSearchPaths();
? ? ? ? ? ? cc.log(JSON.stringify(newPaths));
? ? ? ? ? ? Array.prototype.unshift.apply(searchPaths, newPaths);
? ? ? ? ? ? // This value will be retrieved and appended to the default search path during game startup,
? ? ? ? ? ? // please refer to samples/js-tests/main.js for detailed usage.
? ? ? ? ? ? // !!! Re-add the search paths in main.js is very important, otherwise, new scripts won't take effect.
? ? ? ? ? ? cc.sys.localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths));
? ? ? ? ? ? jsb.fileUtils.setSearchPaths(searchPaths);
? ? ? ? ? ? cc.game.restart();
? ? ? ? }
? ? ? ? cc.log(`update cb? failed : ${failed}? , need restart : ${needRestart} , updating : ${this._updating}`);
? ? }
? ? changeScene() {
? ? ? ? cc.director.loadScene("helloworld");
? ? }
? ? // LIFE-CYCLE CALLBACKS:
? ? onLoad () {
? ? ? ? this.fileProgressBar = this.fileProgressNode.getComponent(cc.ProgressBar);
? ? ? ? this.byteProgressBar = this.byteProgressNode.getComponent(cc.ProgressBar);
? ? ? ? this.fileProgressBar.progress = 0;
? ? ? ? this.byteProgressBar.progress = 0;
? ? ? ? if ( cc.sys.isNative ){
? ? ? ? ? ? this._storagePath = jsb.fileUtils.getWritablePath();
? ? ? ? ? ? cc.log(`Storage path for remote asset : ${this._storagePath}`);
? ? ? ? ? ? this._am = new jsb.AssetsManager('', this._storagePath, this.versionCompareHanle.bind(this));
? ? ? ? ? ? let self = this;
? ? ? ? ? ? this._am.setVerifyCallback( function(path , asset) {
? ? ? ? ? ? ? ? let compressed = asset.compressed;
? ? ? ? ? ? ? ? let expectedMD5 = asset.md5;
? ? ? ? ? ? ? ? let relativePath = asset.path;
? ? ? ? ? ? ? ? let size = asset.size;
? ? ? ? ? ? ? ? if( compressed ){
? ? ? ? ? ? ? ? ? ? self.info.string = "Verification passed : " + relativePath;
? ? ? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? else{
? ? ? ? ? ? ? ? ? ? self.info.string = "Verification passed : " + relativePath + "(" + expectedMD5 + ")";
? ? ? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? });
? ? ? ? ? ? this.info.string = "Hot update is ready , please check or directly update.";
? ? ? ? }
? ? }
? ? private versionCompareHanle( versionA : string , versionB : string ){
? ? ? ? cc.log(`JS Custom Version Compare : version A is ${versionA} , version B is ${versionB}`);
? ? ? ? let vA = versionA.split('.');
? ? ? ? let vB = versionB.split('.');
? ? ? ? cc.log(`version A ${vA} , version B ${vB}`);
? ? ? ? for( let i = 0 ; i < vA.length && i < vB.length ; ++i ){
? ? ? ? ? ? let a = parseInt(vA[i]);
? ? ? ? ? ? let b = parseInt(vB[i]);
? ? ? ? ? ? if ( a === b ){
? ? ? ? ? ? ? ? continue;
? ? ? ? ? ? }
? ? ? ? ? ? else{
? ? ? ? ? ? ? ? return a - b;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? if ( vB.length > vA.length){
? ? ? ? ? ? return -1;
? ? ? ? }
? ? ? ? return 0;
? ? }
? ? start () {
? ? }
? ? onDestroy(){
? ? ? ? this._am.setEventCallback(null);
? ? }
? ? // update (dt) {}
}
4,構(gòu)建編譯版本
設(shè)置初始化場景為hotupdate場景
編譯磨取,構(gòu)建版本完成后人柿,我們會在build目錄下生成一個jsb-link的目錄如下
5,生成新舊版本
這里我們先生成一個新的版本資源忙厌,再生成舊版本的資源凫岖。到官方的示例中把version_generator.js復(fù)制到我們的項目目錄下
生成新版本
執(zhí)行命令:?node version_generator.js -v 1.2.0 -u http://localhost/remote-assets/ -s build/jsb-link/ -d assets/
生成成功,此時項目assets目錄下會生成兩個版本文件
打開工程逢净,配置mainifestUrl ,如圖
6,本地版本服務(wù)器測試環(huán)境搭建
新建nodejs目錄
編寫腳本
var express = require('express')
var path = require('path')
var app = express();
app.use(express.static(path.join(__dirname,'hotUpdate')));
app.listen(80);
另存為app.js
在nodejs下新建hotUpdate 作為熱更新資源的目錄?
在hotUpdate目錄下新建remote-assets遠程版本資源目錄
將生成的版本信息文件哥放、代碼、資源復(fù)制到該目錄下
運行資源服務(wù)器
控制臺執(zhí)行 node app.js?
瀏覽器運行查看資源服務(wù)器是否正常啟動爹土,在瀏覽器中輸入http://localhost/remote-assets/project.manifest ,如果看到如下內(nèi)容甥雕,證明資源服務(wù)器正常運行
7,舊版本的制作?
這里為了方便,直接刪除提helloworld場景作為舊版本
重新構(gòu)建編譯胀茵,這里就不重寫說明構(gòu)建編譯的過程了
構(gòu)建編譯完成后社露,生成舊版本,控制臺執(zhí)行
node version_generator.js -v 1.1.0 -u http://localhost/remote-assets/ -s build/jsb-link/ -d assets/
8,修改main.js文件
?找到build/jsb-link/main.js
在對應(yīng)位置添加如下代碼琼娘,
代碼:
if (window.jsb) {
? ? ? ? var hotUpdateSearchPaths = cc.sys.localStorage.getItem('HotUpdateSearchPaths');
? ? ? ? if (hotUpdateSearchPaths) {
? ? ? ? ? ? jsb.fileUtils.setSearchPaths(JSON.parse(hotUpdateSearchPaths));
? ? ? ? }
? ? ? ? require('src/settings.js');
? ? ? ? require('src/jsb_polyfill.js');
? ? ? ? boot();
? ? ? ? return;
? ? }
9峭弟,運行測試熱更新
打開jsb-link/frameworks/runtime-src/proj.win32工程
如果沒有自己導(dǎo)出c++類到j(luò)s中使用赁濒,這個步驟可以不做。
運行
以下是相關(guān)運行截圖
檢查更新(check)
更新(update)
下載的更新window上會自動下載到該目錄下
C:\Users\Administrator\AppData\Local\你的項目名
跳轉(zhuǎn)(goto):