zoukankan      html  css  js  c++  java
  • cocos2dx-lua UI编辑器的设计思路

      在目前的cocos2dx项目开发中,基本只有2个编辑器可选。一个是现在官方推荐的CocosCreator, 但它并不支持我们常用的lua脚本。另一个是CocosStudio, 官方已经不再对其维护,且使用也并不太方便。那么有没有什么方法能让编辑器更好的接入lua脚本,又能方便地自定义控件呢?

      想要编辑器对lua脚本更好地支持,那么我们的编辑器可以用lua来开发,这样编辑器可以直接分析lua代码。这里推荐使用imgui来开发编辑器, 比直接用cocos-x更快,关于imgui请点击:cocos2dx上适合做工具的UI库ImGui。为了让实现编辑器中结点名与lua变量的自动绑定,我想到了让所有界面继承于BaseLayer,在基类中自动处理lua变量和事件的绑定。一个典型的游戏界面如下,由编辑器自动生成:

    local XxxLayer = class("XxxLayer", require("common.BaseLayer"), function()
        return display.newLayer()
    end)
    -- UI结构定义
    XxxLayer.uiTree = {}
    -- 自定义内容
    
    -- 构造函数
    --[[
        params:
    --]]
    function XxxLayer:ctor(params)
        -- 初始化Layer, 可传入参数详情参见BaseLayer
        self.super.ctor(self)
        -- 初始化UI
        self:initUI()
    end
    -- 初始化UI
    function XxxLayer:initUI()
        self:createUITree()
        -- 进行UI的额外调整
    end
    return XxxLayer

    其中createUITree就是由BaseLayer提供的方法,主要创建uiTree中定义的控件。我们约定"-- 自定义内容"之前的交给编辑器,用户不能编辑(编辑器保存时会被重置)。uiTree中保存的是结点的树形结构,里面定义了结点的类型及各种创建参数,比如加入一个按钮后,会变成:

    XxxLayer.uiTree = {[1]={["cType"]="Button",["children"]={},["name"]="closeBtn",["params"]={["image"]="c_13.png",["pos"]={["x"]=320,["y"]=568,},["scale"]=1,},},}

      为了更好的支持控件的自定义,我们可以定义一种简单的控件格式,如:

    UIWrap = {
        --[[
            return {
                hint = "创建sprite",
                params = {
                    {name = "image", type="file", hint = "图片名", required = "c_11.png"},
                    {name = "pos",  type="ccp", hint = "位置", required = cc.p(100, 100)},
                    {name = "anchor",  type="ccp", hint = "锚点"},
                    {name = "scale",  type="number", hint = "缩放"},
                },
            }
        --]]
        ["Sprite"] = function (params)
            local retSprite = display.newSprite(params.image, params.pos and params.pos.x, params.pos and params.pos.y)
            if params.anchor then
                retSprite:setAnchorPoint(params.anchor)
            end
            if params.scale then
                retSprite:setScale(params.scale)
            end
            return retSprite
        end,
    }

    所有控件都通过此格式来定义,编辑器打开时就可以解析这个lua文件,获得所有的控件类型。其中hint表示控件或参数提示,params中是此控件的所有参数,type主要用于参数设定的个性化(如ccp类型, number类型, file类型甚至可以直接给出文件选择),required表示此参数必须设置并给出了缺省值。可以看到此控件格式最终返回的是一个function, 那么编辑器中创建Sprite结点时,实际上就是调用了一次此function, 而实际游戏代码中仍然也是调用此function来创建Sprite结点, 那么就实现了编辑中的所见及所得。

      BaseLayer:createUITree方法里面主要是循环创建uiTree里面定义的结点,并将结点名和事件与layer进行绑定。大致流程如下:

    function BaseLayer:createUITree()
        -- 创建一个cocos2dx结点列表并添加到指定的父结点上
        local function createChildrenNode(children, parentNode)
            for _,item in ipairs(children) do
                -- 使用编辑器中的参数创建结点
                local newNode = UIWrap[item.cType](item.params)
                parentNode:addChild(newNode)
                --[[
                    默认的结点名以"untitled"开头,表示我们并不关心此结点
                    将需要访问的结点,直接绑定到self上
                    如结点名为"closeBtn", 代码中可以用self.closeBtn直接访问
                --]]
                if not string.find(item.name, "untitled") then
                    self[item.name] = newNode
                end
                -- 创建子结点
                if next(item.children) then
                    createChildrenNode(item.children, newNode)
                end
            end
        end
        -- 从uiTree开始创建
        createChildrenNode(self.uiTree, self)
    end

      按钮事件绑定: 有些特殊控件(如button等)需要设定点击回调,在lua代码中对应的是function类型。为了实现编辑器中参数与lua代码中function的绑定,我们在编辑器中记录下绑定的lua函数名(通过分析Layer的function类型可获得所有函数名)。然后在创建结点之前,将回调参数与Layer中的函数进行绑定:

    -- 结点创建之前,点击事件绑定
    for key, value in pairs(item.params) do
        -- 我们规定点击事件必须以"on"开头来过滤Layer中普通的函数名
        if type(value) == "string" and string.find(value, "on") == 1 and self[value] then
            item.params[key] = handler(self, self[value])
        end
    end

      模版控件处理: 对于像ListView这样的控件,需要特殊处理。我们可以定义一个特殊控件”Layout“,它并不随Layer的创建而创建(在createChildrenNode中发现控件类型为Layout时,则仅记录其结点树数据,不创建其结点),只能代码调用BaseLayer:createLayoutNode来创建。createLayoutNode函数与createUITree类似,只是创建内容变成了Layout和它的子结点。填充ListView时,可以这样:

    -- 填充ListView数据
    for i=1,10 do
        local childLayout = self:createLayoutNode("ListLayout")
        self.goodsListView:pushBackCustomItem(childLayout)
        -- 下面对各childLayout设置表现差异
    end

    我们甚至可以在编辑器中创建多个不同的Layout,来丰富ListView上的显示效果(如ListView中最后显示一个"更多")。

      在编辑器实现基本的布局、增删、修改功能后,控件类型的增加就已经和编辑器无关了。我们甚至可以创建一些复杂的控件类型,如英雄头像,英雄星级之类。比如之间仅仅就是创建的starNum参数值不一样而已。

  • 相关阅读:
    [leetcode]259. 3Sum Smaller 三数之和小于目标值
    题型总结之K Sum
    [Leetcode]167. Two Sum II
    题型总结之Sliding Window
    [Leetcode]703. Kth Largest Element in a Stream 数据流中的第 K 大元素
    [Leetcode]307. Range Sum Query
    pycharm同一目录下无法import明明已经存在的.py文件
    python高级特性:迭代器与生成器
    self的含义,为什么类调用方法时需要传参数?
    git三:远程仓库GitHub
  • 原文地址:https://www.cnblogs.com/hghhe/p/9673579.html
Copyright © 2011-2022 走看看