cocoscreator版本:2.3.2 帶版本管理的版本
cocoscreator 熱更的實現原理:存在2個版本,本地安裝包是一個版本;另一個版本放在服務器上。
增加一個程序實現:1.比對兩個版本。2.下載服務器上版本到客戶端程序上,並實現覆蓋。
cocoscreator熱更實現:
一、在項目裏新建一個層或者場景來顯示的提示熱更操作。我加的一個層和label來提示熱更。
二、使用cocoscreator自帶的熱更插件生成本地的project.manifest和version.manifest文件。或者使用cocoscreator熱更github上提供的 version_generator.js 腳本,具體命令示例:
//官方給出的命令格式
>node version_generator.js -v 1.0.0 -u http://your-server-address/tutorial-hot-update/remote-assets/ -s native/package/ -d assets/
//我的命令
>node version_generator.js -v 1.0.0 -u http://x.x.x.x:8000/HotUpdate/ -s build/jsb-default/ -d assets
//由於我們version_generator文件中,都配置好了參數
//因此可以簡單調用以下命令即可
>node version_generator.js
2.1使用熱更插件生成文件的時候,注意:1.先構建一次項目。2.使用插件生成一個版本,將生成的2個manifest文件拷貝到assets目錄裏,資源服務器的地址需要按照實際情況填寫。
三、編寫HotUpdate.js文件控制熱更,然後加到之前創建的層上。第一貼圖。我的文件如下:
// HotUpdate.js
cc.Class({
extends: cc.Component,
properties: {
manifestUrl: cc.Asset,
_updating: false,
_canRetry: false,
_storagePath: '',
label: {
default: null,
type: cc.Label
},
},
checkCb(event) {
cc.log('Code: ' + event.getEventCode());
switch (event.getEventCode()) {
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
this.node.active =true;
this.label.string = '本地文件丟失';
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
this.node.active =true;
this.label.string = '下載遠程mainfest文件錯誤';
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
this.node.active =false;
this.label.string = '已經是最新版本';
break;
case jsb.EventAssetsManager.NEW_VERSION_FOUND:
this.node.active =true;
this.label.string = '有新版本發現';
// this.hotUpdate();
break;
default:
return;
}
this._am.setEventCallback(null);
this._checkListener = null;
this._updating = false;
},
updateCb(event) {
var needRestart = false;
var failed = false;
switch (event.getEventCode()) {
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
this.label.string = '本地版本文件丟失,無法更新';
failed = true;
break;
case jsb.EventAssetsManager.UPDATE_PROGRESSION:
let percent = parseInt(event.getPercent() * 100);
if (Number.isNaN(percent)) percent = 0;
this.label.string = '更新進度:' + percent;
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
this.label.string = '下載遠程版本文件失敗';
failed = true;
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
this.node.active = false;
this.label.string = '當前爲最新版本';
failed = true;
break;
case jsb.EventAssetsManager.UPDATE_FINISHED:
this.label.string = '更新完成. ' + event.getMessage();
needRestart = true;
this.node.active = false;
break;
case jsb.EventAssetsManager.UPDATE_FAILED:
this.label.string = '更新失敗. ' + event.getMessage();
this._updating = false;
this._canRetry = true;
break;
case jsb.EventAssetsManager.ERROR_UPDATING:
this.label.string = '資源更新錯誤: ' + event.getAssetId() + ', ' + event.getMessage();
break;
case jsb.EventAssetsManager.ERROR_DECOMPRESS:
this.label.string = event.getMessage();
break;
default:
break;
}
if (failed) {
this._am.setEventCallback(null);
this._updateListener = null;
this._updating = false;
}
if (needRestart) {
this._am.setEventCallback(null);
this._updateListener = 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(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.audioEngine.stopAll();
cc.game.restart();
}
},
retry() {
if (!this._updating && this._canRetry) {
this._canRetry = false;
this.label.string = '重現獲取失敗資源...';
this._am.downloadFailedAssets();
}
},
checkUpdate() {
if (this._updating) {
// this.label.string = '檢查更新中...';
return;
}
if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
this._am.loadLocalManifest(this.manifestUrl);
}
if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) {
this.node.active = true;
this.label.string = '本地manifest加載失敗...';
return;
}
this._am.setEventCallback(this.checkCb.bind(this));
this._am.checkUpdate();
this._updating = true;
},
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.manifestUrl);
}
this._failCount = 0;
this._am.update();
this._updating = true;
}
},
// use this for initialization
onLoad() {
if (!cc.sys.isNative) {
return;
}
this.node.active = false;
this._storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'remote-asset');
cc.log('Storage path for remote asset : ' + this._storagePath);
this.versionCompareHandle = (versionA, versionB) => {
// this.label.string = 'Compare: version A is ' + versionA + ', version B is ' + versionB;
var vA = versionA.split('.');
var vB = versionB.split('.');
for (var i = 0; i < vA.length; ++i) {
var a = parseInt(vA[i]);
var b = parseInt(vB[i] || 0);
if (a === b) {
continue;
}
else {
return a - b;
}
}
if (vB.length > vA.length) {
return -1;
}
else {
return 0;
}
};
// Init with empty manifest url for testing custom manifest
this._am = new jsb.AssetsManager('', this._storagePath, this.versionCompareHandle);
this._am.setVerifyCallback((path, asset) => {
var compressed = asset.compressed;
var expectedMD5 = asset.md5;
var relativePath = asset.path;
var size = asset.size;
if (compressed) {
this.label.string = 'Verification passed : ' + relativePath;
return true;
} else {
this.label.string = 'Verification passed : ' + relativePath + ' (' + expectedMD5 + ')';
return true;
}
});
if (cc.sys.os === cc.sys.OS_ANDROID) {
// Some Android device may slow down the download process when concurrent tasks is too much.
// The value may not be accurate, please do more test and find what's most suitable for your game.
this._am.setMaxConcurrentTask(2);
// this.label.string = 'Max concurrent tasks count have been limited to 2';
}
//檢查更新
this.checkUpdate();
},
onDestroy() {
if (this._updateListener) {
this._am.setEventCallback(this.onStartIn.bind(this));
this._updateListener = null;
}
},
onStartIn(){
cc.game.restart();
},
});
四、構建打包出原始包。在 打包之前 需要修改你存放打包目錄裏的main.js文件,在其頭部加上如下內容:
(function () {
if (typeof window.jsb === 'object') {
var hotUpdateSearchPaths = localStorage.getItem('HotUpdateSearchPaths');
if (hotUpdateSearchPaths) {
var paths = JSON.parse(hotUpdateSearchPaths);
jsb.fileUtils.setSearchPaths(paths);
var fileList = [];
var storagePath = paths[0] || '';
var tempPath = storagePath + '_temp/';
var baseOffset = tempPath.length;
if (jsb.fileUtils.isDirectoryExist(tempPath) && !jsb.fileUtils.isFileExist(tempPath + 'project.manifest.temp')) {
jsb.fileUtils.listFilesRecursively(tempPath, fileList);
fileList.forEach(srcPath => {
var relativePath = srcPath.substr(baseOffset);
var dstPath = storagePath + relativePath;
if (srcPath[srcPath.length] == '/') {
cc.fileUtils.createDirectory(dstPath)
}
else {
if (cc.fileUtils.isFileExist(dstPath)) {
cc.fileUtils.removeFile(dstPath)
}
cc.fileUtils.renameFile(srcPath, dstPath);
}
})
cc.fileUtils.removeDirectory(tempPath);
}
}
}
})();
如果不加,那麼打出來的包將無法更新。在構建編譯的時候指的模塊和編譯的目錄對應。我的位置如下圖:
五、成功打包之後,將包安裝到手機或者模擬器上。然後,對項目進行修改、更新。然後,“構建” 一次項目,生產出新版本的資源文件,不要編譯!!然後使用熱更插件 生產出來一個新版本的資源,提高一個版本號,生成出來之後,放到服務器。
六、打開程序 就看到提示更新了。