游戏开发(九) 之 纯 lua 版 热更新 方案

大概看了cocos和quick的更新方案,也挺不错的,但是感觉不太适合自己使用,所以就自己写一套

最早做的时候,我使用的是在c++端完全控制,把需要下载的资源以及lua文件下载完成后再调用engine->executeScriptFile("src/main.lua") lua端完全不用处理任何事情

然后这次因为重新开发,索性选了纯lua的热更方案,与c++不同的是,你要考虑到代码引用的问题,在更新之前,加载最基础的lua更新文件,然后进行更新,更新结束后再继续走lua的逻辑流程.为了方便平时使用,我是单个文件进行下载,没有打包ZIP,后续可以修改成zip下载,现在抓一些重点说一下

整体更新流程:

  1. 服务端制作md5比对文件生成列表(代码链接)(这个表要在客户端第一次上线的时候对客户端代码生成一次,放进客户端)
  2. 将md5的配置表和更新的资源,放入到文件下载服务器中(有一个傻瓜式的软件:HFS,很好用)
  3. 客户端启动的时候在更新界面要做的事情
  • 读取本地配置表
  • 下载服务器配置表,进行比对
  • 检查大版本更新,如需更新直接跳转url
  • 如需热更,需要两个表(1.需要删除的文件,2.需要下载的文件)
  • 删除不在项目中的文件
  • 下载需要的文件
  • 写入文件
  • 用服务器的配置表 替换掉 客户端的配置表
  • 全部完成,引入lua,替换场景

这里有个地方要特别注意一下,就是一定要修改SearchPath的路径顺序,因为cocos内部在找文件的时候,是按照这个顺序,去遍历查找文件的.这里我是加了3个路径,代码如下

--先获取之前的路径
local paths = cc.FileUtils:getInstance():getSearchPaths()
--设置自己最新的路径(可写路径)
local writablePath = cc.FileUtils:getInstance():getWritablePath()
cc.FileUtils:getInstance():setSearchPaths({writablePath,writablePath .. "xxupdate/",writablePath .. "xxupdate/src/",writablePath .. "xxupdate/res/"})
--最后将之前的路径添加进去
for k,v in pairs(paths) do
	cc.FileUtils:getInstance():addSearchPath(v)
end

然后需要注意的就是路径符号 ,要将所有的 '\' 修改成 '/' ,这个也是让我找了好久,因为在windows上面是没有问题,但是在android路径中带\,是无法正确查找到文件的,而且又不报错

 

贴入源码,就一个文件:


local winSize = cc.Director:getInstance():getWinSize()
local localConfigPath = "UpdateConfig.csv"
local serverConfigPath = "UpdateConfig.csv"
local serverUrl = "http://192.168.3.25/files/"
local panelRoot = nil

local serverConfigContent = "" --服务器配置表内容
local localConfig = nil --本地配置表table
local serverConfig = nil --服务器配置表table

local updateEngine = {}
--读取studio界面
local function XXGetRoot(name,children,parent)
	local path = name .. ".csb"
	local csbNode = cc.CSLoader:createNode(path)
	local root = ccui.Helper:seekWidgetByName(csbNode,"root")
	csbNode.XXChildren = {}
	csbNode.XXChildren["root"] = root
	for _,key in ipairs(children) do
		local child = ccui.Helper:seekWidgetByName(csbNode,key)
		if child ~= nil then
			csbNode.XXChildren[key] = child
		else
			print("csb:" .. name .. ",child is not find <".. key ..">")
		end
	end
	parent:addChild(csbNode)
	root:setContentSize(winSize.width,winSize.height)
	ccui.Helper:doLayout(root)
	return csbNode
end

local function checkRequest( event )
	if event.name == "progress" then return -2 end --进度条先无视
	local ok = (event.name == "completed")
	if not ok then  
		print("request fail")
		return -1
	end
	local request = event.request
	local code = request:getResponseStatusCode() 
	if code ~= 200 then 
		print("request fail")
		return -1
	end
	return 0
end
--下载文件
updateEngine.HttpGet = function(callback,url)
	local request = network.createHTTPRequest(function(event) 
		--请求失败
		local requestFlag = checkRequest(event)
		if requestFlag == -1 then 
			callback(-1)
			return
		end
		if requestFlag == -2 then return end
		callback(event.request:getResponseString())
	end,url,"GET")
	request:start()
end
--读取配置表文件
local function readConfig(content)
	local lineStrs = string.split(content, '\r\n')
	local ary = {}
	for i = 1,#lineStrs do
		local lineAry = string.split(lineStrs[i], ',')
		local lineKey = lineAry[1]
		ary[lineKey] = lineAry[2]
	end
	
	return ary
end
local function enterGame()
	cc.FileUtils:getInstance():purgeCachedEntries() --清除文件缓存
	require ("app/XXInit")
	XXMVCManager:getNewView("testV1"):runScene()
end
--替换本地配置表
updateEngine.ChangeConfigFile = function()
	print("替换本地配置表")
	local configPath = cc.FileUtils:getInstance():getWritablePath() .. localConfigPath
	local file =io.open(configPath,"wb")
	local flag = file:write(serverConfigContent)
	file:flush()
	file:close()
	print("更新完成|" .. tostring(flag) .. "|" .. configPath)
	
	cc.FileUtils:getInstance():purgeCachedEntries() --清除文件缓存
	enterGame()
end
--开始更新
updateEngine.startUpdate = function(updateList,removeList)
	--5.删除无用文件
	for _,v in pairs(removeList) do
		local fname = string.match(v, ".+\\(.+)")
		local path = cc.FileUtils:getInstance():fullPathForFilename( fname )
		print("删除无用文件:"..path)
		if cc.FileUtils:getInstance():isFileExist(path) then
			cc.FileUtils:getInstance():removeFile(path)
			panelRoot.XXChildren["updateLbl"]:setString("正在删除 " .. path)
		else
			print("can't find remove path:"..path)
		end
	end
	--6.下载更新
	local allCount = 0
	for _,v in pairs(updateList) do
		allCount=allCount+1
	end
	print("供需要下载文件个数:"..allCount)
	
	if allCount == 0 then
		print("无需更新,开始游戏")
		enterGame()
		return
	end
	print("开始下载")
	local downCount = 0
	local rootPath = cc.FileUtils:getInstance():getWritablePath()
	print("下载文件路径:"..rootPath)
	for _,v in pairs(updateList) do
		--完整路径
		local fullPath = string.gsub(rootPath .. v,'\\','/')
		--文件名
		local filename = string.match(v, ".+\\(.+)")
		--路径
		local path = string.gsub(fullPath,filename,"")
		--创建路径
		print("创建路径:" .. path)
		if cc.FileUtils:getInstance():isDirectoryExist(path) == false then
			cc.FileUtils:getInstance():createDirectory(path)
		end
		--下载文件
		local updateUrl = serverUrl .. v
		updateUrl = string.gsub(updateUrl,'\\','/')
		print("开始下载文件:"..updateUrl)
		
		local function downloadEnd(response)
			if response == -1 then
				print("下载失败:"..filename)
				updateEngine.HttpGet(downloadEnd,updateUrl)
				return
			end
			print("写入文件:"..fullPath)
			local file =io.open(fullPath,"wb")
			file:write(response)
			file:flush()
			file:close()
			downCount = downCount+1
			panelRoot.XXChildren["LoadingBar"]:setPercent(downCount/allCount*100)
			
			if downCount == allCount then
				print("文件全部下载完成:" .. allCount .."个" )
				panelRoot.XXChildren["updateLbl"]:setString("进入游戏!")
				updateEngine.ChangeConfigFile()
			end
		end
		updateEngine.HttpGet(downloadEnd,updateUrl)
	end
end
--检查更新
updateEngine.checkupdate = function()
	print("start checkupdate")
	--1.读取本地配置表
	local fullpath = cc.FileUtils:getInstance():fullPathForFilename( localConfigPath )
	print("本地配置表:"..fullpath)
	localConfig = readConfig(cc.FileUtils:getInstance():getStringFromFile(fullpath))
	
	--2.下载服务器配置表
	local function ServerConfigResponse(response) 
		if response == -1 then
			updateEngine.checkupdate() --重新下载
			return
		end
		
		serverConfigContent = response
		serverConfig = readConfig(response)
		--3.检查大更新版本号
		local clientVersion = localConfig["v"]
		local serverVersion = serverConfig["v"]
		print("client版本号:" .. clientVersion)	
		print("server版本号:" .. serverVersion)
		localConfig["v"] = nil
		serverConfig["v"] = nil
		--dump(localConfig)
		--dump(serverConfig)
		
		--4.检查需要更新的文件
		local updateList = {}
		local removeList = {}
		print("对比更新文件")
		for k,v in pairs(localConfig) do
			if serverConfig[k] == nil then
				--需删除文件
					print("需要删除:"..k)
				table.insert(removeList,k)
			end
		end
		
		for k,v in pairs(serverConfig) do
			if localConfig[k] ~= nil then
				--列表存在此文件
				if localConfig[k] ~= v then
					--需要更新 
					print("需要更新:"..k)
					table.insert(updateList,k)
				end
			else
				print("新增文件:"..k)
				table.insert(updateList,k)
			end
		end
		
		panelRoot.XXChildren["updateLbl"]:setString("正在更新!")
		updateEngine.startUpdate(updateList,removeList)
	end
	updateEngine.HttpGet(ServerConfigResponse,serverUrl..serverConfigPath)
end

local function create()
    local scene = cc.Scene:create()
	local node = cc.Node:create()
    scene:addChild(node)
	
	panelRoot = XXGetRoot("UpdateView",{"LoadingBar","updateLbl"},node)
	panelRoot.XXChildren["LoadingBar"]:setPercent(0)
	panelRoot.XXChildren["updateLbl"]:setString("正在检查资源!")
	
	cc.Director:getInstance():runWithScene(scene)
	
	updateEngine.checkupdate()
end



create()
	

 

然后main.lua 直接调用

require "updateview"

完成!

我先开发这个的原因,是为了在游戏逻辑开发过程中方便查看手机效果的,因为不需要每次都去编译到手机,感觉很麻烦,除非有bug不然一般都是开发完一个功能,上传到服务器,手机热更新查看效果.

先看下我的使用流程图:

1.修改完代码copy到工具目录,然后执行exe

2.生成后的配置表和文件夹 拖到文件更新服务器

3.重启app进行热更

这里我就贴下windows的效果吧.

这里我就随便改了一个文件用作显示.

使用起来是不是很方便,在实际工作中,这种流程可以快速的让测试人员拿到最新的修改包,不然还要发包给别人安装,太麻烦了.

 

就到这了,晚安!

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值