预先创建一个res/config/version_info.json文件
创建这个文件的目的是为了生成project.manifest和version.manifest做数据准备
配置好包的地址
配置好版本号
配置好project和version远程文件的地址
{
"packageUrl" : "http://10.225.14.23:8081/wwwCYH/update/LuaTest009/assets/",
"remoteManifestUrl" : "http://10.225.14.23:8081/wwwCYH//update/LuaTest009/version/project.manifest",
"remoteVersionUrl" : "http://10.225.14.23:8081/wwwCYH//update/LuaTest009/version/version.manifest",
"version" : "1.0.0",
"engineVersion" : "cocos2dx3.10"
}
加密脚本和资源的python代码
此处的加密是依靠路径和资源内容作为加密对象,这样可以保证资源的唯一性
#coding:utf-8
import os
import sys
import json
import hashlib
import subprocess
import getpass
username = getpass.getuser()
# 改变当前工作目录
# os.chdir('/Users/' + username + '/Documents/client/MyProj/')
os.chdir("F:/cocosWork/LuaTest002/")
assetsDir = {
#MyProj文件夹下需要进行热跟的文件夹
"searchDir" : ["src", "res"],
#需要忽略的文件夹
"ignorDir" : ["cocos", "framework", ".svn"],
#需要忽略的文件
"ignorFile":[".DS_Store"],
}
versionConfigFile = "res/config/version_info.json" #版本信息的配置文件路径
versionManifestPath = "res/version/version.manifest" #由此脚本生成的version.manifest文件路径
projectManifestPath = "res/version/project.manifest" #由此脚本生成的project.manifest文件路径
# projectManifestPath = "/Users/ximi/Documents/client/MyProj/res/version/project.manifest" #由此脚本生成的project.manifest文件路径(mac机)
class SearchFile:
def __init__(self):
self.fileList = []
for k in assetsDir:
if (k == "searchDir"):
for searchdire in assetsDir[k]:
self.recursiveDir(searchdire)
def recursiveDir(self, srcPath):
''' 递归指定目录下的所有文件'''
dirList = [] #所有文件夹
files = os.listdir(srcPath) #返回指定目录下的所有文件,及目录(不含子目录)
for f in files:
#目录的处理
if (os.path.isdir(srcPath + '/' + f)):
if (f[0] == '.' or (f in assetsDir["ignorDir"])):
#排除隐藏文件夹和忽略的目录
pass
else:
#添加非需要的文件夹
dirList.append(f)
#文件的处理
elif (os.path.isfile(srcPath + '/' + f)) and (f not in assetsDir["ignorFile"]):
self.fileList.append(srcPath + '/' + f) #添加文件
#遍历所有子目录,并递归
for dire in dirList:
#递归目录下的文件
self.recursiveDir(srcPath + '/' + dire)
def getAllFile(self):
''' get all file path'''
return tuple(self.fileList)
def CalcMD5(filepath):
"""generate a md5 code by a file path"""
with open(filepath,'rb') as f:
md5obj = hashlib.md5()
md5obj.update(f.read())
return md5obj.hexdigest()
def getVersionInfo():
'''get version config data'''
configFile = open(versionConfigFile,"r")
json_data = json.load(configFile)
configFile.close()
# json_data["version"] = json_data["version"] + '.' + str(GetSvnCurrentVersion())
json_data["version"] = json_data["version"]
return json_data
def GenerateVersionManifestFile():
''' 生成大版本的version.manifest'''
json_str = json.dumps(getVersionInfo(), indent = 2)
fo = open(versionManifestPath,"w")
fo.write(json_str)
fo.close()
def GenerateProjectManifestFile():
searchfile = SearchFile()
fileList = list(searchfile.getAllFile())
project_str = {}
project_str.update(getVersionInfo())
dataDic = {}
for f in fileList:
dataDic[f] = {"md5" : CalcMD5(f)}
print f
project_str.update({"assets":dataDic})
json_str = json.dumps(project_str, sort_keys = True, indent = 2)
fo = open(projectManifestPath,"w")
fo.write(json_str)
fo.close()
if __name__ == "__main__":
GenerateVersionManifestFile()
GenerateProjectManifestFile()
实现热更的代码:
local function assetsUpdate()
local writablePath = cc.FileUtils:getInstance():getWritablePath()
local storagePath = writablePath .. "new_version"
--将下载目录的src和res作为优先级最高的搜索目录,这样才能保证下载的能覆盖原来的代码
cc.FileUtils:getInstance():addSearchPath(storagePath.."/src/",true)
cc.FileUtils:getInstance():addSearchPath(storagePath.."/res/",true)
print("下载的文件地址--"..storagePath)
-- 创建AssetsManagerEx对象
local assetsManagerEx = cc.AssetsManagerEx:create("src/version/project.manifest", storagePath)
assetsManagerEx:retain()
-- 设置下载消息listener
local function handleAssetsManagerEx(event)
if (cc.EventAssetsManagerEx.EventCode.ALREADY_UP_TO_DATE == event:getEventCode()) then
print("已经是最新版本了,进入游戏主界面")
require("app.MyApp"):create():run()
end
if (cc.EventAssetsManagerEx.EventCode.NEW_VERSION_FOUND == event:getEventCode()) then
print("发现新版本,开始升级")
end
if (cc.EventAssetsManagerEx.EventCode.UPDATE_PROGRESSION == event:getEventCode()) then
print("更新进度=" .. event:getPercent())
end
if (cc.EventAssetsManagerEx.EventCode.UPDATE_FINISHED == event:getEventCode()) then
print("更新完毕,重新启动")
require("app.MyApp"):create():run()
end
if (cc.EventAssetsManagerEx.EventCode.ERROR_NO_LOCAL_MANIFEST == event:getEventCode()) then
print("发生错误:本地找不到manifest文件")
end
if (cc.EventAssetsManagerEx.EventCode.ERROR_DOWNLOAD_MANIFEST == event:getEventCode()) then
print("发生错误:下载manifest文件失败")
end
if (cc.EventAssetsManagerEx.EventCode.ERROR_PARSE_MANIFEST == event:getEventCode()) then
print("发生错误:解析manifest文件失败")
end
if (cc.EventAssetsManagerEx.EventCode.ERROR_UPDATING == event:getEventCode()) then
print("发生错误:更新失败")
end
end
local dispatcher = cc.Director:getInstance():getEventDispatcher()
local eventListenerAssetsManagerEx = cc.EventListenerAssetsManagerEx:create(assetsManagerEx, handleAssetsManagerEx)
dispatcher:addEventListenerWithFixedPriority(eventListenerAssetsManagerEx, 1)
-- 检查版本并升级
assetsManagerEx:update()
end
对于热更源码的解读:
AssetsManagerEx:在创建它的时候需要给他一个本地的project.manifest,至于路径可以自己定义,我这里存放的是src/version/project.manifest,该类利用这个数据做一个原版的比较对象,当你第一次热更以后,这个路径下的project.manifest就没用了,因为下载目下也有一个更新版本的project.manifest,之后都以下载目录下作为比较对象,至于为啥没有用是因为我们修改了require文件路径的优先级
AssetsManagerEx:update();当调用这个函数的时候,就开始下载资源服务器version.manifest,比较版本号,如果发现本地的版本号小于资源服的版本,则下载资源服的project.manifest,然后那本地的资源(名字,MD5)和刚下载下来的资源(名字,MD5)进行比较,此处有三个结果,删除,修改,添加,最后将这些差异放在一个数组里去处理(删除或者下载资源),特别注意当发现资源服上没有,本地有的话,这里的删除只能删下载目录中的资源,游戏包中的资源是你所无法删除的
远程部署:
每一次版本更新 都要执行对本地全部资源的MD5加密,然后将本地资源全部考到资源服
总结:
当要做版本更新的时候,要在本地关闭热更功能,用本地资源跑最新游戏,然后对本地资源进行MD5加密,最后修改版本号,把本地全部资源上传资源服
本地开发最新客户端-----------------------------------------------------------------------------》玩家手中的旧版本客户端
上传 下载
资源服 -----------------------------------------------------------------------------》android缓存