哈喽大家好,我们又碰面了,不知觉已经发布3期相关内容了,也不知道有多少人从中获得了帮助。前方的还很遥远,我将用的我的一生,囚你无期!
我们继续上期内容接着讲。
我们上期已经说到了分包设计,已经讲解boot中的大致结构。那么下一步我们要考虑的就是我们如何去设计main包呢?
我们在期初之前需要想一下这块 到底我们需要哪些才能满足我们的需求。
在我想肯定需要这几项:
1.logic逻辑模块
2.moudels通用模块(只需要一个这样的模块)
3.uis游戏界面控制模块
这里logic因为我们是在设计框架,所以暂时无需去考虑游戏中的具体逻辑。同样的uis模块也是一样。所以我们这里最要注意的是moudles通用模块
#1.log
游戏日志模块,主要管理游戏输出的日志。(这个模块其实可以放在boot包中)这里我将大概的代码给出
其中fileMgr就是对cocos中的FileUtils封装类让后通过lua绑定就可以使用了
utils = utils or {}
--[[
http GET调用
]]
function utils.http_get(url, onComplete, resptype)
local xhr = cc.XMLHttpRequest:new()
xhr.responseType = resptype or cc.XMLHTTPREQUEST_RESPONSE_STRING
xhr:open("GET", url)
local function onReadyStateChanged()
if onComplete then
if xhr.readyState == 4 and (xhr.status >= 200 and xhr.status < 207) then
onComplete(true,xhr.response)
else
onComplete(false)
end
end
xhr:unregisterScriptHandler()
end
xhr:registerScriptHandler(onReadyStateChanged)
xhr:send()
end
-- 获得真实lua文件
function utils.get_real_luafile(filename)
local finfo = io.pathinfo(filename)
local luafile = finfo.dirname .. finfo.basename .. ".luac"
if fileUtils:isFileExist(luafile) then
return luafile
end
luafile = finfo.dirname .. finfo.basename .. ".lua"
if fileUtils:isFileExist(luafile) then
return luafile
end
end
--[[
日志管理器
--]]
local THIS_MODULE = ...
-- 最大日志缓冲 默认1M
local C_BUFFERSIZE = 1024 * 1024
-- 日志等级
local C_LEVEL = {
VERBOSE = 1, -- 啰嗦
DEBUG = 2, -- 调试
INFO = 3, -- 信息
WARN = 4, -- 警告
ERROR = 5, -- 错误
SILENT = 6, -- 寂静,用于关闭日志
}
-- 日志等级标识
local C_LFLAG = {
[C_LEVEL.VERBOSE] = "V",
[C_LEVEL.DEBUG] = "D",
[C_LEVEL.INFO] = "I",
[C_LEVEL.WARN] = "W",
[C_LEVEL.ERROR] = "E",
[C_LEVEL.SILENT] = "S",
}
local LogManager = class("LogManager")
LogManager.LEVEL = C_LEVEL -- 日志等级
-- 获得单例对象
local instance = nil
function LogManager:getInstance()
if instance == nil then
instance = LogManager:create()
-- 定义错误处理
__G__TRACKBACK__ = function(msg)
local msg = debug.traceback(msg, 3)
instance:error("*",
"\n---------------------------------ERROR--------------------------------\n" ..
"%s" ..
"\n----------------------------------------------------------------------",
msg)
return msg
end
end
return instance
end
-- 构造函数
function LogManager:ctor()
self._loglevel = C_LEVEL.VERBOSE -- 日志输出等级
self._flushlevel = C_LEVEL.ERROR -- 日志刷新缓冲等级
self._buffsize = C_BUFFERSIZE -- 日志缓冲大小
self._logformat = handler(self,LogManager._defaultLogFormat) -- 日志格式函数
self._logpath = fileMgr:getWritablePath() .. DIRECTORY.LOG .. "/"
self._logfile = self._logpath .. "/" .. os.date("%Y%m%d%H%M%S") .. ".log"
self._logbuffer = { logs = {}, size = 0, } -- 日志缓冲
fileMgr:createDirectory(self._logpath)
end
--[[
默认日志格式化函数
config
level 等级
tag 标签
thread 线程
content 日志内容
]]
function LogManager:_defaultLogFormat(config, content)
return string.format("%s %s %s%s - ",
os.date("%m-%d %H:%M:%S"),
config.thread or "[MAIN]",
C_LFLAG[config.level],
config.tag and ("/" .. config.tag) or "") .. content
end
-- 设置日志输出等级
function LogManager:setLogLevel(level)
self._loglevel = level
end
-- 设置刷新缓冲区等级
function LogManager:setFlushLevel(level)
self._flushlevel = level
end
-- 设置日志缓冲区大小
function LogManager:setBufferSize(size)
self._buffsize = size
end
--[[
设置日志格式化函数
format 格式化函数
]]
function LogManager:setLogFormat(format)
self._logformat = format
if not format then
self._logformat = handler(self,LogManager._defaultLogFormat)
end
end
--[[
写入日志
config
level 等级
tag 标签
thread 线程
fmt 格式字符串
... 日志参数
]]
function LogManager:_writeLog(config, fmt, ...)
if config.level >= self._loglevel then
local content = string.format(fmt, ...)
local log = self._logformat(config, content)
self:_writeContent(config.level, log)
end
end
--[[
写入日志内容
log 日志字符串
]]
function LogManager:_writeContent(level, log)
print(log)
table.insert(self._logbuffer.logs, log)
self._logbuffer.size = self._logbuffer.size + #log
if level >= self._flushlevel or self._logbuffer.size >= self._buffsize then
self:flushLog()
end
end
-- 完全写入日志缓冲
function LogManager:flushLog()
if self._logbuffer.size > 0 then
fileMgr:appendStringToFile(table.concat(self._logbuffer.logs, "\n") .. "\n", self._logfile)
self._logbuffer = { logs = {}, size = 0, }
end
end
-- 输出 VERBOSE 日志
function LogManager:verbose(config, fmt, ...)
if type(config) ~= "table" then
config = { tag = config }
end
config.level = C_LEVEL.VERBOSE
self:_writeLog(config, fmt, ...)
end
-- 输出 DEBUG 日志
function LogManager:debug(config, fmt, ...)
if utils.isDebug() then
if type(config) ~= "table" then
config = { tag = config }
end
config.level = C_LEVEL.DEBUG
self:_writeLog(config, fmt, ...)
end
end
-- 输出 INFO 日志
function LogManager:info(config, fmt, ...)
if type(config) ~= "table" then
config = { tag = config }
end
config.level = C_LEVEL.INFO
self:_writeLog(config, fmt, ...)
end
-- 输出 WARN 日志
function LogManager:warn(config, fmt, ...)
if type(config) ~= "table" then
config = { tag = config }
end
config.level = C_LEVEL.WARN
self:_writeLog(config, fmt, ...)
end
-- 输出 ERROR 日志
function LogManager:error(config, fmt, ...)
if type(config) ~= "table" then
config = { tag = config }
end
config.level = C_LEVEL.ERROR
self:_writeLog(config, fmt, ...)
end
return LogManager
#config
游戏配置模块,主要提供游戏本地和远程配置功能。
-- 标志
FLAG = {
ENABLEREMOTE = true, -- 远程配置
ENABLEUPDATE = true, -- 使能更新
HTTPSVERIFY = true, -- https验证
}
-- 目录
DIRECTORY = {
TEMP = "temp", -- 临时
DOWNLOAD = "downloads", -- 下载
LOG = "logs", -- 日志
}
-- 解决系统require的时候,可能定义全局变量,而又禁止定义
local g_cenable = true
function cc.safe_require(packpath)
local r = nil
local oge = g_cenable
if not oge then
cc.enable_global(true)
end
r = require(packpath)
if not oge then
cc.enable_global(false)
end
return r
end
--[[
游戏配置模块
]]
local THIS_MODULE = ...
local C_LOGTAG = "GameConfig"
-- 游戏配置表
local GAMECONFIG = {}
local cjson = cc.require("cjson")
local utils = cc.safe_require("utils")
local GameConfig = class("GameConfig", GAMECONFIG)
-- 获得单例对象
local instance = nil
function GameConfig:getInstance()
if instance == nil then
instance = GameConfig:create()
end
return instance
end
-- 解密配置数据
function GameConfig:decryptData(data)
if not self._aesok then
self._aesok,self._aeskey,self._aesiv = utils.getConfAESKey()
end
if data and self._aesok then
local decok,decdata = utils.AES_decrypt(self._aeskey,self._aesiv,data)
if not decok then
error("config decrypt failure")
else
return decdata
end
end
end
--[[
加载配置文件
confile 配置文件
onComplete 完成回调
END 配置读取完成
REMOTE 加载远程配置
]]
function GameConfig:loadConfig(confile,onComplete)
self:clear()
if fileMgr:isFileExist(confile) then
local lcdata = self:decryptData(fileMgr:getDataFromFile(confile))
if lcdata then
table.merge(GAMECONFIG,cjson.decode(lcdata))
-- 读取远程配置
if self:isRemoteReadable() then
if onComplete then onComplete("REMOTE") end
GAMECONFIG.remoteurl = io.pathinfo(GAMECONFIG.remoteconfig).dirname
logMgr:info(C_LOGTAG, "remote config file : %s", GAMECONFIG.remoteconfig)
logMgr:info(C_LOGTAG, "remote url : %s", GAMECONFIG.remoteurl)
return utils.http_get(GAMECONFIG.remoteconfig,function (result,data)
if result then
local rcdata = self:decryptData(data)
if rcdata then
table.merge(GAMECONFIG,cjson.decode(rcdata))
end
end
if onComplete then onComplete("END") end
end)
end
end
end
if onComplete then onComplete("END") end
end
-- 清除配置
function GameConfig:clear()
for _,key in ipairs(table.keys(GAMECONFIG)) do
GAMECONFIG[key] = nil
end
end
-- 远程配置是否可以读取
function GameConfig:isRemoteReadable()
return FLAG.ENABLEREMOTE and GAMECONFIG.enableremote and GAMECONFIG.remoteconfig
end
-- 包是否可以更新
function GameConfig:isPackUpdatable()
return FLAG.ENABLEUPDATE and GAMECONFIG.enableupdate and GAMECONFIG.remoteurl and GAMECONFIG.packs
end
return GameConfig
-- 包
PACK = {
FORMAT = ".zip", -- 包格式
BASES = { "main" }, -- 基础包
BOOT = {}, -- 启动包
LOADED = {}, -- 加载包
}
#pack
游戏包模块,主要管理游戏包的更新和加载。
--[[
包 更新器
]]
local THIS_MODULE = ...
local C_LOGTAG = "PackUpdater"
local C_DOWNINFO = "down.info" -- 下载信息
local C_DOWNING_MAX = 3 -- 最大下载数
local C_REDOWN_COUNT = 3 -- 重新下载次数
local cjson = cc.require("cjson")
local utils = cc.safe_require("utils")
local PackUpdater = class("PackUpdater")
-- 获得单例对象
local instance = nil
function PackUpdater:getInstance()
if instance == nil then
instance = PackUpdater:create()
end
return instance
end
-- 构造函数
function PackUpdater:ctor()
self._downpath = fileMgr:getWritablePath() .. DIRECTORY.DOWNLOAD .. "/"
self._downinfo = self._downpath .. "/" .. C_DOWNINFO
self._temppath = fileMgr:getWritablePath() .. DIRECTORY.TEMP .. "/"
fileMgr:removeDirectory(self._temppath)
fileMgr:createDirectory(self._temppath)
end
--[[
下载配置
verscode 版本
config 配置
onComplete 完成回调
R {true/false} 结果
P {float} 进度
]]
function PackUpdater:download(verscode,config,onComplete)
if fileMgr:isFileExist(self._downinfo) then
local downconf = cjson.decode(fileMgr:getDataFromFile(self._downinfo))
if downconf.verscode ~= verscode or downconf.path ~= config.path then
fileMgr:removeDirectory(self._downpath)
end
end
fileMgr:createDirectory(self._downpath)
if not fileMgr:isFileExist(self._downinfo) then
fileMgr:writeStringToFile(cjson.encode({
verscode = verscode,
path = config.path,
}),self._downinfo)
end
local function _onComplete(...)
if onComplete then onComplete(...) end
end
-- 下载任务配置
local downtasks = {}
for i = 1, config.count do
local downfile = string.format(config.format, i-1)
downtasks[i] = {
remotef = gameConfig.remoteurl .. config.path .. "/" .. downfile, -- 远程文件
localf = self._downpath .. downfile, -- 保存的本地文件
progress = 0, -- 进度
redowncount = 0, -- 重新下载次数
}
end
local downindex = 1 -- 下载任务索引
local taskprog = 1 / config.count -- 每个任务进度
local downingmax = gameConfig.downingmax or C_DOWNING_MAX -- 最大正在下载数量
local downingnum = 0 -- 当前正在下载数量
local redowncount = gameConfig.redowncount or C_REDOWN_COUNT -- 重新下载次数
local downloader = cc.Downloader.new() -- 下载器
local updateProgress = nil -- 进度更新
local downloadNexts = nil -- 下载剩下的文件
updateProgress = function(testend)
local prog = 0
local complete = true
local success = true
for _,downtask in pairs(downtasks) do
prog = prog + downtask.progress * taskprog
if testend and downindex > #downtasks then
if downtask.state == nil then
complete = false
elseif downtask.state == false then
success = false
end
end
end
_onComplete("P", prog)
if testend and downindex > #downtasks then
if complete then
if success then
_onComplete("R", true)
else -- failure
_onComplete("R", false)
end
end
end
end
downloadNexts = function (downfail)
while downindex <= #downtasks do
local downtask = downtasks[downindex]
if downfail then
downindex = downindex + 1
downtask.progress = 0
downtask.state = false
updateProgress(true)
else
if fileMgr:isFileExist(downtask.localf) then
downindex = downindex + 1
downtask.progress = 1
downtask.state = true
updateProgress(true)
else
if downingnum >= downingmax then
break -- 到达最大下载数量
end
downloader:createDownloadFileTask(downtask.remotef, downtask.localf, tostring(downindex));
downindex = downindex + 1
downingnum = downingnum + 1
end
end
end
end
-- 下载文件
downloader:setOnFileTaskSuccess(function (task)
local downtask = downtasks[tonumber(task.identifier)]
downtask.progress = 1
downtask.state = true
updateProgress(true)
downingnum = downingnum - 1
downloadNexts()
end)
downloader:setOnTaskProgress(function (task, received, totalReceived, totalExpected)
downtasks[tonumber(task.identifier)].progress = totalReceived / totalExpected
updateProgress(false)
end)
downloader:setOnTaskError(function (task, errorCode, errorCodeInternal, errorStr)
local downtask = downtasks[tonumber(task.identifier)]
if downtask.redowncount < redowncount then
downtask.redowncount = downtask.redowncount + 1
logMgr:info(C_LOGTAG, "retry download %s : %d", downtask.remotef, downtask.redowncount)
downtask.progress = 0
updateProgress(false)
downloader:createDownloadFileTask(downtask.remotef, downtask.localf, task.identifier);
else
logMgr:info(C_LOGTAG, "download %s failure : %s", downtask.remotef, errorStr)
downtask.state = false
downingnum = downingnum - 1
updateProgress(true)
downloadNexts(true)
end
end)
downloadNexts()
end
-- 检查并返回指定包的更新
function PackUpdater:checkUpdate(packname,packpath)
local rmpack = gameConfig.packs[packname]
if rmpack then
packpath = packpath or (PACKSPATH .. packname .. PACK.FORMAT)
local lcvers = fileMgr:lookPackVersion(packpath)
if rmpack.verscode > lcvers then -- 远程版本号 > 本地版本号
local compsize = rmpack.complete.size
local patchsize = 0
local patchs = {}
local rmvern = rmpack.versname
local curvern = utils.get_version_name(lcvers)
if rmpack.patchs then
local function findPatch(vernbegin)
for pname,pconf in pairs(rmpack.patchs) do
local bgvern,edvern = unpack(string.split(pname,"_"))
if vernbegin == bgvern then
return edvern,pconf
end
end
end
while curvern ~= rmvern do
local edvern,patch = findPatch(curvern)
if not edvern then
break
end
patchs[#patchs + 1] = patch
patchsize = patchsize + patch.size
curvern = edvern
end
end
if rmvern == curvern and #patchs > 0 and patchsize < compsize then
return {
type = "P", -- 补丁包更新配置
lcvers = lcvers,
rmvers = rmpack.verscode,
size = patchsize,
patchs = patchs,
path = packpath,
pack = packname,
}
else
return {
type = "C", -- 整包更新配置
lcvers = lcvers,
rmvers = rmpack.verscode,
size = compsize,
complete = rmpack.complete,
path = packpath,
pack = packname,
}
end
elseif lcvers > rmpack.verscode then -- 本地版本比服务器更高
logMgr:warn(C_LOGTAG, "[%s] pack local version(%s) > remote version(%s)",
packname, utils.get_version_name(lcvers), utils.get_version_name(rmpack.verscode))
end
end
end
--[[
更新包
configs 包配置数组,checkUpdate返回组成的数组
onComplete
R {true/false} 结果
P {float} 进度
C {config} 当前配置
]]
function PackUpdater:updatePacks(configs,onComplete)
local function _onComplete(...)
if onComplete then onComplete(...) end
end
table.asyn_walk_sequence(function ()
_onComplete("P",1)
_onComplete("R",true)
end,configs,
function (updateNext,config,index)
local update = handler(self,config.type == "C" and PackUpdater.completeUpdate or PackUpdater.patchUpdate)
_onComplete("C",config)
update(config,function (ctype,result)
if ctype == "P" then
_onComplete("P",(1/#configs) * (index - 1 + result))
elseif ctype == "R" then
if result then
updateNext()
else
_onComplete("R", false, config)
end
end
end)
end)
end
--[[
整包更新
config 整包配置
onComplete 完成回调
R {true/false} 结果
P {float} 进度
]]
function PackUpdater:completeUpdate(config,onComplete)
logMgr:info(C_LOGTAG, "update pack [%s] by complete from %s to %s, size : %s" ,
config.pack, utils.get_version_name(config.lcvers), utils.get_version_name(config.rmvers),
utils.format_store_size(config.size))
local function _onComplete(...)
if onComplete then onComplete(...) end
end
local function downEnd()
local destpack = io.pathinfo(config.path).dirname .. "temp.pack"
utils.mergeFile(self._downpath,destpack,config.complete.format,true)
_onComplete("P", 0.9)
if fileMgr:isFileExist(config.path) then
fileMgr:removeFile(config.path)
end
fileMgr:renameFile(destpack,config.path)
logMgr:info(C_LOGTAG, "[%s] update success", config.pack)
_onComplete("P", 1)
_onComplete("R",true)
end
self:download(config.rmvers,config.complete,function (ctype,result)
if ctype == "P" then
_onComplete("P", 0.8 * result)
elseif ctype == "R" then
if result then
downEnd()
else
_onComplete("R",false)
end
end
end)
end
--[[
补丁更新
config 补丁配置
onComplete 完成回调
R {true/false} 结果
P {float} 进度
]]
function PackUpdater:patchUpdate(config,onComplete)
logMgr:info(C_LOGTAG, "update pack [%s] by patchs from %s to %s, size : %s" ,
config.pack, utils.get_version_name(config.lcvers), utils.get_version_name(config.rmvers),
utils.format_store_size(config.size))
local function _onComplete(...)
if onComplete then onComplete(...) end
end
local function downEnd()
local destpack = io.pathinfo(config.path).dirname .. "pack.temp"
local temppack = self._temppath .. "pack.temp"
local function swappath()
local tmp = destpack
destpack = temppack
temppack = tmp
end
if #config.patchs % 2 == 0 then
swappath()
end
-- 打第一个补丁
if not utils.patchH(destpack,config.path,self._temppath .. "1.patch") then
_onComplete("R",false)
error(string.format("%s patch 1 failure", config.pack))
end
_onComplete("P", 0.8 + 0.1 * (1/#config.patchs))
-- 打剩余补丁
for i = 2, #config.patchs do
swappath()
if not utils.patchH(destpack,temppack,self._temppath .. tostring(i) .. ".patch") then
_onComplete("R",false)
error(string.format("%s patch %d failure", config.pack, i))
end
_onComplete("P", 0.8 + 0.1 * (i/#config.patchs))
end
if fileMgr:isFileExist(config.path) then
fileMgr:removeFile(config.path)
end
fileMgr:renameFile(destpack,config.path)
logMgr:info(C_LOGTAG, "[%s] update success", config.pack)
_onComplete("P", 1)
_onComplete("R",true)
end
-- 下载所有补丁
table.asyn_walk_sequence(downEnd,config.patchs,function (downNext, patch, index)
self:download(config.rmvers,patch,function (ctype,result)
if ctype == "P" then
_onComplete("P", (0.8 / #config.patchs) * (index - 1 + result))
elseif ctype == "R" then
if result then
utils.mergeFile(self._downpath,self._temppath .. tostring(index) .. ".patch", patch.format,true)
downNext()
else
_onComplete("R",false)
end
end
end)
end)
end
return PackUpdater
今天就到这里太多了,下期继续喽!(Cocos游戏设计心得(五)已经出炉慢慢品尝)
###留下个人博客以供学习:taoqy666.com,生活视频模块已添加