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

新建Hall工程

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);this.am = new jsb.AssetsManager("", 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) {cc.log("Verification passed : " + relativePath);return true;}else {cc.log("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);cc.log("Max concurrent tasks count have been limited to 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 (cc.loader.md5Pipe) {url = cc.loader.md5Pipe.transformURL(url);}this.am.loadLocalManifest(url);}if (!this.am.getLocalManifest() || !this.am.getLocalManifest().isLoaded()) {cc.log('Failed to load local manifest ...');return;}this.checkUpdateListener = new jsb.EventListenerAssetsManager(this.am, this.checkCallback.bind(this));cc.eventManager.addListener(this.checkUpdateListener, 1);this.am.checkUpdate();this.isUpdating = true;}private static checkCallback(event) {cc.log('Code: ' + event.getEventCode());switch (event.getEventCode()) {case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:cc.log("No local manifest file found, hot update skipped.");break;case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:cc.log("Fail to download manifest file, hot update skipped.");break;case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:cc.log("Already up to date with the latest remote version.");break;case jsb.EventAssetsManager.NEW_VERSION_FOUND:cc.log('New version found, please try to update.');if (this.checkNewVersionListener) {this.checkNewVersionListener();}break;default:return;}cc.eventManager.removeListener(this.checkUpdateListener);this.checkUpdateListener = null;this.isUpdating = false;}public static update(updateProgressListener?:Function,updateErrorListener?:Function,updateFinishListener?:Function) {if (this.am && !this.isUpdating) {this.updateProgressListener = updateProgressListener;this.updateErrorListener = updateErrorListener;this.updateFinishListener = updateFinishListener;this.updateListener = new jsb.EventListenerAssetsManager(this.am, this.updateCallback.bind(this));cc.eventManager.addListener(this.updateListener, 1);if (this.am.getState() === jsb.AssetsManager.State.UNINITED) {// Resolve md5 urlvar url = this.manifestUrl;if (cc.loader.md5Pipe) {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('No local manifest file found, hot update skipped.');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('Fail to download manifest file, hot update skipped.');if(this.updateErrorListener){this.updateFinishListener('Fail to download manifest file, hot update skipped.');}failed = true;break;case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:cc.log("Already up to date with the latest remote version.");failed = true;break;case jsb.EventAssetsManager.UPDATE_FINISHED:cc.log('Update finished. ' + event.getMessage());needRestart = true;if(this.updateFinishListener){this.updateFinishListener();}break;case jsb.EventAssetsManager.UPDATE_FAILED:cc.log('Update failed. ' + event.getMessage());if(this.updateErrorListener){this.updateErrorListener('Update failed. ' + event.getMessage());}this.isUpdating = false;break;case jsb.EventAssetsManager.ERROR_UPDATING:cc.log('Asset update error: ' + event.getAssetId() + ', ' + event.getMessage());if(this.updateErrorListener){this.updateErrorListener('Asset update error: ' + event.getAssetId() + ', ' + event.getMessage());}break;case jsb.EventAssetsManager.ERROR_DECOMPRESS:cc.log(event.getMessage());break;default:break;}if (failed) {cc.eventManager.removeListener(this.updateListener);this.updateListener = null;this.isUpdating = false;}if (needRestart) {cc.eventManager.removeListener(this.updateListener);this.updateListener = null;// Prepend the manifest's search pathvar 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);}}
}

使用脚本

import { HotUpdateUtil } from "./HotUpdateUtil";const {ccclass, property} = cc._decorator;@ccclass
export default class HotUpdate extends 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(){this.updateTip.active = true;HotUpdateUtil.update((progress)=>{let temp = ~~(progress * 100);this.updateTip.getComponentInChildren(cc.Label).string = "下载中" + temp + "%";},null,()=>{this.finishUpdateTip.active = true;});}private restart(){cc.game.restart();}}

生成manifest文件

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

image.png

然后,开始构建项目,不要勾选MD5 Cache,以Android为例,模板为default,构建完成后,在项目的根目录下放文件version_generator.js,里面的路径根据需要修改:

/*** 此模块用于热更新工程清单文件的生成*/var fs = require('fs');
var path = require('path');
var crypto = require('crypto');var manifest = {//服务器上资源文件存放路径(src,res的路径)packageUrl: 'http://192.168.0.136:8000',//服务器上project.manifest路径remoteManifestUrl: 'http://192.168.0.136:8000/project.manifest',//服务器上version.manifest路径remoteVersionUrl: 'http://192.168.0.136:8000/version.manifest',version: '1.0.0',assets: {},searchPaths: []
};//生成的manifest文件存放目录
var dest = 'assets/';
//项目构建后资源的目录
var src = 'build/jsb-default/';/*** node version_generator.js -v 1.0.0 -u http://your-server-address/tutorial-hot-update/remote-assets/ -s native/package/ -d assets/*/
// Parse arguments
var i = 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 manifest.searchPaths;
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: settings.md5AssetsMap});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 = jsList.map(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 ? cc.DebugMode.INFO : cc.DebugMode.ERROR,showFPS: (!false && !false) && settings.debug,frameRate: 60,jsList: jsList,groupList: settings.groupList,collisionMatrix: settings.collisionMatrix,renderMode: 0}cc.game.run(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 = cc.loader.md5Pipe || cc.loader.assetLoader;cc.loader.insertPipeAfter(prevPipe, qqPlayDownloader);// <plugin script code>boot();return;}if (false) {require(window._CCSettings.debug ? 'cocos2d-js.js' : 'cocos2d-js-min.js');require('./libs/weapp-adapter/engine/index.js');var prevPipe = cc.loader.md5Pipe || 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安装测试了。

CocosCreator热更新(v1.10.2)相关推荐

  1. cocoscreator热更新

    首先要看一下官方文档 官方热更新范例 官方热更新文档 官方热更新API 官方 version_generator.js 文件 首先我们要熟悉一下官方的热更新文档,了解热更新机制.其次自己要去通过实践来 ...

  2. [CocosCreator]热更新插件使用心得以及注意事项

    最近在搞热更新这块,琢磨了一段时间,终于搞明白怎么使用插件去做热更新了,此文章将记录我使用过程中遇到的坑,和使用心得,希望能对萌新有一定的帮助! (老规矩:广告位留给自己)         欢迎喜欢或 ...

  3. Cocos Creater 热更新

    转载自:https://www.jianshu.com/p/a033059f376d 惯例先上官方文档: http://docs.cocos.com/creator/manual/zh/advance ...

  4. vue项目 热更新慢

    vue项目 --- 热更新慢 一.查找热更新慢是哪里慢---分析原因 二.解决办法 1.安装babel-plugin-dynamic-import-node插件 2..babelrc文件里添加配置dy ...

  5. CocosCreator大厅+子游戏+热更新方案

    转载自:https://www.jianshu.com/p/efee9f5937a3 前言 随着游戏的玩法越来越多,也就意味着包体越来越大,对于玩家来说,首次下载的包体就会越来越大,从而也会增加首次启 ...

  6. cocoscreator2.0.10 热更新大厅子游戏模式 学习使用记录

    热更新主要用到cocos2d的热更新模块RawAsset    AssetsManager 先按教程写个demo 创建大厅空项目 ,添加场景helloworld ,如下图在场景中加热更需要的控件 ch ...

  7. CocosCreator 2.4.3热更新实现方案(AssetBundle),大厅+子游戏模式快速实现

    _ 实现功能 项目环境 关于 Asset Bundle 实现过程 工程结构 快速使用代码 构建发布 Creator 构建 制作热更新资源 制作随包发布模块 测试功能 模块完整下载 windows下测试 ...

  8. CocosCreator游戏热更新完整教程,超简单,超详细

    使用cocos已经是第7个年头了,也算是老司机了,今天就介绍下使用cocos creator开发游戏如何热更新. 预备知识 首先,科普下基础知识,热更新的基础原理是,不同版本的游戏资源对应不同的man ...

  9. 技术分享|集成开放平台使用Consul Watch机制实现配置热更新

    源宝导读:在微服务架构体系中,由于微服务众多,服务之间又有互相调用关系,因此,一个通用的分布式配置管理是必不可少的.本文将介绍如何使用Consul Watch机制实现配置集中管理与热更新. 前言 随着 ...

最新文章

  1. 第十六届全国大学智能汽车竞赛竞速比赛规则
  2. Bzoj 3122 随机数生成器
  3. exfat最佳单元大小_047|仓储物流自动化系统中的物料单元
  4. 二十三、 爬取mzsock网站写真社区
  5. [转]C#与数据结构--树论--平衡二叉树(AVL Tree)
  6. UTF-8 可变编码格式
  7. weblogic概览下的上下文根配置_Weblogic服务下获取上下文路劲问题
  8. JavaScript生成指定范围内的随机数
  9. 电商5个流程的用户体验
  10. daab 3.1使用笔记
  11. 【linux学习笔记八】常用命令
  12. C语言fwrite()与Java writeFloat()数据转换
  13. 金色圣诞幻灯片AE模板
  14. 最最简单的一个爬虫代码
  15. python爬取一条新闻内容_一个爬取近百万数据的Python爬虫
  16. 高德地图开发-- 自定义图标
  17. “创药网”-创新药领域专业资讯网站​
  18. 人均 3.6万行代码, C++ 成最烫手山药:腾讯首度披露技术研发数据!
  19. oracle序列号的使用
  20. 导航栏不变,切换局部页面的方法

热门文章

  1. 二开WP黑金壁纸小程序源码+实测可用
  2. 川大计算机生物学双学位,【学长学姐对你说】计算机双学位经验分享
  3. 关于SpringCloud的所有笔记
  4. MySQL分库分表面试知识点
  5. 盘点2017年崛起的那些 JS 项目
  6. jQuery css() 方法
  7. 阿里云 语音服务-国内语音服务
  8. 正则表达式替换不包含指定头尾
  9. 为什么用于打游戏的计算机需要独立显卡,玩游戏,教给您一些购买独立显卡的注意事项...
  10. 创造性能不能教?(转)