先标记上文章的出处,嘿休嘿,感谢这位:https://www.jianshu.com/p/fe54ca980384
cocos creator版本: 1.9.1
首先建立大厅的项目
添加文件 SubgameManager .js 文件内容如下,该文件管理的是子游戏的更新
const SubgameManager = {
_storagePath: [],
_getfiles: function(name, type, downloadCallback, finishCallback) {
this._storagePath[name] = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'ALLGame/' + name);
this._downloadCallback = downloadCallback;
this._finishCallback = finishCallback;
this._fileName = name;
/// 替换该地址
var UIRLFILE = "http://192.168.1.15/subGame/" + name + "/";
var filees = this._storagePath[name] + '/project.manifest';
var customManifestStr = JSON.stringify({
'packageUrl': UIRLFILE,
'remoteManifestUrl': UIRLFILE + 'project.manifest',
'remoteVersionUrl': UIRLFILE + 'version.manifest',
'version': '0.0.1',
'assets': {},
'searchPaths': []
});
var versionCompareHandle = function(versionA, 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;
}
};
this._am = new jsb.AssetsManager('', this._storagePath[name], versionCompareHandle);
if (!cc.sys.ENABLE_GC_FOR_NATIVE_OBJECTS) {
this._am.retain();
}
this._am.setVerifyCallback(function(path, asset) {
var compressed = asset.compressed;
if (compressed) {
return true;
} else {
return true;
}
});
if (cc.sys.os === cc.sys.OS_ANDROID) {
this._am.setMaxConcurrentTask(2);
}
if (type === 1) {
this._updateListener = new jsb.EventListenerAssetsManager(this._am, this._updateCb.bind(this));
} else if (type == 2) {
this._updateListener = new jsb.EventListenerAssetsManager(this._am, this._checkCb.bind(this));
} else {
this._updateListener = new jsb.EventListenerAssetsManager(this._am, this._needUpdate.bind(this));
}
cc.eventManager.addListener(this._updateListener, 1);
if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
var manifest = new jsb.Manifest(customManifestStr, this._storagePath[name]);
this._am.loadLocalManifest(manifest, this._storagePath[name]);
}
if (type === 1) {
this._am.update();
this._failCount = 0;
} else {
this._am.checkUpdate();
}
this._updating = true;
console.log('更新文件:' + filees);
},
// type = 1
_updateCb: function(event) {
var failed = false;
let self = this;
switch (event.getEventCode()) {
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
/*0 本地没有配置文件*/
console.log('updateCb本地没有配置文件');
failed = true;
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
/*1下载配置文件错误*/
console.log('updateCb下载配置文件错误');
failed = true;
break;
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
/*2 解析文件错误*/
console.log('updateCb解析文件错误');
failed = true;
break;
case jsb.EventAssetsManager.NEW_VERSION_FOUND:
/*3发现新的更新*/
console.log('updateCb发现新的更新');
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
/*4 已经是最新的*/
console.log('updateCb已经是最新的');
failed = true;
break;
case jsb.EventAssetsManager.UPDATE_PROGRESSION:
/*5 最新进展 */
self._downloadCallback && self._downloadCallback(event.getPercentByFile());
break;
case jsb.EventAssetsManager.ASSET_UPDATED:
/*6需要更新*/
break;
case jsb.EventAssetsManager.ERROR_UPDATING:
/*7更新错误*/
console.log('updateCb更新错误');
break;
case jsb.EventAssetsManager.UPDATE_FINISHED:
/*8更新完成*/
self._finishCallback && self._finishCallback(true);
break;
case jsb.EventAssetsManager.UPDATE_FAILED:
/*9更新失败*/
self._failCount++;
if (self._failCount <= 3) {
self._am.downloadFailedAssets();
console.log(('updateCb更新失败' + this._failCount + ' 次'));
} else {
console.log(('updateCb失败次数过多'));
self._failCount = 0;
failed = true;
self._updating = false;
}
break;
case jsb.EventAssetsManager.ERROR_DECOMPRESS:
/*10解压失败*/
console.log('updateCb解压失败');
break;
}
if (failed) {
cc.eventManager.removeListener(self._updateListener);
self._updateListener = null;
self._updating = false;
self._finishCallback && self._finishCallback(false);
}
},
// type = 2
_checkCb: function(event) {
var failed = false;
let self = this;
switch (event.getEventCode()) {
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
/*0 本地没有配置文件*/
console.log('checkCb本地没有配置文件');
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
/*1下载配置文件错误*/
console.log('checkCb下载配置文件错误');
failed = true;
break;
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
/*2 解析文件错误*/
console.log('checkCb解析文件错误');
failed = true;
break;
case jsb.EventAssetsManager.NEW_VERSION_FOUND:
/*3发现新的更新*/
self._getfiles(self._fileName, 1, self._downloadCallback, self._finishCallback);
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
/*4 已经是最新的*/
console.log('checkCb已经是最新的');
self._finishCallback && self._finishCallback(true);
break;
case jsb.EventAssetsManager.UPDATE_PROGRESSION:
/*5 最新进展 */
break;
case jsb.EventAssetsManager.ASSET_UPDATED:
/*6需要更新*/
break;
case jsb.EventAssetsManager.ERROR_UPDATING:
/*7更新错误*/
console.log('checkCb更新错误');
failed = true;
break;
case jsb.EventAssetsManager.UPDATE_FINISHED:
/*8更新完成*/
console.log('checkCb更新完成');
break;
case jsb.EventAssetsManager.UPDATE_FAILED:
/*9更新失败*/
console.log('checkCb更新失败');
failed = true;
break;
case jsb.EventAssetsManager.ERROR_DECOMPRESS:
/*10解压失败*/
console.log('checkCb解压失败');
break;
}
this._updating = false;
if (failed) {
self._finishCallback && self._finishCallback(false);
}
},
// type = 3
_needUpdate: function(event) {
let self = this;
switch (event.getEventCode()) {
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
console.log('子游戏已经是最新的,不需要更新');
self._finishCallback && self._finishCallback(false);
break;
case jsb.EventAssetsManager.NEW_VERSION_FOUND:
console.log('子游戏需要更新');
self._finishCallback && self._finishCallback(true);
break;
// 检查是否更新出错
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
case jsb.EventAssetsManager.ERROR_UPDATING:
case jsb.EventAssetsManager.UPDATE_FAILED:
self._downloadCallback();
break;
}
},
/**
* 下载子游戏
* @param {string} name - 游戏名
* @param progress - 下载进度回调
* @param finish - 完成回调
* @note finish 返回true表示下载成功,false表示下载失败
*/
downloadSubgame: function(name, progress, finish) {
this._getfiles(name, 2, progress, finish);
},
/**
* 进入子游戏
* @param {string} name - 游戏名
*/
enterSubgame: function(name) {
if (!this._storagePath[name]) {
this.downloadSubgame(name);
return;
}
console.log("点击进入子游戏!");
setTimeout(() => {
console.log("200ms之后!");
if(jsb.fileUtils.isFileExist(this._storagePath[name] + '/src'))
{
console.log("subGame_src存在");
}
else
{
console.log("subGame_src不存在");
}
if(jsb.fileUtils.isFileExist(this._storagePath[name] + '/src/main.js'))
{
console.log("subGame_main.js存在");
}
else
{
console.log("subGame_main.js不存在");
}
window.require(this._storagePath[name] + '/src/main.js');
}, 200);
},
/**
* 判断子游戏是否已经下载
* @param {string} name - 游戏名
*/
isSubgameDownLoad: function (name) {
let file = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'ALLGame/' + name + '/project.manifest';
if (jsb.fileUtils.isFileExist(file)) {
console.log("子游戏已经下载!");
return true;
} else {
return false;
}
},
/**
* 判断子游戏是否需要更新
* @param {string} name - 游戏名
* @param isUpdateCallback - 是否需要更新回调
* @param failCallback - 错误回调
* @note isUpdateCallback 返回true表示需要更新,false表示不需要更新
*/
needUpdateSubgame: function (name, isUpdateCallback, failCallback) {
this._getfiles(name, 3, failCallback, isUpdateCallback);
},
};
module.exports = SubgameManager;
然后再添加个EnterSubGame.js文件,挂在场景节点上,添加按钮,按钮的回调为 ClickSubGameBtn,js文件内容如下
const SubgameManager = require('SubgameManager');
cc.Class({
extends: cc.Component,
properties: {
downloadLabel: {
default: null,
type: cc.Label
}
},
onLoad: function () {
const name = 'ddz';
//判断子游戏有没有下载
if (SubgameManager.isSubgameDownLoad(name)) {
//已下载,判断是否需要更新
SubgameManager.needUpdateSubgame(name, (success) => {
if (success) {
this.downloadLabel.string = "子游戏需要更新";
} else {
this.downloadLabel.string = "子游戏不需要更新";
}
}, () => {
console.log('出错了');
});
} else {
this.downloadLabel.string = "子游戏未下载";
}
},
ClickSubGameBtn: function()
{
//下载子游戏/更新子游戏
SubgameManager.downloadSubgame("ddz", (progress) => {
if (isNaN(progress)) {
progress = 0;
}
this.downloadLabel.string = "资源下载中 " + parseInt(progress * 100) + "%";
}, function(success) {
if (success) {
SubgameManager.enterSubgame('ddz');
} else {
console.log('下载失败');
}
});
},
});
然后创建一个子游戏的项目,添加version_generator.js,文件内容如下:
/**
* 此模块用于热更新工程清单文件的生成
*/
var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
var manifest = {
//服务器上资源文件存放路径(src,res的路径)
packageUrl: 'http://192.168.1.15/hall/remote-assets/',
//服务器上project.manifest路径
remoteManifestUrl: 'http://192.168.1.15/hall/remote-assets/project.manifest',
//服务器上version.manifest路径
remoteVersionUrl: 'http://192.168.1.15/hall/remote-assets/version.manifest',
version: '1.0.4',
assets: {},
searchPaths: []
};
//生成的manifest文件存放目录
var dest = 'manifest/';
//项目构建后资源的目录
var src = 'build/jsb-link/';
/**
* 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 Bytes
size = 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');
});
添加子游戏的入口main.js,和返回大厅的入口dating.js,main.js内容如下,dating.js内容只需要稍稍进行修改就行:
(function () {
if (window.jsb) {
/// 1.初始化资源Lib路径Root.
var subgameSearchPath = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/')+'ALLGame/ddz/';
// dating.js 改成 var subgameSearchPath = "assets"; 就可以了 ios的需要更改为:
// var subgameSearchPath = jsb.reflection.callStaticMethod("AppController", "getHallPath")+"/";
// 然后在AppController类下加入方法getHallPath:
// (NSString *)getHallPath
// {
// return [[NSBundle mainBundle] bundlePath];
// }
/// 2.subgame资源未映射,则初始化资源映射表,否则略过映射. 我这里没有添加 会造成内存泄漏 你们要加一下哦
//if(!cc.HallAndSubGameGlobal.subgameGlobal){
//cc.HallAndSubGameGlobal.subgameGlobal = {};
var subGameGlobal = {};
/// 加载settings.js
require(subgameSearchPath + 'src/settings.js');
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 asset
if (typeof type === 'number') {
entry[1] = assetTypes[type];
}
// retrieve uuid
realEntries[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]];
}
}
}
}
/// 加载project.js
var projectDir = 'src/project.js';
if ( settings.debug ) {
projectDir = 'src/project.dev.js';
}
window.require(subgameSearchPath + projectDir);
/// 如果当前搜索路径没有subgame,则添加进去搜索路径。
var currentSearchPaths = jsb.fileUtils.getSearchPaths();
if(currentSearchPaths && currentSearchPaths.indexOf(subgameSearchPath) === -1){
jsb.fileUtils.addSearchPath(subgameSearchPath, true);
console.log('subgame main.js 之前未添加,添加下subgameSearchPath' + currentSearchPaths);
}
cc.AssetLibrary.init({
libraryPath: 'res/import',
rawAssetsBase: 'res/raw-',
rawAssets: settings.rawAssets,
packedAssets: settings.packedAssets,
md5AssetsMap: settings.md5AssetsMap
});
/* cc.HallAndSubGameGlobal. */subGameGlobal.launchScene = settings.launchScene;
/// 将subgame的场景添加到cc.game中,使得cc.director.loadScene可以从cc.game._sceneInfos查找到相关场景
for(var i = 0; i < settings.scenes.length; ++i){
cc.game._sceneInfos.push(settings.scenes[i]);
}
}
console.log("加载场景!");
/// 3.加载初始场景
var launchScene = /* cc.HallAndSubGameGlobal. */subGameGlobal.launchScene;
cc.director.loadScene(launchScene, null, function () {
console.log('subgame main.js 成功加载初始场景' + launchScene);
}
);
// }
})();
在子游戏中创建个按钮,按钮的回调函数为:
//因为我只弄了个ddz的,所以这边是ddz
window.require(jsb.fileUtils.getWritablePath() + "ALLGame/ddz/src/dating.js");
//这里需要注意下,子游戏的 脚本加密密钥 要跟 大厅的 脚本加密密钥 是一致的才可以
然后构建子游戏的项目,构建完成之后,把 自己创建的 main.js 和 dating.js 放进资源的 src 文件夹下,再去运行生成manifest的脚本,一定要先放文件再生成,然后把文件放到服务器上,使用大厅的项目达成apk包就可以测试了。
如果自己在本地测试的话,可以安装下 http-server 或者 tomcat 来使用局域网进行下载资源。