zoukankan      html  css  js  c++  java
  • `cocos2dx 非完整` UI解析模块

    昨天在cocos2dx的一个群里,遇到一位匿名为x的朋友询问的问题,是关于ui的.他使用c++写了不少的ui封装节点,用来实现游戏中的各种不同效果.然后现在想改用lua,于是尝试使用最小代价去复用自己的代码.当然这个是可以做到的,相信很多人都是知道方法的.今天的这篇文章就来谈谈ui部分的处理以及个人的见解.

    我们都知道,cocos2dx引擎提供了ui工具cocostudio.后来改名为cocos engine.这些就不赘述了,很多人都会使用这款工具.新版本的工具我没有使用过,不过我承认是方便了很多.可是很多时候我们期望有着自己一套的资源管理方式,尤其是项目琐碎ui资源,效果资源等等一大堆的时候,后期优化app包大小的时候也是个问题. 所以我个人倾向的方案是提供基于项目的ui库.当然对于lua这样的语言不存在库的说法,我在fw目录下面提供了一个ui模块,为了解决自己提供的ui方式,首先得想明白我们如何去使用自己的ui模块.我期望的方式是这个样子的:

     1 local component_cfg = 
     2 {
     3     {
     4         name     = "bg_img",
     5         creator = ui_parser.parsers.img,  -- 基于cocos2dx ui
     6         pos     = cc.p(100,200), 
     7         -- ...
     8         parent  = self,
     9     },
    10     {
    11         name    = "loading_bar",
    12         creator = ui_parser.parsers.widget_loading_bar, -- 基于自己扩展的ui
    13         -- ...
    14         parent  = "bg_img",        -- 节点内部相互添加也可以支持
    15     },    
    16 
    17     onfinsh = function(components)
    18         components["loading_bar"]:set_percent(100), -- 初始化以后的操作
    19     end,
    20 }
    21 
    22 local components = ui_parser.parse(component_cfg)

    看上面我提供的实例代码,我期望的是创建一个ui模块,只需要按照lua table的方式一样配置就好.这样有一个好处,可以复用很多其他模块的代码.需要的其他功能我都在注释中写出来了.接下来继续分析一下需求,我们可能会有自己的ui封装,所以得提供一个基类,当然这是仿照oop的说法,所以我就提供了一个ui_widget文件用来实现这个功能:

     1 --小岩<757011285@qq.com>
     2 --2015-5-30 13:25
     3 local ui_widget = {}
     4 
     5 function ui_widget:ctor(cfg)
     6     self.type_ = "ui_widget"
     7     self.cfg_  = cfg
     8     self:init_widget()
     9 end
    10 
    11 function ui_widget:init_widget()
    12     error(self.__cname .. ":init_widget() method not implemented!")
    13 end
    14 
    15 function ui_widget:get_root_node()
    16     error(self.__cname .. ":get_root_node() method not implemented!")
    17 end
    18 
    19 function ui_widget:add_to_node(parent)
    20     if parent.type_ and parent.type_ == "ui_widget" then
    21         parent:get_root_node():addChild(self:get_root_node())
    22     else
    23         parent:addChild(self:get_root_node())
    24     end
    25 end
    26 
    27 function ui_widget:add_child(child)
    28     if child.type_ and child.type_ = "ui_widget" then
    29         self:get_root_node():addChild(child:get_root_node())
    30     else
    31         self:get_root_node():addChild(child)
    32     end
    33 end
    34 
    35 function ui_widget:set_ap(ap)
    36     self:get_root_node():setAnchorPoint(ap)
    37 end
    38 
    39 function ui_widget:get_ap()
    40     return self:get_root_node():getAnchorPoint()
    41 end
    42 
    43 function ui_widget:set_pos(pos)
    44     self:get_root_node():setPosition(pos)
    45 end
    46 
    47 function ui_widget:get_pos()
    48     local posx, posy = self:get_root_node():getPosition()
    49     return cc.p(posx, posy)
    50 end
    51 
    52 return ui_widget

    好了,基本的东西已经提供好了,下面就去处理.首先是处理creator,因为需求中明确的表示,第一我们要兼容cocos ui创建的api,另外我们还会有自己的ui封装.所以我将这些节点的创建方式都放在一起,方便管理.所以就有了ui_creator文件:

     1 --小岩<757011285@qq.com>
     2 --2015-5-30 12:45
     3 local ui_creator = {}
     4 
     5 ui_creator.creators_ = 
     6 {
     7     ccsprite = 
     8     {
     9         function(...) 
    10             return cc.Sprite:create(...) 
    11         end,
    12         function(...)
    13             return cc.Sprite:createWithSpriteFrameName(...)
    14         end
    15     },
    16 }
    17 
    18 function ui_creator.new_ccsprite(mode, ...)
    19     return ui_creator.creators_.ccsprite[mode](...)
    20 end
    21 
    22 return ui_creator

    可能很多人会奇怪这里面的mode参数是做什么用的,这个是用作资源管理用的,在开发初期的时候我们不会对资源进行合并处理,大部分都是散乱的放在各个文件夹中,而后期的时候我们会对资源进行合并.例如打包成为png大图等.通过浏览cocos2dx源码可以知道,cocos2dx的资源管理方式分为local和cache,所以我就提供了两种不同的创建接口.但是又消除了api的不同,对于使用者而言,仅仅是指定一下mode参数而已.对于api消除不同性接下来还要说.看ui_widget可以知道我提供的api和cocos2dx提供的api不同.所以我要消除这部分的不同性.不过要先来看一下ui_parser的实现:

     1 --小岩<757011285@qq.com>
     2 --2015-5-30 12:45
     3 local ui_creator = require "fw.ui.ui_creator"
     4 local checker    = require "fw.ui.check"
     5 local api_adapter= require "fw.ui.api_adapter"
     6 
     7 local ui_parser = {}
     8 
     9 -- 解析ui控件函数
    10 ui_parser.parsers =
    11 {
    12     img = function(cfg)
    13         checker(cfg.mode, "number", string.format("ui_parser.parsers_.img() -- wrong mode %d", tonumber(cfg.mode)))
    14         checker(cfg.img,  "string", string.format("ui_parser.parsers_.img() -- wrong img %s",  tostring(cfg.img)))
    15         local img_ins = ui_creator.new_ccsprite(cfg.mode, cfg.img)
    16         api_adapter.adapter_cc(img_ins)
    17         return img_ins
    18     end,
    19 }
    20 
    21 local function _strict_loop_with_given_list(list, callback)
    22     for index, item in ipairs(list) do
    23         callback(index, item)
    24     end
    25 end
    26 
    27 --解析ui,生成ui控件
    28 function ui_parser.parse(component_cfg)
    29     local ui_components_ = {}
    30     local ui_cbs_          = {}
    31     local unspports_     = {}
    32 
    33     --解析callback
    34     local function parse_cb(ui_cb)
    35         table.insert(ui_cbs_, ui_cb)
    36     end
    37 
    38     --解析ui控件
    39     local function parse_ui(ui_cfg)
    40         checker(ui_cfg.name,     "string",     string.format("ui_parser.parse.parse_ui() -- cfg name error!"))
    41         checker(ui_cfg.creator, "function", string.format("ui_parser.parse.parse_ui() -- cfg creator error!"))
    42 
    43         --处理节点相互添加
    44         if ui_cfg.parent and type(ui_cfg.parent) == "string" then
    45             parse_cb(function(ui_components)
    46                     local ui_component                 = ui_components[ui_cfg.name]
    47                     local ui_component_type         = ui_component.type_
    48                     local ui_component_parent         = ui_componens[ui_cfg.parent]
    49                     local ui_component_parent_type     = ui_component_parent.type_
    50                     if ui_component_type and ui_component_type == "ui_widget" then
    51                         ui_component:add_to_node(ui_component_parent)
    52                     else
    53                         if ui_component_parent_type and ui_component_parent_type == "ui_widget" then
    54                             ui_component_parent:add_child(ui_component)
    55                         end
    56                     end
    57                 end)
    58         end
    59 
    60         ui_components_[ui_cfg.name] = ui_cfg.creator(ui_cfg)
    61     end
    62 
    63     --保存unspport
    64     local function parse_unspports(unspport)
    65         table.insert(unspports_, unspport)
    66     end
    67 
    68     _strict_loop_with_given_list(component_cfg, function(_, item)
    69             local item_type = type(item)
    70             if item_type == "table" then
    71                 parse_ui(item)
    72             elseif item_type == "function" then
    73                 parse_cb(item)
    74             else
    75                 parse_unspports(item)
    76             end
    77         end)
    78 
    79     _strict_loop_with_given_list(ui_cbs_, function(_, cb)
    80             cb(ui_components_)
    81         end)
    82 
    83     ui_components_["unspport"] = unspports_
    84 
    85     return ui_components_
    86 end
    87 
    88 
    89 return ui_parser

    其中我做了很多的事情,例如很多时候我们在做UI布局的时候需要做相对适配,所以坐标不好指定,所以我在解析compoennt_cfg的时候认为如果提供的节点是function类型的话,那么就等所有的节点都解析完了之后统一操作,这样就可以处理相对布局的东西了.另外cocos2dx node派生的节点,和我们事先的ui_widget派生的节点如何相互添加的操作也做了.好了,创建的节点我在ui_parser.parsers中封装了,下面我们就来处理api的不同性,并消除掉。

     1 --小岩<757011285@qq.com>
     2 --2015-5-30 14:17
     3 local api_adapter = {}
     4 
     5 api_adapter.cc = 
     6 {
     7     parent = function(node, parent)
     8                 if type(parent) ~= "string" then
     9                     if parent.type_ and parent.type_ == "ui_widget" then
    10                         parent:add_child(node)
    11                     else
    12                         parent:addChild(node)
    13                     end
    14                 end
    15             end,
    16     pos   = function(node, pos)
    17                 node:setPosition(pos) 
    18             end,
    19     ap    = function(node, ap)
    20                 node:setAnchorPoint(ap)
    21             end,
    22     show  = function(node, show)
    23                 node:setVisible(show)
    24             end,
    25     content_size = function(node,  content_size)
    26                 node:setContentSize(content_size)
    27             end,
    28     rotation = function(node, rotation)
    29                 node:setRotation(rotation)
    30             end,
    31     scale = function(node, scale)
    32                 node:setScale(scale)
    33             end,
    34 
    35     scalex = function(node, scalex)
    36                 node:setScaleX(scalex)
    37             end,
    38     scaley = function(node, scaley)
    39                 node:setScaleY(scaley)
    40             end,
    41     rsx    = function(node, rsx)
    42                 node:setRotationSkewX(rsx)
    43             end,
    44     rsy    = function(node, rsy)
    45                 node:setRotationSkewY(rsy)
    46             end,
    47 }
    48 
    49 api_adapter.widget = 
    50 {
    51     parent = function(node, parent)
    52                 if type(parent) ~= "string" then
    53                         node:add_to_parent(parent)
    54                 end
    55             end,
    56     pos    = function(node, pos)
    57                 node:set_pos(pos)
    58             end,
    59     ap     = function(node, ap)
    60                 node:set_ap(ap)
    61             end,
    62     show   = function(node, show)
    63                 node:get_root_node():setVisible(show)
    64             end,
    65     content_size = function(node, content_size)
    66                 node:set_content_size(content_size)
    67             end,
    68 }
    69 
    70 function api_adapter.adapter_cc(node, cfg)
    71     if cfg.parent then api_adapter.cc.parent(node, cfg.parent) end
    72     if cfg.pos    then api_adapter.cc.pos(node, cfg.pos) end
    73     if cfg.ap     then api_adapter.cc.ap(node, cfg.ap) end
    74     if cfg.show or type(cfg.show) == "boolean" then api_adapter.cc.show(node, cfg.show) end
    75     if cfg.content_size then api_adapter.cc.content_size(node, cfg.content_size) end
    76     if cfg.rotation then api_adapter.cc.rotation(node, cfg.rotation) end
    77     if cfg.scale  then api_adapter.cc.scale(node, cfg.scale) end
    78     if cfg.scalex then api_adapter.cc.scalex(node, cfg.scalex) end
    79     if cfg.scaley then api_adapter.cc.scaley(node, cfg.scaley) end
    80     if cfg.rsx    then api_adapter.cc.rsx(node, cfg.rsx) end
    81     if cfg.rsy    then api_adapter.cc.rsy(node, cfg.rsy) end 
    82 end
    83 
    84 function api_adapter.adapter_widget(node, cfg)
    85     if cfg.parent then api_adapter.widget.parent(node, cfg.parent) end
    86     if cfg.pos    then api_adapter.widget.pos(node, cfg.pos) end
    87     if cfg.ap     then api_adapter.widget.ap(node, cfg.ap) end
    88     if cfg.show   then api_adapter.widget.show(node, cfg.show) end
    89     if cfg.content_size then api_adapter.widget.content_size(node, cfg.content_size) end
    90 end
    91 
    92 return api_adapter

    我按部就班的提供了上面的工具. 这样就消除了api不同性. 如果添加了我们自己的ui封装,响应的在creator中添加创建函数,在parser中添加响应的解析,在api_adaper中添加相应的函数.这样就做到了我前面预期的事情了。

    在这里要说一句抱歉,因为我并没有添加测试用例的功能,所以不能给大家提供直观的测试表现,很快我就会考虑添加的.这篇文章就到这里啦.希望对大家有用。

  • 相关阅读:
    局部组件
    flex布局
    Websocket
    关于Javascript夜里再来分析下
    go build、go mod等命令
    websocket
    FileSystemWatcher使用
    DataGridView双缓冲
    C#读INI文件
    c 通过 COM接口调用 Excel.Application 问题终于解决
  • 原文地址:https://www.cnblogs.com/respawn/p/4540381.html
Copyright © 2011-2022 走看看