昨天在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 --小岩<[email protected]> 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 --小岩<[email protected]> 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 --小岩<[email protected]> 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 --小岩<[email protected]> 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中添加相应的函数.这样就做到了我前面预期的事情了。
在这里要说一句抱歉,因为我并没有添加测试用例的功能,所以不能给大家提供直观的测试表现,很快我就会考虑添加的.这篇文章就到这里啦.希望对大家有用。