转载至: http://blog.csdn.net/pt_xxj/article/details/68927705

为什么还要再写一篇关于cocos2d js热更新的笔记,最单纯的想法就是记录心得,另外也是因为添加了一个记录热更新资源大小的小功能,故而想分享一下。

在cocos2d js引擎中的js是作为一种特殊的资源文件使用(脚本文件),那么游戏运行过程中通过定制的资源管理器从服务器获取新的js文件并重新加载运行,那么也就实现了看似比较玄妙的热更新功能。当然此资源管理器需要具备资源对比、资源下载、资源处理、资源覆盖及资源加载等等的一系列功能,幸运的是cocos已经实现了这些功能,我们需要做的就是学会配置使用而已。

此热更新基于cocos2d-js 3.6.1测试使用。

————————————————朴实无华分割线,以上内容不重要————————————————

cocos2d js的热更新功能主要由jsb.AssetsManager实现,绑定了引擎底层的C++代码AssetsManager类,具体的逻辑和实现都可以查看此类。具体的热更新的是通过一个预先配置的project.manifest以及服务器端的project.manifest和version.manifest配合使用来实现的。

1、热更新系统实现

初始project.manifest:

{
"packageUrl": "http://192.168.0.73:8080/projectName/std/",
"remoteManifestUrl": "http://192.168.0.73:8080/projectName/std/project.manifest",
"remoteVersionUrl": "http://192.168.0.73:8080/projectName/std/version.manifest",
"version": "1.0.0",
"groupVersions" :
{
},
"groupSizes" :
{
},
"engineVersion": "3.6.1",
"assets" :
{
},
"searchPaths":
[
]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

具有两个版本增量更新的服务器project.manifest:

{
"packageUrl": "http://192.168.0.73:8080/projectName/std/",
"remoteManifestUrl": "http://192.168.0.73:8080/projectName/std/project.manifest",
"remoteVersionUrl": "http://192.168.0.73:8080/projectName/std/version.manifest",
"version": "1.1.2",
"groupVersions" :
{
"1":"1.1.1",
"2":"1.1.2"
},
"groupSizes" :
{
"1":115.0,
"2":115.0
},
"engineVersion": "3.6.1",
"assets" :
{
"update1":
{
"path":"update1.zip",
"md5":"",
"compressed" : true,
"group":"1"
},
"update2":
{
"path":"update2.zip",
"md5":"",
"compressed" : true,
"group":"2"
}
},
"searchPaths":
[
"update1/",
"update2/"
]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

具有两个版本增量更新的服务器version.manifest:

{
"packageUrl": "http://192.168.0.73:8080/projectName/std/",
"remoteManifestUrl": "http://192.168.0.73:8080/projectName/std/project.manifest",
"remoteVersionUrl": "http://192.168.0.73:8080/projectName/std/version.manifest",
"version": "1.1.2",
"groupVersions" :
{
"1":"1.1.1",
"2":"1.1.2"
},
"groupSizes" :
{
"1":115.0,
"2":115.0
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

此热更新配置文件我添加了groupSizes字段,用于展示更新资源文件大小,方便用户玩家自主选择更新时机。另外配置的时候按照渠道划分的,这个是std是标准渠道的热更新文件配置。至于其他字段网路上已经有太多的讲解,就不再重复。

资源文件配置完毕,那么就是具体的使用了,我使用了一个js的静态类来实现热更新的检测以及更新过程,我看其他人的实现一般都是直接强制更新,个人感觉不太友好,所以我的热更新实现是分步进行的。

hotfix-manager.js

/**
* 热更新管理器
*/
var HotFixManager = HotFixManager || { // 更新资源管理器
_mAssetsMgr:null,
_mTryCount:0, // 更新基本配置
_mManiFest:"",
_mWritePath:"",
_mTryTimes:1, // 更新过程进度回调函数
_mUpdateCall:null,
_mUpdatetarget:null, // 更新完成回调
_mFinishCall:null,
_mFinishtarget:null, // 更新已找到标识
_mHadFind:false, /**
* 热更新系统初始化
* @param call 更新完成回调函数
* @param target 回调函数绑定节点
*/
init:function(call,target)
{
this._mFinishCall = call;
this._mFinishtarget = target; // 这一部分的配置使用也可以通过传递参数进行实现
this._mManiFest = "res/static/project.manifest";
this._mWritePath = jsb.fileUtils?jsb.fileUtils.getWritablePath()+"hotFix/":"./hotFix/";
this._mTryTimes = 1; this._mAssetsMgr = new jsb.AssetsManager(this._mManiFest, this._mWritePath);
this._mAssetsMgr.retain(); cc.eventManager.addListener(new jsb.EventListenerAssetsManager(this._mAssetsMgr,this._eventListener.bind(this)),1);
},
_eventListener:function(event)
{
cc.log("assetsMgr:state = %s;event:id = %s,code = %s,message = %s",
this._mAssetsMgr.getState(),event.getAssetId(),
event.getEventCode(),JSON.stringify(event.getMessage())); switch(event.getEventCode())
{
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: // 0
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: // 1
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST: // 2
case jsb.EventAssetsManager.ERROR_UPDATING: // 7
case jsb.EventAssetsManager.ERROR_DECOMPRESS: // 10
// 此处可以另加一个this.onError(event.getEventCode())操作,不过个人感觉没必要,直接直接做失败结束处理
this.onFinish(false);
break; case jsb.EventAssetsManager.UPDATE_FINISHED: // 8
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: // 4
this.onFinish(true);
break; // 资源下载完成,进行游戏内资源更新(包括资源解压等)
case jsb.EventAssetsManager.ASSET_UPDATED: // 6
break; // 此处checkUpdate()和 update()都会触发新版本发现操作(区别在于前者到此处任务已结束,后者会继续后续资源下载操作)
case jsb.EventAssetsManager.NEW_VERSION_FOUND: // 3
this._onFind();
break; case jsb.EventAssetsManager.UPDATE_PROGRESSION: // 5
// 状态值8代表热更新资源下载中,添加此判断是筛选更新下载进度
if(this._mAssetsMgr.getState() == 8)
{
cc.log(event.getPercent());
this._onUpdate(event.getPercent());
}
break; case jsb.EventAssetsManager.UPDATE_FAILED: // 9
this._mTryCount++;
if (this._mTryCount < this._mTryTimes)
{
this._mAssetsMgr.downloadFailedAssets();
}
else
{
this._mTryCount = 0;
this.onFinish(false);
}
break;
default:break;
}
},
/**
* 检测是否有热更新资源
*/
checkUpdate:function()
{
this._mAssetsMgr.checkUpdate();
},
_onFind:function()
{
// 系统在进行正式更新时,在下载project.manifest后仍旧会再次回调此处回调
if(this._mHadFind)
{
return;
}
this._mHadFind = true; // 计算热更新资源大小,需在cocos提供的文件配置基础上自主增加groupSizes字段
var updateSize = 0.0;
try
{
var vSize = 0.0;
var pSize = 0.0; var versionString = jsb.fileUtils.getStringFromFile(this._mWritePath+"version.manifest");
var projectString = jsb.fileUtils.getStringFromFile(this._mWritePath+"project.manifest"); if(versionString)
{
var vJson = JSON.parse(versionString);
for(var it in vJson.groupSizes)
{
vSize += parseFloat(vJson.groupSizes[it]);
}
} if(projectString)
{
var pJson = JSON.parse(projectString);
for(var it in pJson.groupSizes)
{
pSize += parseFloat(pJson.groupSizes[it]);
}
} updateSize = vSize - pSize;
}
catch (e)
{
cc.log(e);
} var layer = new HotFixFindLayer(updateSize);
cc.director.getRunningScene().addChild(layer);
},
/**
* 执行热更新
*/
doUpdate:function()
{
this._mAssetsMgr.update();
},
/**
* 设置热更新下载进度回调
* @param call
* @param target
*/
setUpdateCall:function(call,target)
{
this._mUpdateCall = call;
this._mUpdatetarget = target;
},
_onUpdate:function(percent)
{
this._mUpdateCall&&this._mFinishtarget&&this._mFinishtarget.runAction(cc.callFunc(this._mUpdateCall, this._mFinishtarget, percent));
},
/**
* 热更新结束
* @param success bool更新是否成功
*/
onFinish:function(success)
{
this._mFinishCall&&this._mFinishtarget&&this._mFinishtarget.runAction(cc.callFunc(this._mFinishCall, this._mFinishtarget, success));
this._mAssetsMgr.release();
this._mHadFind = false;
}
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
/**
* 热更新找到选择对话框,主要考虑到资源比较大,又不是强制更新的情况
*/
var HotFixFindLayer = cc.Layer.extend({
ctor:function(updateSize)
{
this._super(); var popBoxBg = new cc.LayerColor(cc.color(0, 0, 0, 200),this.width*0.75, this.height*0.15);
popBoxBg.setPosition(this.width*0.5-popBoxBg.width/2, this.height*0.5-popBoxBg.height/2);
this.addChild(popBoxBg); var HotUpdateFindDesc = new ccui.Text("从游戏服务器检测到有"+updateSize+"M资源更新,是够立即进行更新操作?", "Arial", 28);
HotUpdateFindDesc.setContentSize(popBoxBg.width*0.85, popBoxBg.height*0.7);
HotUpdateFindDesc.setPosition(popBoxBg.width*0.5, popBoxBg.height*0.5);
HotUpdateFindDesc.ignoreContentAdaptWithSize(false);
popBoxBg.addChild(HotUpdateFindDesc); var sureItem = new cc.MenuItemLabel(ccui.Text("立即更新", "Arial", 28),function(){ HotFixManager.doUpdate();
popBoxBg.removeAllChildren(true); var percentDesc = new ccui.Text("更新进度:0%", "Arial", 32);
percentDesc.setAnchorPoint(0,0.5);
percentDesc.setPosition(popBoxBg.width*0.25, popBoxBg.height*0.5);
popBoxBg.addChild(percentDesc); HotFixManager.setUpdateCall(function(target,data){
percentDesc.setString(cc.formatStr("更新进度:%s%",parseInt(data)));
if(parseInt(data) == 100)
{
this.removeFromParent(true);
HotFixManager.setUpdateCall();
}
},this); },this); var sureMenu = new cc.Menu(sureItem);
sureMenu.setPosition(popBoxBg.width*0.25, popBoxBg.height*0.2)
popBoxBg.addChild(sureMenu); var cancelItem = new cc.MenuItemLabel(ccui.Text("下次更新", "Arial", 28),function(){
this.removeFromParent(true);
HotFixManager.onFinish(false);
},this); var cancelMenu = new cc.Menu(cancelItem);
cancelMenu.setPosition(popBoxBg.width*0.75, popBoxBg.height*0.2)
popBoxBg.addChild(cancelMenu);
}
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

其实这个更新操作的实现方式不是特别符合设计模式,在HotFixManager的_onFind函数尾部

var layer = new HotFixFindLayer(updateSize);
cc.director.getRunningScene().addChild(layer);
  • 1
  • 2

是不该直接实现界面显示的,应该通过使用时自己定制一个弹窗界面然后再根据玩家选择调用HotFixManager对应的函数进行操作,不过我的初始想法就是让使用尽量方便,所以我尽量去掉了资源的使用,当成了一个系统内的控件进行了尽量的封装操作。

2、热更新系统使用

新建一个scene使用即可hotfix-scene.js:

var HotFixScene = cc.Scene.extend({
ctor:function()
{
this._super(); var layer = new cc.Layer();
this.addChild(layer); var loadPoster = new cc.Sprite("res/static/poster.png");
loadPoster.setPosition(layer.width*0.5,layer.height*0.5)
loadPoster.setScale(layer.height/loadPoster.height);
layer.addChild(loadPoster); // 热更新系统使用
HotFixManager.init(this._updateFinish,this);
HotFixManager.checkUpdate();
},
_updateFinish:function()
{
var delay_func = function()
{
// js文件列表文件,此文件在热更新过程中应该保持路径不变
// 因为此处是写死的,不太好改变,除非你自己在热更新配置一个字段,用于保存此路径
var jsFile = "src/dynamic/js-list.js"; // 加载js文件列表
cc.loader.loadJs(jsFile, function(){ // 加载js文件列表中的js文件
cc.loader.loadJs(jsList, function(){ // 跳转到正式游戏scene
cc.director.runScene(new GameScene());
});
});
} // 此操作是为了避开当前帧跳转,不然界面会有卡顿感觉
this.scheduleOnce(delay_func, 0.05);
}
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

热更新系统的使用是特别简单的,只需要在main.js的cc.game.onStart函数执行

cc.director.runScene(new HotFixScene());
  • 1

就可以了。当然这两个文件你要配置在project.json中。

3、热更新系统应用于已完成项目

其实我真正想做到的是作为一个插件使用,对于已经完成的项目,要添加此热更新插件特别简单,只需要做几件事情就可以了:

1、将原有的project.json中的js文件列表写进”src/dynamic/js-list.js”文件的var jsList=[]数组中(jsList名字对应于HotFixScene中的jsList)。

2、将上述两个文件加入游戏,并将其路径配置到project.json。

3、配置自己的热更新文件配置(包括搭建自己的服务器),修改HotFixManager.init()中的读写目录(若跟我的目录一致则不用改写)。

4、修改热更新结束后的scene跳转为已实现的文件跳转。

// 跳转到正式游戏scene
cc.director.runScene(new GameScene());
  • 1
  • 2

5、测试运行。

————————————————朴实无华分割线,以下内容不重要————————————————

下面是两张实际运行图:(背景图借用皇室战争的海报图)

选择界面: 

资源更新中界面: 

结束

为什么还要再写一篇关于cocos2d js热更新的笔记,最单纯的想法就是记录心得,另外也是因为添加了一个记录热更新资源大小的小功能,故而想分享一下。

在cocos2d js引擎中的js是作为一种特殊的资源文件使用(脚本文件),那么游戏运行过程中通过定制的资源管理器从服务器获取新的js文件并重新加载运行,那么也就实现了看似比较玄妙的热更新功能。当然此资源管理器需要具备资源对比、资源下载、资源处理、资源覆盖及资源加载等等的一系列功能,幸运的是cocos已经实现了这些功能,我们需要做的就是学会配置使用而已。

此热更新基于cocos2d-js 3.6.1测试使用。

————————————————朴实无华分割线,以上内容不重要————————————————

cocos2d js的热更新功能主要由jsb.AssetsManager实现,绑定了引擎底层的C++代码AssetsManager类,具体的逻辑和实现都可以查看此类。具体的热更新的是通过一个预先配置的project.manifest以及服务器端的project.manifest和version.manifest配合使用来实现的。

1、热更新系统实现

初始project.manifest:

{
"packageUrl": "http://192.168.0.73:8080/projectName/std/",
"remoteManifestUrl": "http://192.168.0.73:8080/projectName/std/project.manifest",
"remoteVersionUrl": "http://192.168.0.73:8080/projectName/std/version.manifest",
"version": "1.0.0",
"groupVersions" :
{
},
"groupSizes" :
{
},
"engineVersion": "3.6.1",
"assets" :
{
},
"searchPaths":
[
]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

具有两个版本增量更新的服务器project.manifest:

{
"packageUrl": "http://192.168.0.73:8080/projectName/std/",
"remoteManifestUrl": "http://192.168.0.73:8080/projectName/std/project.manifest",
"remoteVersionUrl": "http://192.168.0.73:8080/projectName/std/version.manifest",
"version": "1.1.2",
"groupVersions" :
{
"1":"1.1.1",
"2":"1.1.2"
},
"groupSizes" :
{
"1":115.0,
"2":115.0
},
"engineVersion": "3.6.1",
"assets" :
{
"update1":
{
"path":"update1.zip",
"md5":"",
"compressed" : true,
"group":"1"
},
"update2":
{
"path":"update2.zip",
"md5":"",
"compressed" : true,
"group":"2"
}
},
"searchPaths":
[
"update1/",
"update2/"
]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

具有两个版本增量更新的服务器version.manifest:

{
"packageUrl": "http://192.168.0.73:8080/projectName/std/",
"remoteManifestUrl": "http://192.168.0.73:8080/projectName/std/project.manifest",
"remoteVersionUrl": "http://192.168.0.73:8080/projectName/std/version.manifest",
"version": "1.1.2",
"groupVersions" :
{
"1":"1.1.1",
"2":"1.1.2"
},
"groupSizes" :
{
"1":115.0,
"2":115.0
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

此热更新配置文件我添加了groupSizes字段,用于展示更新资源文件大小,方便用户玩家自主选择更新时机。另外配置的时候按照渠道划分的,这个是std是标准渠道的热更新文件配置。至于其他字段网路上已经有太多的讲解,就不再重复。

资源文件配置完毕,那么就是具体的使用了,我使用了一个js的静态类来实现热更新的检测以及更新过程,我看其他人的实现一般都是直接强制更新,个人感觉不太友好,所以我的热更新实现是分步进行的。

hotfix-manager.js

/**
* 热更新管理器
*/
var HotFixManager = HotFixManager || { // 更新资源管理器
_mAssetsMgr:null,
_mTryCount:0, // 更新基本配置
_mManiFest:"",
_mWritePath:"",
_mTryTimes:1, // 更新过程进度回调函数
_mUpdateCall:null,
_mUpdatetarget:null, // 更新完成回调
_mFinishCall:null,
_mFinishtarget:null, // 更新已找到标识
_mHadFind:false, /**
* 热更新系统初始化
* @param call 更新完成回调函数
* @param target 回调函数绑定节点
*/
init:function(call,target)
{
this._mFinishCall = call;
this._mFinishtarget = target; // 这一部分的配置使用也可以通过传递参数进行实现
this._mManiFest = "res/static/project.manifest";
this._mWritePath = jsb.fileUtils?jsb.fileUtils.getWritablePath()+"hotFix/":"./hotFix/";
this._mTryTimes = 1; this._mAssetsMgr = new jsb.AssetsManager(this._mManiFest, this._mWritePath);
this._mAssetsMgr.retain(); cc.eventManager.addListener(new jsb.EventListenerAssetsManager(this._mAssetsMgr,this._eventListener.bind(this)),1);
},
_eventListener:function(event)
{
cc.log("assetsMgr:state = %s;event:id = %s,code = %s,message = %s",
this._mAssetsMgr.getState(),event.getAssetId(),
event.getEventCode(),JSON.stringify(event.getMessage())); switch(event.getEventCode())
{
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: // 0
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: // 1
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST: // 2
case jsb.EventAssetsManager.ERROR_UPDATING: // 7
case jsb.EventAssetsManager.ERROR_DECOMPRESS: // 10
// 此处可以另加一个this.onError(event.getEventCode())操作,不过个人感觉没必要,直接直接做失败结束处理
this.onFinish(false);
break; case jsb.EventAssetsManager.UPDATE_FINISHED: // 8
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: // 4
this.onFinish(true);
break; // 资源下载完成,进行游戏内资源更新(包括资源解压等)
case jsb.EventAssetsManager.ASSET_UPDATED: // 6
break; // 此处checkUpdate()和 update()都会触发新版本发现操作(区别在于前者到此处任务已结束,后者会继续后续资源下载操作)
case jsb.EventAssetsManager.NEW_VERSION_FOUND: // 3
this._onFind();
break; case jsb.EventAssetsManager.UPDATE_PROGRESSION: // 5
// 状态值8代表热更新资源下载中,添加此判断是筛选更新下载进度
if(this._mAssetsMgr.getState() == 8)
{
cc.log(event.getPercent());
this._onUpdate(event.getPercent());
}
break; case jsb.EventAssetsManager.UPDATE_FAILED: // 9
this._mTryCount++;
if (this._mTryCount < this._mTryTimes)
{
this._mAssetsMgr.downloadFailedAssets();
}
else
{
this._mTryCount = 0;
this.onFinish(false);
}
break;
default:break;
}
},
/**
* 检测是否有热更新资源
*/
checkUpdate:function()
{
this._mAssetsMgr.checkUpdate();
},
_onFind:function()
{
// 系统在进行正式更新时,在下载project.manifest后仍旧会再次回调此处回调
if(this._mHadFind)
{
return;
}
this._mHadFind = true; // 计算热更新资源大小,需在cocos提供的文件配置基础上自主增加groupSizes字段
var updateSize = 0.0;
try
{
var vSize = 0.0;
var pSize = 0.0; var versionString = jsb.fileUtils.getStringFromFile(this._mWritePath+"version.manifest");
var projectString = jsb.fileUtils.getStringFromFile(this._mWritePath+"project.manifest"); if(versionString)
{
var vJson = JSON.parse(versionString);
for(var it in vJson.groupSizes)
{
vSize += parseFloat(vJson.groupSizes[it]);
}
} if(projectString)
{
var pJson = JSON.parse(projectString);
for(var it in pJson.groupSizes)
{
pSize += parseFloat(pJson.groupSizes[it]);
}
} updateSize = vSize - pSize;
}
catch (e)
{
cc.log(e);
} var layer = new HotFixFindLayer(updateSize);
cc.director.getRunningScene().addChild(layer);
},
/**
* 执行热更新
*/
doUpdate:function()
{
this._mAssetsMgr.update();
},
/**
* 设置热更新下载进度回调
* @param call
* @param target
*/
setUpdateCall:function(call,target)
{
this._mUpdateCall = call;
this._mUpdatetarget = target;
},
_onUpdate:function(percent)
{
this._mUpdateCall&&this._mFinishtarget&&this._mFinishtarget.runAction(cc.callFunc(this._mUpdateCall, this._mFinishtarget, percent));
},
/**
* 热更新结束
* @param success bool更新是否成功
*/
onFinish:function(success)
{
this._mFinishCall&&this._mFinishtarget&&this._mFinishtarget.runAction(cc.callFunc(this._mFinishCall, this._mFinishtarget, success));
this._mAssetsMgr.release();
this._mHadFind = false;
}
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
/**
* 热更新找到选择对话框,主要考虑到资源比较大,又不是强制更新的情况
*/
var HotFixFindLayer = cc.Layer.extend({
ctor:function(updateSize)
{
this._super(); var popBoxBg = new cc.LayerColor(cc.color(0, 0, 0, 200),this.width*0.75, this.height*0.15);
popBoxBg.setPosition(this.width*0.5-popBoxBg.width/2, this.height*0.5-popBoxBg.height/2);
this.addChild(popBoxBg); var HotUpdateFindDesc = new ccui.Text("从游戏服务器检测到有"+updateSize+"M资源更新,是够立即进行更新操作?", "Arial", 28);
HotUpdateFindDesc.setContentSize(popBoxBg.width*0.85, popBoxBg.height*0.7);
HotUpdateFindDesc.setPosition(popBoxBg.width*0.5, popBoxBg.height*0.5);
HotUpdateFindDesc.ignoreContentAdaptWithSize(false);
popBoxBg.addChild(HotUpdateFindDesc); var sureItem = new cc.MenuItemLabel(ccui.Text("立即更新", "Arial", 28),function(){ HotFixManager.doUpdate();
popBoxBg.removeAllChildren(true); var percentDesc = new ccui.Text("更新进度:0%", "Arial", 32);
percentDesc.setAnchorPoint(0,0.5);
percentDesc.setPosition(popBoxBg.width*0.25, popBoxBg.height*0.5);
popBoxBg.addChild(percentDesc); HotFixManager.setUpdateCall(function(target,data){
percentDesc.setString(cc.formatStr("更新进度:%s%",parseInt(data)));
if(parseInt(data) == 100)
{
this.removeFromParent(true);
HotFixManager.setUpdateCall();
}
},this); },this); var sureMenu = new cc.Menu(sureItem);
sureMenu.setPosition(popBoxBg.width*0.25, popBoxBg.height*0.2)
popBoxBg.addChild(sureMenu); var cancelItem = new cc.MenuItemLabel(ccui.Text("下次更新", "Arial", 28),function(){
this.removeFromParent(true);
HotFixManager.onFinish(false);
},this); var cancelMenu = new cc.Menu(cancelItem);
cancelMenu.setPosition(popBoxBg.width*0.75, popBoxBg.height*0.2)
popBoxBg.addChild(cancelMenu);
}
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

其实这个更新操作的实现方式不是特别符合设计模式,在HotFixManager的_onFind函数尾部

var layer = new HotFixFindLayer(updateSize);
cc.director.getRunningScene().addChild(layer);
  • 1
  • 2

是不该直接实现界面显示的,应该通过使用时自己定制一个弹窗界面然后再根据玩家选择调用HotFixManager对应的函数进行操作,不过我的初始想法就是让使用尽量方便,所以我尽量去掉了资源的使用,当成了一个系统内的控件进行了尽量的封装操作。

2、热更新系统使用

新建一个scene使用即可hotfix-scene.js:

var HotFixScene = cc.Scene.extend({
ctor:function()
{
this._super(); var layer = new cc.Layer();
this.addChild(layer); var loadPoster = new cc.Sprite("res/static/poster.png");
loadPoster.setPosition(layer.width*0.5,layer.height*0.5)
loadPoster.setScale(layer.height/loadPoster.height);
layer.addChild(loadPoster); // 热更新系统使用
HotFixManager.init(this._updateFinish,this);
HotFixManager.checkUpdate();
},
_updateFinish:function()
{
var delay_func = function()
{
// js文件列表文件,此文件在热更新过程中应该保持路径不变
// 因为此处是写死的,不太好改变,除非你自己在热更新配置一个字段,用于保存此路径
var jsFile = "src/dynamic/js-list.js"; // 加载js文件列表
cc.loader.loadJs(jsFile, function(){ // 加载js文件列表中的js文件
cc.loader.loadJs(jsList, function(){ // 跳转到正式游戏scene
cc.director.runScene(new GameScene());
});
});
} // 此操作是为了避开当前帧跳转,不然界面会有卡顿感觉
this.scheduleOnce(delay_func, 0.05);
}
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

热更新系统的使用是特别简单的,只需要在main.js的cc.game.onStart函数执行

cc.director.runScene(new HotFixScene());
  • 1

就可以了。当然这两个文件你要配置在project.json中。

3、热更新系统应用于已完成项目

其实我真正想做到的是作为一个插件使用,对于已经完成的项目,要添加此热更新插件特别简单,只需要做几件事情就可以了:

1、将原有的project.json中的js文件列表写进”src/dynamic/js-list.js”文件的var jsList=[]数组中(jsList名字对应于HotFixScene中的jsList)。

2、将上述两个文件加入游戏,并将其路径配置到project.json。

3、配置自己的热更新文件配置(包括搭建自己的服务器),修改HotFixManager.init()中的读写目录(若跟我的目录一致则不用改写)。

4、修改热更新结束后的scene跳转为已实现的文件跳转。

// 跳转到正式游戏scene
cc.director.runScene(new GameScene());
  • 1
  • 2

5、测试运行。

————————————————朴实无华分割线,以下内容不重要————————————————

下面是两张实际运行图:(背景图借用皇室战争的海报图)

选择界面: 

资源更新中界面: 

结束

Cocos2d-js 热更新学习笔记的更多相关文章

  1. Lua的热更新学习笔记_01

    热更新的的实现方式 1.使用lua脚本编写游戏的UI或者其他的逻辑 2.使用C#的反射技术 3.使用C#Light AssetBundle是什么? 1.unity提供一个资源更新技术,就是通过Asse ...

  2. Underscore.js 源码学习笔记(下)

    上接 Underscore.js 源码学习笔记(上) === 756 行开始 函数部分. var executeBound = function(sourceFunc, boundFunc, cont ...

  3. Underscore.js 源码学习笔记(上)

    版本 Underscore.js 1.9.1 一共 1693 行.注释我就删了,太长了… 整体是一个 (function() {...}());  这样的东西,我们应该知道这是一个 IIFE(立即执行 ...

  4. 【09-23】js原型继承学习笔记

    js原型继承学习笔记 function funcA(){ this.a="prototype a"; } var b=new funcA(); b.a="object a ...

  5. 浏览器中js执行机制学习笔记

    浏览器中js执行机制学习笔记 RiverSouthMan关注 0.0772019.05.15 20:56:37字数 872阅读 291 同步任务 当一个脚本第一次执行的时候,js引擎会解析这段代码,并 ...

  6. 纯JS实现KeyboardNav(学习笔记)一

    纯JS实现KeyboardNav(学习笔记)一 这篇博客只是自己的学习笔记,供日后复习所用,没有经过精心排版,也没有按逻辑编写 GitHub项目源码 预览地址 最终效果 KeyboardNav使用指南 ...

  7. 纯JS实现KeyboardNav(学习笔记)二

    纯JS实现KeyboardNav(学习笔记)二 这篇博客只是自己的学习笔记,供日后复习所用,没有经过精心排版,也没有按逻辑编写 这篇主要是添加css,优化js编写逻辑和代码排版 GitHub项目源码 ...

  8. [360前端星计划]BlackJack(21点)(纯JS,附总部学习笔记)

    [360前端星计划]总部学习笔记(6/6) [360前端星计划]详情跳转 游戏界面预览 目录 一.游戏介绍 1.起源 2.规则 3.技巧 二.游戏设计 1.整体UI构思 2.素材采集 3.游戏总规划 ...

  9. JS第一周学习笔记整理

    目录 JS正式课第一周笔记整理 JS正式课第一周笔记整理 webstorm : 代码编辑器 浏览器: 代码解析器: Git : 是一个工具;用于团队协作开发项目管理代码的工具:在工作中用git.svn ...

随机推荐

  1. The CHECK_POLICY and CHECK_EXPIRATION options cannot be turned OFF when MUST_CHANGE is ON. (Microsoft SQL Server,错误: 15128)

    记录下 The CHECK_POLICY and CHECK_EXPIRATION options cannot be turned OFF when MUST_CHANGE is ON. (Micr ...

  2. Ubuntu下常用的快捷键

    熟练地快捷键操作可以大大的节省我们的时间,下面贴上一些快捷键的操作: 桌面常用快捷键 Alt + F1:聚焦到桌面左侧任务导航栏,可按上下键进行导航 Alt + F2:运行命令 Alt + F4:关闭 ...

  3. Linux下安装scrapy包出错

    pip install -i https://pypi.douban.com/simple/ scrapy 出现错误: error: command 将依赖包装全. sudo apt-get inst ...

  4. Tomcat部署项目后有括号的处理方法

    常见的问题,收录整理了一下,方便查找. 如下3个地方都修改为一致即可解决. 1,右键项目名 --> properties --> 输入web project settings --> ...

  5. 2018-2019-2 20165210《网络对抗技术》Exp6 信息搜集与漏洞扫描

    2018-2019-2 20165210<网络对抗技术>Exp6 信息搜集与漏洞扫描 一.实验目标: 掌握信息搜集的最基础技能与常用工具的使用方法. 二.实验内容: 各种搜索技巧的应用 G ...

  6. 重构代码 —— 函数即变量(Replace temp with Query)

    函数即变量,这里的函数指的是返回值为某一对象的函数.Replace temp with query,query 是一种查询函数. example 1 double price() { return t ...

  7. OpenCV-Python在图片上输出中文

    OpenCV中在图片上输出中文一般需要借助FreeType库实现.FreeType库是一个完全免费(开源)的.高质量的且可移植的字体引擎,它提供统一的接口来访问多种字体格式文件.但使用FreeType ...

  8. nodejs 不同请求获取前端传的参数

    get方法 参数在req.query中获取 router.get('/', function(req, res, next) { console.log("reqquery:",r ...

  9. linux下redis服务器安装使用 安装php的redis扩展 安装laravel下的redis

    linux下redis服务器安装使用 学习源头: https://blog.csdn.net/itmanba/article/details/77335012 安装完毕试运行redis的时候,可能会出 ...

  10. html5 日常小结

    HTML5新标签汇总 1.  html5新的 (input type=类型) 元素 <input type="number" name="quantity" ...