当前位置:科技动态 > CocosCreator 热更新(v1.10.2)

CocosCreator 热更新(v1.10.2)

  • 发布:2023-09-29 11:52

creator 生成的项目纯粹是数据驱动而非代码驱动。一切都是数据,包括脚本、图片、音频、动画等,数据驱动需要一个入口。就是main.js,setting.js描述了整个资源的配置文件。我们自己写的脚本编译后存放在project.js中。根据这个原理,热更新将游戏资源存储在服务器上,与本地manifest进行比较,将差异文件下载到某个文件夹,并在入口文件main.js中设置搜索路径为更新的文件夹,从而实现热更新的目的。

新馆项目

VersionTip 用于提示更新,UpdateTip 用于显示更新进度,FinishTip 用于提示完成更新并重启。

热更新脚本:

onst hotResDir = "AllGame/Hall"; //更新的资源目录export class HotUpdateUtil {private static am;private static isUpdating = false;private static checkUpdateListener;private static updateListener;private static manifestUrl;private static checkNewVersionListener: Function;private static updateProgressListener: Function;private static updateErrorListener: Function; private static updateFinishListener: Function;public static init(manifestUrl: cc.Asset) {if (!cc.sys.isNative) return;this.manifestUrl = manifestUrl;let storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + hotResDir);www.sychzs.cn = new jsb.AssetsManager("", storagePath, this.versionCompareHandle);if (!cc.sys.ENABLE_GC_FOR_NATIVE_OBJECTS) {this.am.retain();}www.sychzs.cn .setVerifyCallback(function (path, asset) {varcompressed=asset.compressed;varexpectedMD5=www.sychzs.cn5;varrelativePath=asset.path;varsize=asset.size;if(compressed){cc.log("验证通过: " + 相对路径);return true;}else {cc.log("验证通过:" +relativePath + '('+expectedMD5 +')');return true;}});if (cc.sys.os === cc. sys.OS_ANDROID) {// 部分Android设备并发任务过多时,下载速度可能会变慢。// 该值可能不准确,请多做测试,找到最适合您游戏的值。this.am.setMaxConcurrentTask (2);cc.log("最大并发任务数已限制为2");}}//版本对比private static versionCompareHandle(versionA, versionB): number {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;}}public static checkUpdate(checkNewVersionCallback?:Function) { if (this.isUpdating) {cc.log("正在更新中...");return;}this.checkNewVersionListener = checkNewVersionCallback;if (this.am.getState() === jsb.AssetsManager.State.UNINITED) {var url = this.manifestUrl;cc.log(url);if (www.sychzs.cn5Pipe) {url = cc.loader.md5Pipe.transformURL(url);}this.am.loadLocalManifest(url);}if (!this.am.getLocalManifest() || !this.am.getLocalManifest().isLoaded()) {cc.log('无法加载本地清单...' );return;}this.checkUpdateListener = new jsb.EventListenerAssetsManager(www.sychzs.cn, this.checkCallback.bind(this));cc.eventManager.addListener(this.checkUpdateListener, 1);this.am.checkUpdate();this .isUpdating = true;}private static checkCallback(event) {cc.log('代码:' + event.getEventCode());switch (event.getEventCode()) {case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:cc.log("未找到本地清单文件,跳过热更新。");break;case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:cc.log("下载清单文件失败,跳过热更新。");break;case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:cc.log("已经是最新的远程版本。");break;case www.sychzs.cn_VERSION_FOUND:cc.log('发现新版本,请尝试update.');if (this.checkNewVersionListener) {this.checkNewVersionListener();}break;default:return;}cc.eventManager.removeListener(this.checkUpdateListener);this.checkUpdateListener = null;this.isUpdating = false;}公共静态更新(updateProgressListener?:功能,updateErrorListener?:功能,updateFinishListener?:功能){if(www.sychzs.cn &&!this.isUpdating){this.updateProgressListener = updateProgressListener;this.updateErrorListener = updateErrorListener;this.updateFinishListener = updateFinishListener; this.updateListener = new jsb.EventListenerAssetsManager(www.sychzs.cn, this.updateCallback.bind(this));cc.eventManager.addListener(this.updateListener, 1);if (this.am.getState() === jsb. AssetsManager.State.UNINITED) {// 解析 md5 urlvar url = this.manifestUrl;if (www.sychzs.cn5Pipe) {url = cc.loader.md5Pipe.transformURL(url);}this.am.loadLocalManifest(url); }this.am.update();this.isUpdating = true;}}private static updateCallback(event) {var needRestart = false;var failed = false;switch (event.getEventCode()) {case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:cc.log('未找到本地清单文件,跳过热更新。');failed = true;break;case jsb.EventAssetsManager.UPDATE_PROGRESSION:if(this.updateProgressListener){this.updateProgressListener(event.getDownloadedBytes(),event.getTotalBytes());}break;case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:cc.log ('清单文件下载失败,跳过热更新。');if(this.updateErrorListener){this.updateFinishListener('清单文件下载失败,跳过热更新。');}failed = true;break;case jsb. EventAssetsManager.ALREADY_UP_TO_DATE:cc.log("已经是最新的远程版本。");failed = true;break;case jsb.EventAssetsManager.UPDATE_FINISHED:cc.log('更新完成。' + event.getMessage() );needRestart = true;if(this.updateFinishListener){this.updateFinishListener();}break;case jsb.EventAssetsManager.UPDATE_FAILED:cc.log('更新失败。' + event.getMessage());if(this.updateErrorListener){this.updateErrorListener('更新失败。' + event.getMessage());}this .isUpdating = false;break;case jsb.EventAssetsManager.ERROR_UPDATING:cc.log('资产更新错误:' + event.getAssetId() + ', ' + event.getMessage());if(this.updateErrorListener){this .updateErrorListener('资源更新错误:' + event.getAssetId() + ', ' + event.getMessage());}break;case jsb.EventAssetsManager.ERROR_DECOMPRESS:cc.log(event.getMessage());break;默认:break;}if(失败){cc.eventManager.removeListener(this.updateListener);this.updateListener = null;this.isUpdating = false;}if(需要重新启动){cc.eventManager.removeListener(this.updateListener); this.updateListener = null;// 前置清单的搜索路径var searchPaths = jsb.fileUtils.getSearchPaths();var newPaths = this.am.getLocalManifest().getSearchPaths();cc.log(JSON.stringify(newPaths));大批.prototype.unshift.apply(searchPaths, newPaths);// 游戏启动时会检索该值并附加到默认搜索路径中,// 详细使用请参考samples/js-tests/main.js。// !!!在main.js中重新添加搜索路径非常重要,否则新脚本将不会生效。cc.sys.localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths));jsb.fileUtils.setSearchPaths(搜索路径);}}}

使用脚本

从 "./HotUpdateUtil" 导入 { HotUpdateUtil };const {ccclass, property} = cc._decorator;@ccclass导出默认类 HotUpdate 扩展 cc.Component {@property(cc.Asset)manifest:cc.Asset = null;@property(cc.Node)newVersionTip:cc.Node = null;@property(cc.Node)updateTip:cc. Node = null;@property(cc.Node)finishUpdateTip:cc.Node = null;onLoad(){HotUpdateUtil.init(this.manifest);} start(){HotUpdateUtil.checkUpdate(()=>{this.newVersionTip. active = true;});}private updateVersion(){www.sychzs.cn = true;HotUpdateUtil.update((progress)=>{let temp = ~~(progress * 100);this.updateTip.getComponentInChildren(cc. Label).string = "下载中" + temp + "%";},null,()=>{www.sychzs.cn = true;});}private restart(){cc.game.restart(); }}

生成manifest文件

首先,一开始HotUpdate的manifest是空的:

图片.png

? version_generator.js,根据需要修改里面的路径:

/*** 该模块用于生成热更新项目清单文件*/var fs = require('fs');
var 路径 = require('路径');
var crypto = require('crypto'); var manifest = {//服务器上的资源文件存放路径(src,res路径) packageUrl: 'http://192.168.0.136:8000',//服务器上的project.manifest Path remoteManifestUrl: 'http:// 192.168.0.136:8000/project.manifest',//version.manifest在服务器上的路径remoteVersionUrl:'http://192.168.0.136:8000/version.manifest',version:'1.0. 0',资产:{},搜索路径:[]
};//生成的manifest文件存放目录
var dest = '资产/';
//项目构建后的资源目录var src = 'build/jsb-default/';/*** 节点 version_generator.js -v 1.0.0 -u http://your-server-address/tutorial-hot-update/remote-assets/ -s native /包/ -d 资产/*/
// 解析参数
变量我 = 2;
while ( i < process.argv.length) {var arg = process.argv[i];switch (arg) {case '--url' :case '-u' :var url = process.argv[i+1] ;manifest.packageUrl = url;manifest.remoteManifestUrl = url + 'project.manifest';manifest.remoteVersionUrl = url + 'version.manifest';i += 2;break;case '--version' :case '-v' :manifest.version = process.argv[i+1];i += 2;break;case '--src' :case '-s' :src = process.argv[i+1];i += 2; break;case '--dest' :case '-d' :dest = process.argv[i+1];i += 2;break;default :i++;break;}
}function readDir (dir, obj) {var stat = fs.statSync(dir);if (!stat.isDirectory()) {return;}var subpaths = fs.readdirSync(dir), subpath, size, md5, compressed, relative;for (var i = 0; i < subpaths.length; ++i) {if (subpaths[i][0] === '.') {continue;}subpath = path.join(dir, subpaths[i]);stat = fs.statSync(subpath);if (stat.isDirectory()) {readDir(subpath, obj);}else if (stat.isFile()) {// Size in Bytessize = stat['size'];md5 = crypto.createHash('md5').update(fs.readFileSync(subpath, 'binary')).digest('hex');compressed = path.extname(subpath).toLowerCase() === '.zip';relative = path.relative(src, subpath);relative = relative.replace(/\\/g, '/');relative = encodeURI(relative);obj[relative] = {'size' : size,'md5' : md5};if (compressed) {obj[relative].compressed = true;}}}
}var mkdirSync = function (path) {try {fs.mkdirSync(path);} catch(e) {if ( e.code != 'EEXIST' ) throw e;}
}// Iterate res and src folder
readDir(path.join(src, 'src'), manifest.assets);
readDir(path.join(src, 'res'), manifest.assets);var destManifest = path.join(dest, 'project.manifest');
var destVersion = path.join(dest, 'version.manifest');mkdirSync(dest);fs.writeFile(destManifest, JSON.stringify(manifest), (err) => {if (err) throw err;console.log('Manifest successfully generated');
});delete manifest.assets;
delete www.sychzs.cnPaths;
fs.writeFile(destVersion, JSON.stringify(manifest), (err) => {if (err) throw err;console.log('Version successfully generated');
});

接着cd到工程目录下,执行 node version_generator.js,在assets会自动生成了两个文件,project. manifest和version. manifest,把project. manifest拖给HotUpdate:

然后构建项目,构建成功后,修改mian.js:

(function () {//添加这段if ( cc.sys.isNative) {var hotUpdateSearchPaths = cc.sys.localStorage.getItem('HotUpdateSearchPaths');  //这个key对于HotUpdateUtilif (hotUpdateSearchPaths) {jsb.fileUtils.setSearchPaths(JSON.parse(hotUpdateSearchPaths));}}'use strict';//-------------function boot () {var settings = window._CCSettings;window._CCSettings = undefined;if ( !settings.debug ) {var uuids = settings.uuids;var rawAssets = settings.rawAssets;var assetTypes = settings.assetTypes;var realRawAssets = settings.rawAssets = {};for (var mount in rawAssets) {var entries = rawAssets[mount];var realEntries = realRawAssets[mount] = {};for (var id in entries) {var entry = entries[id];var type = entry[1];// retrieve minified raw assetif (typeof type === 'number') {entry[1] = assetTypes[type];}// retrieve uuidrealEntries[uuids[id] || id] = entry;}}var scenes = settings.scenes;for (var i = 0; i < scenes.length; ++i) {var scene = scenes[i];if (typeof scene.uuid === 'number') {scene.uuid = uuids[scene.uuid];}}var packedAssets = settings.packedAssets;for (var packId in packedAssets) {var packedIds = packedAssets[packId];for (var j = 0; j < packedIds.length; ++j) {if (typeof packedIds[j] === 'number') {packedIds[j] = uuids[packedIds[j]];}}}}// init enginevar canvas;if (cc.sys.isBrowser) {canvas = document.getElementById('GameCanvas');}if (false) {var ORIENTATIONS = {'portrait': 1,'landscape left': 2,'landscape right': 3};BK.Director.screenMode = ORIENTATIONS[settings.orientation];initAdapter();}function setLoadingDisplay () {// Loading splash scenevar splash = document.getElementById('splash');var progressBar = splash.querySelector('.progress-bar span');cc.loader.onProgress = function (completedCount, totalCount, item) {var percent = 100 * completedCount / totalCount;if (progressBar) {progressBar.style.width = percent.toFixed(2) + '%';}};splash.style.display = 'block';progressBar.style.width = '0%';cc.director.once(cc.Director.EVENT_AFTER_SCENE_LAUNCH, function () {splash.style.display = 'none';});}var onStart = function () {cc.loader.downloader._subpackages = settings.subpackages;if (false) {BK.Script.loadlib();}cc.view.resizeWithBrowserSize(true);if (!false && !false) {if (cc.sys.isBrowser) {setLoadingDisplay();}if (cc.sys.isMobile) {if (settings.orientation === 'landscape') {cc.view.setOrientation(cc.macro.ORIENTATION_LANDSCAPE);}else if (settings.orientation === 'portrait') {cc.view.setOrientation(cc.macro.ORIENTATION_PORTRAIT);}cc.view.enableAutoFullScreen([cc.sys.BROWSER_TYPE_BAIDU,cc.sys.BROWSER_TYPE_WECHAT,cc.sys.BROWSER_TYPE_MOBILE_QQ,cc.sys.BROWSER_TYPE_MIUI,].indexOf(cc.sys.browserType) < 0);}// Limit downloading max concurrent task to 2,// more tasks simultaneously may cause performance draw back on some android system / browsers.// You can adjust the number based on your own test result, you have to set it before any loading process to take effect.if (cc.sys.isBrowser && cc.sys.os === cc.sys.OS_ANDROID) {cc.macro.DOWNLOAD_MAX_CONCURRENT = 2;}}// init assetscc.AssetLibrary.init({libraryPath: 'res/import',rawAssetsBase: 'res/raw-',rawAssets: settings.rawAssets,packedAssets: settings.packedAssets,md5AssetsMap: www.sychzs.cn5AssetsMap});if (false) {cc.Pipeline.Downloader.PackDownloader._doPreload("WECHAT_SUBDOMAIN", settings.WECHAT_SUBDOMAIN_DATA);}var launchScene = settings.launchScene;// load scenecc.director.loadScene(launchScene, null,function () {if (cc.sys.isBrowser) {// show canvascanvas.style.visibility = '';var div = document.getElementById('GameDiv');if (div) {div.style.backgroundImage = '';}}cc.loader.onProgress = null;console.log('Success to load scene: ' + launchScene);});};// jsListvar jsList = settings.jsList;if (!false) {var bundledScript = settings.debug ? 'src/project.dev.js' : 'src/project.js';if (jsList) {jsList = www.sychzs.cn(function (x) {return 'src/' + x;});jsList.push(bundledScript);}else {jsList = [bundledScript];}}// anysdk scriptsif (cc.sys.isNative && cc.sys.isMobile) {
//            jsList = jsList.concat(['src/anysdk/jsb_anysdk.js', 'src/anysdk/jsb_anysdk_constants.js']);}var option = {//width: width,//height: height,id: 'GameCanvas',scenes: settings.scenes,debugMode: settings.debug ? www.sychzs.cn : cc.DebugMode.ERROR,showFPS: (!false && !false) && settings.debug,frameRate: 60,jsList: jsList,groupList: settings.groupList,collisionMatrix: settings.collisionMatrix,renderMode: 0}www.sychzs.cn(option, onStart);}if (false) {BK.Script.loadlib('GameRes://libs/qqplay-adapter.js');BK.Script.loadlib('GameRes://src/settings.js');BK.Script.loadlib();BK.Script.loadlib('GameRes://libs/qqplay-downloader.js');qqPlayDownloader.REMOTE_SERVER_ROOT = "";var prevPipe = www.sychzs.cn5Pipe || cc.loader.assetLoader;cc.loader.insertPipeAfter(prevPipe, qqPlayDownloader);// boot();return;}if (false) {require(window._CCSettings.debug ? 'cocos2d-js.js' : 'cocos2d-js-min.js');require('./libs/weapp-adapter/engine/index.js');var prevPipe = www.sychzs.cn5Pipe || cc.loader.assetLoader;cc.loader.insertPipeAfter(prevPipe, wxDownloader);boot();return;}if (window.jsb) {require('src/settings.js');require('src/jsb_polyfill.js');boot();return;}if (window.document) {var splash = document.getElementById('splash');splash.style.display = 'block';var cocos2d = document.createElement('script');cocos2d.async = true;cocos2d.src = window._CCSettings.debug ? 'cocos2d-js.js' : 'cocos2d-js-min.js';var engineLoaded = function () {document.body.removeChild(cocos2d);cocos2d.removeEventListener('load', engineLoaded, false);if (typeof VConsole !== 'undefined') {window.vConsole = new VConsole();}boot();};cocos2d.addEventListener('load', engineLoaded, false);document.body.appendChild(cocos2d);}})();

修改好main.js后,就可以编辑项目安装到手机上了,接着修改项目,换个图片,保存然后构建,不用选MD5 Cache,构建成功后,修改version_generator.js版本号改为1.0.1,然后执行 node version_generator.js,然后把构建后的src、res和生成的project.manifest、version.manifest放在服务端,比如:
通过mac模拟服务器 python -m SimpleHTTPServer 8000

 

启动好后,就可以打开App安装测试了。

 

 

 

 

 

 

相关文章