cocos的热更新是基于原始文件列表和远程文件列表的md5对比。 如果有多个远程资源库,可以作为包投递解决方案。 大概的流程是这样的:
1.确定外包策略
首先,规划应将动态加载的资源按照一定的策略划分为若干个包。 比如游戏等级,80级之前一包,120级之前一包,200级之前一包; 或级别。 这个逻辑需要通过规划来确定。 前期不会用到的资源放在中期包中,中期不会用到的资源放在后期资源包中。
如果你想去GooglePlay,限制是100M空包,可以将第一个包做成一个没有任何动态加载资源的空包,上传到GooglePlay,播放器下载并开始更新第一个包的资源。 相比obb承包的用处,统一采用热更新的方式,不需要研究obb相关技术,但iOS也可以使用这种策略。缺点:首先需要提供自己的远程资源服务器,如果您有大量用户,则需要 CDN; 其次,登录游戏需要下载大量资源,玩家可能会流失; 第三,热更新需要上传到服务器的资源很多,因为多个资源包分别对应一个服务器资源空包,每个资源都需要上传到服务器
第二代外包资源
契约分配策略确定后,让规划者填写一份不同包资源对应的配置表,然后需要编写脚本根据计划配置表形成下一生不同的资源包。 如:p0-空包、p1-第一个资源包、p2-第二个资源包,以此类推。 下面的脚本根据配置表sub_pack_and_config生成不同的包。
def gen_update_assets(update_path, black_list):
"""
将工程目录的 res 和 src 拷贝到 update_path
"""
print "[make version] => copy asset to update directory", update_path
print "black list len", len(black_list)
project_path = version.get_project_path()
assert_path = os.path.join(update_path, "assets")
if not os.path.exists(assert_path):
os.makedirs(assert_path)
src_path = os.path.join(assert_path, "src")
if os.path.exists(src_path):
shutil.rmtree(src_path)
res_path = os.path.join(assert_path, "res")
if os.path.exists(res_path):
shutil.rmtree(res_path)
opfile.copy_dir(os.path.join(project_path, "src"), src_path, black_list)
opfile.copy_dir(os.path.join(project_path, "res"), res_path, black_list)
def get_black(pack_list):
black = {}
for i, sub_name in enumerate(pack_list):
cfg = sub_pack_and_config[sub_name]
black_dir_list = cfg["resList"]
black[sub_name] = meta.get_uuid_list_from_path(black_dir_list)
return black
def replace_native(update_path):
plat = version.get_platform()
project_path = version.get_project_path()
# android 母包用 p0 的资源, ios 母包用 p1 资源
sub_name = "p0" if plat == "android" else "p1"
update_path_pack = os.path.join(update_path, sub_name, "assets")
src_path = os.path.join(project_path, "src")
if os.path.exists(src_path):
shutil.rmtree(src_path)
src_path_p = os.path.join(update_path_pack, "src")
shutil.copytree(src_path_p, src_path)
res_path = os.path.join(project_path, "res")
if os.path.exists(res_path):
shutil.rmtree(res_path)
res_path_p = os.path.join(update_path_pack, "res")
shutil.copytree(res_path_p, res_path)
def build():
pack_list = sub_pack_and_config.keys()
pack_list.sort()
black = get_black(pack_list)
update_path = version.get_update_path()
url_root = version.get_update_path()
for i, sub_name in enumerate(pack_list):
black_list = black.get(sub_name)
is_update = i > 0
pack_path = os.path.join(update_path, sub_name)
update_url = url_root + (sub_name if is_update else "p1") + "/"
ver = version.get_version(i)
print "\nversion", ver
print "update_url", update_url
gen_update_assets(pack_path, black_list)
manifest.gen_manifest(ver, update_url, pack_path)
print "\n"
is_update = version.get_is_update()
if not is_update:
pack_path = os.path.join(update_path, "p0")
replace_native(update_path)
manifest.replace_manifest(pack_path)
mainjs.change_main_js()
三生成对比文件列表
签约资源可用后,根据资源的来世生成文件列表清单。 这个过程和热更新生成manifest的过程是一样的,只是比较的文件目录变成了上一步生成的通用包资源。 这里需要注意的是版本号。 由于热更新首先会比较版本号,所以需要保证下一个包的版本号较高。 即使侧面热更新后,也应该低于热更新后的版本号。 建议版本号这样设置:游戏的主要版本。 父包的版本。
第一次更新后,只需更新包版本号即可:
如果重新打开父包:
这样就保证了后续的版本号必须低于之前的版本号,保证资源可以热更新。
下面的脚本根据资源生成一个清单。 官方的是js,这里是python。 我们使用python来实现手动打包过程。
def gen_manifest(ver, update_url, update_path):
"""
生成热更新的资源
update_url 服务器资源 url
update_path 本地资源存放路径
:return:
"""
print "[make version] => make hot update assets",
manifest = {
'packageUrl': update_url + "assets/",
'remoteManifestUrl': update_url + 'project.manifest',
'remoteVersionUrl': update_url + 'version.manifest',
'version': ver,
'assets': {},
'searchPaths': [],
}
print "packageUrl", manifest["packageUrl"]
print "remoteManifestUrl", manifest["remoteManifestUrl"]
print "remoteVersionUrl", manifest["remoteVersionUrl"]
update_path = os.path.normpath(update_path)
assert_path = os.path.join(update_path, "assets")
for root, dirs, files in os.walk(assert_path):
assets = manifest["assets"]
for filename in files:
path = os.path.join(root, filename)
rel_path = path[len(assert_path) + 1:].replace('\\', '/')
assets[rel_path] = {'size': opfile.get_file_size(path), 'md5': opfile.get_file_md5(path)}
# 热更新远程文件,需要上传到资源服务器上
remote_manifest_path = os.path.join(update_path, 'project.manifest')
opfile.write_json_file(remote_manifest_path, manifest)
# 去掉 assets 和 searchPaths,即为 version 文件内容
manifest.pop("assets")
manifest.pop("searchPaths")
remote_version_path = os.path.join(update_path, 'version.manifest')
opfile.write_json_file(remote_version_path, manifest)
print "local update asset path", assert_path
print "local update manifest", remote_manifest_path
print "local update version", remote_version_path
def replace_manifest(update_path):
"""
:param update_path: 新 manifest 所在路径
:return:
"""
root = version.get_client_root()
client_manifest = root + "assets/project.manifest"
project_path = root + "build/jsb-default"
assert_path = os.path.join(update_path, "assets")
remote_manifest_path = os.path.join(update_path, 'project.manifest')
print "[manifest] => use manifest", remote_manifest_path
# 替换 client 中的 manifest
# shutil.copy(remote_manifest_path, client_manifest)
# print "[manifest] => replace manifest", client_manifest
# 替换原生工程中的 manifest
uuid = meta.get_uuid_form_meta(client_manifest)
dirname = uuid[0:2]
project_manifest_path = os.path.join(project_path, "res", "raw-assets", dirname, uuid + ".manifest")
shutil.copy(remote_manifest_path, project_manifest_path)
print "[manifest] => replace manifest", project_manifest_path
# 替换热更新资源中的 manifest
update_manifest_path = os.path.join(assert_path, "res", "raw-assets", dirname, uuid + ".manifest")
shutil.copy(remote_manifest_path, update_manifest_path)
print "[manifest] => replace manifest", update_manifest_path
四次触发下一个数据包
当玩家达到一定条件时,触发风暴,更改玩家本地的manifest文件,然后进行热更新流程下载资源。 需要注意的是,玩家的本地清单有两个路径。 如果没有更新过,则是打包后asset目录下的project.manifest; 如果已更新,则将远程manifest文件下载到热更新目录中。 向下。 因此,在更改本地manifest文件时,需要进行一些判断。 下面提供了一封辞职信,这是我们之前项目中使用的。
LocalCmd.sub = function(args, localManifestPath) {
DebugLog("cmd: test, arg:", args);
let update_url = StrUtils.format("{0}{1}/p{2}/", HttpUtils.URL_ROOT, MsicUtils.getNameOS(), args);
let ret = HttpUtils.changeManifest(update_url, localManifestPath);
if (ret) {
cc.game.restart();
}
return "ok";
};
/**
* 修改热更新地址,用于更新分包下载
* */
HttpUtils.changeManifest = function(update_url, localManifestPath) {
if(!window.jsb){
return;
}
DebugLog("update url", update_url);
DebugLog("hot update path", Const.hotUpdatePath);
DebugLog("local manifest path", localManifestPath);
try {
let manifestPath = localManifestPath;
let downloadManifest = Const.hotUpdatePath + "project.manifest";
if (jsb.fileUtils.isFileExist(downloadManifest)) {
DebugLog("remote manifest");
manifestPath = downloadManifest
} else {
DebugLog("local manifest", localManifestPath);
if (!localManifestPath) {
return;
}
// 没有热更过要新建一下热更新目录
if (!jsb.fileUtils.isDirectoryExist(Const.hotUpdatePath)) {
jsb.fileUtils.createDirectory(Const.hotUpdatePath);
}
}
DebugLog("last manifest path", manifestPath);
let loadManifest = jsb.fileUtils.getStringFromFile(manifestPath);
let manifestObject = JSON.parse(loadManifest);
manifestObject.packageUrl = update_url + "assets/";
manifestObject.remoteManifestUrl = update_url + "project.manifest";
manifestObject.remoteVersionUrl = update_url + "version.manifest";
let afterString = JSON.stringify(manifestObject);
let isWritten = jsb.fileUtils.writeStringToFile(afterString, Const.hotUpdatePath + '/project.manifest');
DebugLog("Written Status : ", isWritten);
return isWritten;
} catch (error) {
DebugLog("change manifest error", error.message || error);
return false;
}
};
五项热点更新
更改热更新文件并重新启动游戏后,就会进入正常的热更新流程。 热更新完成后,再次重启即可进入游戏。
整个计划是这样的。 考虑到每次下载都会有损失,尤其是GooglePlay下载空包后,第一次需要下载好几G的资源,这是不可接受的。 最后我们使用obb来发送包,这样也节省了下载和发送包的带宽。 成本。
以上内容均来自网络搜集,如有侵权联系客服删除