Cocos游戏设计心得(五)

#游戏索引模块设计

之前写了关于modules模块中的一些模块代码,但是最终感觉还是太麻烦了,而却一部分代码段,估计你们也是看的云里雾里,所以呢我就不去再贴出代码段了,在这里我只列出一些必要的模块,仅供参考:

**`font` 游戏字体模块,管理游戏中使用的字体。**
**`script` 游戏脚本管理模块。**
**`model` 游戏模型管理模块。**
**`ui` 游戏UI管理模块。**
**`uiwidget` 游戏UI组件管理模块。**
**`variable` 游戏变量管理模块,针对游戏字符串中的变量。**
**`archive` 游戏存档模块,针对单机或联网游戏。**
**`behavior3` 游戏行为树模块。**
**`effect` 游戏特效模块。**
**`map` 游戏地图管理模块。**
**`play` 游戏剧本管理模块,主要针对单机RPG游戏。**
**`control` 游戏控制模块,特别针对游戏控制器,按键等。**
**`audio` 游戏音频模块,管理游戏声音。**
**`formula` 游戏公式模块,针对策划可以在数据库配置游戏的公式,如伤害计算等。**
**`scene` 游戏场景管理模块。**

##模块设计目的

由于前面我们在游戏中使用分包设计,所以传统编码方式访问其他包中的文件是比较麻烦的事,而且还涉及到各个分包加载与否,或者加载先后的问题。这样一来不就很是不舒爽了吗?
所以索引模块的设计就是为了解决分包文件访问问题,力求在编码时和传统编码一样,根本不用考虑分包的问题。

##索引模块的本质

索引模块有的同学可能问他是不是像键值对一样存在一张表中呢,用到时直接下表访问那种呢?嗯。。。可以这么去理解,但是也不完全是这样的。
索引模块是为了解决包文件共享而设计的,但是它并不是文件管理器,或者资源管理器。索引模块仅仅只是一张表,表中的各个节点记录着各种数据
**当我们要访问某一类资源时只要在索引模块中查找就可以了。这样一来的确是省了很多的力气活,为我们后期的编码开发提供了一种规范。 **

包文件共享共享的是什么? 一般我们要共享的不是单个文件,而是一类配置,比如某个包中的怪物需要共享,那么这些怪的配置、资源、程序就是索引表中的数据。

##索引模块如何工作

每个游戏包都可以在它的资源目录中定义索引加载表,以加载当前包的共享配置数据等。资源索引加载表路径为Game/<游戏包>/res/indexes.json,这是一个JSON文件里面包含了所有加载表,如;
** **{** ** "src": "$(respath)/main/indexes/src.json",** ** "res": "$(respath)/main/indexes/res.json",** ** "db_auto": "$(respath)/main/indexes/db_auto.json",** ** "prot_auto": "$(respath)/main/indexes/prot_auto.json"** **}** **
*_auto 以_auto结尾的键一般为游戏脚本工具生成的,不要去修改它。比如db_auto表示脚本工具生成的数据库索引文件,prot_auto表示脚本工具生成的协议索引文件。
$(respath)这个是索引模块的一个环境变量,可以自定义。

具体的src.jsonres.jsondb_auto.jsonprot_auto.json其实就是普通的JSON文件,当然也是可以使用环境变量的。

**索引模块会把加载表中的索引JSON合并为一个索引表,当然只要有包文件加载就会把包文件中的索引配置读取出来合并为一个表。 **
这样当我们需要加载一类游戏配置的时候只要知道索引表中的节点路径就可以了,当然索引表需要仔细设计,他是很重要的。
##索引模块重要函数

readJson(jsonfile) 读取JSON文件,游戏中读取JSON最好都使用这个函数。因为游戏包在打包阶段JSON文件可能会有优化,比如直接转换为lua文件,或者luac字节码文件,这对JSON加载速度可能会提升 10 ~ 50 倍。
writeJson(jdata,jsonfile) 写入JSON文件,这和普通的写入没有区别,但是注意,在配置JSON优化的时候,需要避开这些可能会写入的问题,否则可能会出问题。
readJsonConfig(jsonfile)读取JSON配置文件,它在读取了JSON后,还会转换JSON配置中的环境变量。目前这类文件不能使用JSON优化,主要是出于转换环境变量的效率考虑。目前还不知道优劣。
getIndex(path)获得指定路径的索引配置。
addListener(listener, ipaths, priority)这个可以监听索引模块索引变化情况,对于依赖于索引模块的其他模块,监听是很重要的。比如包文件加载和释放都会存在索引表变动,需要及时更新其他对应模块。

##索引模块很重要

索引模块应该算是游戏框架中很重要的模块,如果没有这个模块,其他很多模块都会变得很难使用。

最后还是想了一下将索引代码放一些在上面,供大家参考使用
##代码

**--[[**
**    索引管理器**
**--]]**

**local THIS_MODULE = ...**
**local C_LOGTAG = "IndexManager"**

**local cjson = cc.safe_require("cjson")**
**local IndexManager = class("IndexManager")**

**-- 获得单例对象**
**local instance = nil**
**function IndexManager:getInstance()**
**    if instance == nil then**
**        instance = IndexManager:create()**
**    end**
**    return instance**
**end**

**-- 构造函数**
**function IndexManager:ctor()**
**    self._envs          = {}            -- 环境变量**
**    self._indexnodes    = {}            -- 索引节点**
**    self._indexes       = {}            -- 索引表**
**    self._listen        = {             -- 监听**
**        remove = { entities = {} },     -- 移除配置**
**        loads = {},                     -- 加载配置**
**    }**
**end**

**----------------------------------------------------------**
**--  环境变量**

**-- 设置环境变量**
**function IndexManager:setEnv(key, value)**
**    local fkey = "$(" .. key .. ")"**
**    if self._envs[fkey] then**
**        local msg = string.format("index env %s conflict", key)**
**        if ERROR.INDEX_ENV_CONFLICT then**
**            error(msg)**
**        else**
**            logMgr:warn(C_LOGTAG, "%s", msg)**
**        end**
**    end**
**    self._envs[fkey] = value**
**end**

**-- 设置环境配置**
**function IndexManager:setEnvConfig(envconf)**
**    if envconf then**
**        for key,value in pairs(envconf) do**
**            self:setEnv(key, value)**
**        end**
**    end**
**end**

**-- 获得环境变量**
**function IndexManager:getEnv(key)**
**    return self._envs["$(" .. key .. ")"]**
**end**

**-- 清除环境变量**
**function IndexManager:clearEnvs()**
**    self._envs = {}**
**end**

**----------------------------------------------------------**
**--  json配置读写**

**-- 读取json文件**
**function IndexManager:readJson(jsonfile)**
**    local jsonpath = fileUtils:fullPathForFilename(jsonfile)**
**    if fileUtils:isFileExist(jsonpath) then**
**        return cjson.decode(fileUtils:getDataFromFile(jsonpath))**
**    end**
**end**

**-- 写入json文件**
**function IndexManager:writeJson(jdata,jsonfile)**
**    if jdata and jsonfile then**
**        fileUtils:writeStringToFile(cjson.encode(jdata), jsonfile)**
**    end**
**end**

**-- 读取json配置文件,环境变量替换**
**function IndexManager:readJsonConfig(jsonfile)**
**    logMgr:verbose(C_LOGTAG, "read config json : %s", jsonfile)**
**    local jsonpath = fileUtils:fullPathForFilename(jsonfile)**
**    if fileUtils:isFileExist(jsonpath) then**
**        local newenvs = { **
**            ["$(curpath)"] = io.pathinfo(jsonpath).dirname **
**        }**
**        table.merge(newenvs, self._envs)**

**        local jsondata = fileUtils:getDataFromFile(jsonpath)**

**        jsondata = jsondata:gsub("%$%(%w+%)", newenvs)**

**        return cjson.decode(jsondata)**
**    end**
**end**

**----------------------------------------------------------**
**--  索引操作**

**-- 合并表**
**function IndexManager:mergeTalbe(jdest, jsrc, jpath, listen, jsonpath)**
**    jpath = jpath or ""**
**    for name,value in pairs(jsrc) do**
**        if type(name) == "number" then**
**            name = #jdest + 1**
**        end**
**        local ijpath = (jpath ~= "") and (jpath .. "/" .. name) or name**

**        if type(value) == "table" then**
**            local itable = jdest[name]**
**            if not itable then**
**                itable = {}**
**                jdest[name] = itable**
**            end**
**            self:mergeTalbe(itable, value, ijpath, listen, jsonpath)**
**        else**
**            if jdest[name] then**
**                local msg = string.format("index path %s conflict : %s", ijpath, jsonpath or "[unknown]")**
**                if ERROR.INDEX_PATH_CONFLICT then**
**                    error(msg)**
**                else**
**                    logMgr:warn(C_LOGTAG, "%s", msg)**
**                end**
**            end**
**            jdest[name] = value**
**        end**

**        if listen then self:notifyIndexesLoaded(ijpath, value) end**
**    end**
**end**

**-- 重构索引**
**function IndexManager:rebuildIndexes()**
**    self._indexes = {}**
**    for node,index in pairs(self._indexnodes) do**
**        self:mergeTalbe(self._indexes, index)**
**    end**
**end**

**-- 获得指定索引**
**function IndexManager:getIndex(path)**
**    local value = self._indexes**
**    for _,pnode in ipairs(path:split('/')) do**
**        if not value then **
**            return nil**
**        end**
**        value = value[pnode]**
**    end**
**    return value**
**end**

**-- 加载索引文件**
**function IndexManager:loadIndexFile(file, node)**
**    node = node or file**
**    local newindex = self:readJsonConfig(file)**

**    local nodeindex = self._indexnodes[node]**
**    if not nodeindex then**
**        self._indexnodes[node] = newindex**
**    else**
**        self:mergeTalbe(nodeindex, newindex)**
**    end**

**    self:mergeTalbe(self._indexes, newindex, "", true, file)**
**end**

**-- 移除索引节点**
**function IndexManager:removeIndexNode(node)**
**    if self._indexnodes[node] then**
**        self._indexnodes[node] = nil**
**        self:rebuildIndexes()**
**        self:notifyIndexesRemoved()**
**    end**
**end**

**-- 加载索引文件配置**
**function IndexManager:loadIndexFileConfig(confile, node)**
**    local indexconf = self:readJsonConfig(confile)**
**    if indexconf then**
**        for _,indexfile in pairs(indexconf) do **
**            self:loadIndexFile(indexfile, node or confile)**
**        end**
**    end**
**end**

**-- 清除全部索引**
**function IndexManager:clearIndexes()**
**    self._indexnodes    = {}**
**    self._indexes       = {}**
**    self:notifyIndexesRemoved()**
**end**

**----------------------------------------------------------**
**--  索引监听**

**-- 添加监听器**
**function IndexManager:addListener(listener, ipaths, priority)**
**    self:removeListener(listener)**

**    local lentity = {**
**        listener = listener,**
**        priority = priority or 0,**
**    }**

**    -- 更新移除监听器**
**    self._listen.remove.entities[listener] = lentity**
**    self._listen.remove.queue = table.values(self._listen.remove.entities)**
**    table.sort(self._listen.remove.queue, function (a, b)**
**        return a.priority < b.priority**
**    end)**

**    -- 更新加载路径监听器**
**    if ipaths then**
**        for _,ipath in ipairs(ipaths) do**
**            if ipath:byte(#ipath) == 47 then    -- /**
**                ipath = ipath:sub(1,-2)**
**            end**
**            local loadconf = self._listen.loads[ipath]**
**            if not loadconf then**
**                loadconf = { entities = {} }**
**                self._listen.loads[ipath] = loadconf**
**            end**
**            loadconf.entities[listener] = lentity**
**            loadconf.queue = table.values(loadconf.entities)**
**            table.sort(loadconf.queue, function (a, b)**
**                return a.priority > b.priority**
**            end)**
**        end**
**    end**
**end**

**-- 移除监听器**
**function IndexManager:removeListener(listener)**
**    if self._listen.remove.entities[listener] then**
**        -- 移除移除表**
**        self._listen.remove.entities[listener] = nil**
**        self._listen.remove.queue = table.values(self._listen.remove.entities)**
**        table.sort(self._listen.remove.queue, function (a, b)**
**            return a.priority < b.priority**
**        end)**

**        -- 移除路径表**
**        for _,loadconf in pairs(self._listen.loads) do**
**            if loadconf.entities[listener] then**
**                loadconf.entities[listener] = nil**
**                loadconf.queue = table.values(loadconf.entities)**
**                table.sort(loadconf.queue, function (a, b)**
**                    return a.priority > b.priority**
**                end)**
**            end**
**        end**
**    end**
**end**

**-- 清空监听器**
**function IndexManager:clearListeners()**
**    self._listen = { **
**        remove = { entities = {} },**
**        loads = {},**
**    }   **
**end**

**-- 通知索引加载**
**function IndexManager:notifyIndexesLoaded(ipath, ivalue)**
**    local loadconf = self._listen.loads[ipath]**
**    if loadconf then**
**        for _,lentity in ipairs(loadconf.queue or {}) do**
**            lentity.listener:onIndexesLoaded(ipath, ivalue)**
**        end**
**    end**
**end**

**-- 通知索引移除**
**function IndexManager:notifyIndexesRemoved()**
**    for _,lentity in ipairs(self._listen.remove.queue or {}) do**
**        lentity.listener:onIndexesRemoved()**
**    end**
**end**

**return IndexManager**

##未完待续
##留下个人博客以供学习使用taoqy666.com