别人提供了一些lua接口可以调用c#代码从而在unity中创建各种界面。
但是提供的lua接口我使用起来感觉非常的不舒服,所以想着稍微简化下,让自己用起来更舒适

设想

关于创建

-- 原本api创建按钮
API.ButtonCreate(parent_ui,button_name,button_show_text,x,y,w,h)
-- 我想要的
ui.btn {parent_ui=parent_ui,button_name=button_name,button_show=button_show,x=x,y=y,w=w,h=h}
  1. 传入参数是一个表
  2. 每个参数都有key也就是名字,这样就可以乱序传入,也知道每个参数的含义
  3. 参数具有默认值,如果默认值符合我创建时的要求,我就可以不用传入从而修改这个默认值
  4. 可以传入一些非创建所需要的参数,而是调整控件的参数,一般来说创建一个节点后还需各种修饰代码,我这样的话可以一行搞定

  1. 创建出一个节点后,可以围绕这个节点类似链式的创建其他节点
-- ...表示省略的参数
local btn = ui.btn(...)
local edit = btn + {type='btn',...} - {type='edit',...}
local img = edit * {type='group',...} / {type='checkbox',...} % {type='img',...}
  • 最理性的状态就是,使用链式调用,每行创建一个控件,把界面创建出来

关于属性

-- 原本api获取属性
local x = API.GetPositionX(btn)
-- 原本api设置属性
API.SetPositionX(btn,num)

-- 希望改成
local x = btn.x
btn.x = num

就希望简化这么点,多的也不要了,也不奢望改成像html样直接做个解释器

实现

讲一讲我的思路,这个简化的程序将返回一个表,调用这个表的方法时实际上是通过元表去调用,所有核心的部分都写在元表中。创建节点后返回的对象也是一个表,其中属性ui表示创建出的节点,是一个userdata数据类型,它还有一个s_type属性(simple type 简化的数据类型),使用它调用方法以及链式创建其他节点也是通过元表的方法实现。

  1. 首先弄好数据,来个表
m.d={
	-- 公共的方法
	common = {
		-- 控件获取属性的表
		get={
			-- 获取x属性
			x={func=原api方法,custom_func=自定义方法},
			...
		},
		-- 控件设置属性的表
		set={
			-- 设置x属性
			x={func=原api方法,custom_func=自定义方法},
			...
		},
		-- 获取属性时方法需要参数的表
		mul_params_get={
			-- 获取子节点需要字节的的名字参数
			child = {...}
		},
	},
	-- 每个控件各自的方法
	uinode = {
		简化的控件类型={type=原本api获取到的控件的类型, cre=创建这个节点的方法, ...}
		btn = {type='Button', cre = m.cre_btn, get={...}, set={...}, mul_params_get={...}},
		...
	},
}
  1. 写一个函数返回这个简化api的table
  • 这个表只能用来创建控件节点,并没有其他的功能
-- 根据s_type 返回控件的创建方法
m.find_cre_node_func = function(s_type)
    local node_tab = nil
    for k, v in pairs(m.d.ui_node) do
        if k == s_type then
            node_tab = v
            break
        end
    end
    if node_tab == nil then
        print('无法找到' .. s_type .. '类型的UI节点')
        return false
    end
    return node_tab.cre
end
-- 返回简化api表
m.cre = function(file_tab)
	-- 不要奇怪为啥file_tab没用到,这里删除了部分代码
    local tab = {}
    setmetatable(tab, {
        __newindex = function(my_tab, k, v)
            print('无法插入任何值 ' .. k)
        end,
        __index = function(my_tab, s_type)
            local cre_func = m.find_cre_node_func(s_type)
            if cre_func ~= false then
                return cre_func
            end
        end,
        __metatable = '无法修改'
    })
    return tab
end
  • 使用示例
-- 在其他的文件中使用这个简化api
local m = {}
-- 这个${filename} 代表当前文件的名称
m.script_name = '${filename}'
_G[m.script_name] = m
-- 这里的sui就是simple gui,就是简化api
-- 将这个文件的m表传入,然后调用了刚才定义的方法
local ui = sui.cre(m)

-- 开始使用
-- 这样就会调用刚才建好的表中的m.d.ui_noe.btn.cre方法,并将参数传入其中
local btn = ui.btn(...) -- 参数省略
-- 获取及修改其x属性
local x = btn.x
btn.x = x + 10
  1. 定义创建节点的方法 核心部分
  • 先定义几个需要的函数
-- 获取节点属性时 使用的方法
m.run_index_fun = function(ui, func_tab)
    if func_tab.custom_func ~= nil then
        return func_tab.custom_func(ui)
    else
        return func_tab.func(ui)
    end
end
-- 修改节点属性时使用的方法
m.run_new_index_fun = function(ui, func_tab, v)
    -- 如果是数组
    if type(v) == 'table' and v[1] ~= nil then
    -- 没有类似python的*args语法糖,本来打算使用loadstring,没成功,还是这样写吧
        if func_tab.custom_func ~= nil then
            func_tab.custom_func(ui, v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8], v[9], v[10])
        else
            func_tab.func(ui, v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8], v[9], v[10])
        end
    else
        if func_tab.custom_func ~= nil then
            func_tab.custom_func(ui, v)
        else
            func_tab.func(ui, v)
        end
    end
end
-- 使用+-*/%创建控件时使用 控制了父节点
m.run_operational_character = function(my_tab, new_tab, parent_ui)
    if new_tab.type == nil then
        print('无法创建,不存在type字段(UI Type)')
    else
        if m.d.ui_node[new_tab.type] == nil then
           print('无法创建,创建列表内不存在type字段(UI Type)')
            return
        end
        local s_type = new_tab.type
        new_tab.type = nil
        if new_tab.parent_ui ~= nil then
            new_tab.parent_ui = parent_ui
        else
            if new_tab[1] ~= nil then
                table.insert(new_tab, 1, parent_ui)
            else
                new_tab.parent_ui = parent_ui
            end
        end
        return m.d.ui_node[s_type].cre(new_tab)
    end
end
-- 返回一个元表数据 这个是最核心的部分
m.pack_metatable = function()
    local tab = {
        __index = function(my_tab, k)
            local s_type = my_tab.s_type
            -- 获取控件属性
            if m.d.common.get[k] then
                return m.run_index_fun(my_tab.ui, m.d.common.get[k])
            -- 获取带参数的控件属性
            elseif m.d.common.mul_params_get and m.d.common.mul_params_get[k] then
            	-- 闭包插入ui控件参数
                return function(...)
                    return m.d.common.mul_params_get[k](my_tab.ui, ...)
                end
             -- 获取控件属性 此类型的控件特有的
            elseif m.d.ui_node[s_type].get[k] then
                return m.run_index_fun(my_tab.ui, m.d.ui_node[s_type].get[k])
             -- 获取带参数的控件属性 此类型的控件特有的
            elseif m.d.ui_node[s_type].mul_params_get and m.d.ui_node[s_type].mul_params_get[k] then
                return function(...)
                    return m.d.ui_node[s_type].mul_params_get[k](my_tab.ui, ...)
                end
             -- 获取此控件的创建方法
            elseif m.d.ui_node[k] then
                return m.d.ui_node[k].cre
            else
                print('无法获取此值' .. k)
            end
        end,

        __newindex = function(my_tab, k, v)
            local s_type = my_tab.s_type
            -- 设置属性
            if m.d.common.set[k] then
                m.run_new_index_fun(my_tab.ui, m.d.common.set[k], v)
            -- 设置属性 此类型的控件特有的
            elseif m.d.ui_node[s_type].set[k] then
                m.run_new_index_fun(my_tab.ui, m.d.ui_node[s_type].set[k], v)
            else
                print('无法设置此值' .. k)
            end
        end,

        -- 打算作为复制当前节点,但是客户端那边没用对应的api
        __call = function(my_tab, ...)
        end,

		-- + 链式创建节点 位于当前节点的子节点
        __add = function(my_tab, new_tab)
            return m.run_operational_character(my_tab, new_tab, my_tab.ui)
        end,
        -- - 位于当前节点的同级节点
        __sub = function(my_tab, new_tab)
            return m.run_operational_character(my_tab, new_tab, API.GetParentElement(my_tab.ui))
        end,

        -- 因为+-的算术优先级低于*(当 btn+btn*btn时顺序会不对),而又需要三个,所以使用了*/%
        -- * 子节点
        __mul = function(my_tab, new_tab)
            return m.run_operational_character(my_tab, new_tab, my_tab.ui)
        end,
        -- / 同级节点
        __div = function(my_tab, new_tab)
            return m.run_operational_character(my_tab, new_tab, API.GetParentElement(my_tab.ui))
        end,
        -- % 父级同级节点
        __mod = function(my_tab, new_tab)
            return m.run_operational_character(my_tab, new_tab,API.GetParentElement(API.GetParentElement(my_tab.ui)))
        end,

        __metatable = '无法修改'

    }
    return tab
end
-- 使用原生API创建节点返回`userdata`类型的节点后调用的方法 设置其元表
m.pack_ui = function(ui)
    local tab = {}
    tab.ui = ui
    -- 根据其API获取的type类型获取简化的type类型
    -- 例如 根据Button 获取 btn
    local type = tostring(GUI.GetUIType(ui))
    for k, v in pairs(m.d.ui_node) do
        if type == v.type then
            tab.s_type = k
            break
        end
    end
    if tab.s_type == nil then
        print('包装ui节点失败,未找到对应的节点类型数据')
        return
    end
    -- 设置元表
    setmetatable(tab, m.pack_metatable())
    return tab
end
  • 使用创建btn来示例
-- 处理传入的参数  创建控件前调用
m.handle_params = function(...)
    -- 传入可能有三种情况
    -- 1. a,b,c
    -- 2. {a,b,c}
    -- 3. {parent_ui=,name=}
    local param = {}
    local p1 = select(1, ...)
    if p1 ~= nil then
        -- 如果时情况1 将其改成情况2
        if type(p1) ~= 'table' then
            if type(...) == 'userdata' then
                for k = 1, select('#', ...) do
                    param[k] = select(k, ...)
                end
            else
                for k, v in ipairs(...) do
                    table.insert(param, v)
                end
            end
        else
            param = p1
        end
    end
    return param
end
-- 替换默认的参数 创建控件前调用
m.replace_default_params = function(param, p)
    if param ~= nil and type(param) == 'table' then
        -- 情况2 {a,b,c}
        if param[1] ~= nil then
            for k, v in ipairs(p) do
                if param[k] ~= nil then
                    v.v = param[k]
                end
            end
            -- 情况3. {parent_ui=,name=}
        else
            for _, v in ipairs(p) do
                if param[v.n] ~= nil then
                    v.v = param[v.n]
                end
            end
        end
    end
    return p
end
  • 终于到创建的方法了 依赖了太多的其他的方法
m.cre_btn = function(...)
	-- 对参数进行处理
    local param = m.handle_params(...)
    -- 默认参数列表
    local p = {
        [1] = { n = 'parent_ui', v = nil },
        [2] = { n = 'name', v = 'btn' },
        [3] = { n = 'image_id', v = "1800402040" },
        [4] = { n = 'x', v = 0 },
        [5] = { n = 'y', v = 0 },
        [6] = { n = 'text', v = '' },
        [7] = { n = 'w', v = 125 },
        [8] = { n = 'h', v = 55 },
        [9] = { n = 'script_name', v = nil },
        [10] = { n = 'event_name', v = nil },
    }
    -- 替换默认参数
    p = m.replace_default_params(param, p)
    if p[1].v == nil then
        print('创建按钮节点时,没有父节点,创建失败')
        return
    end
    -- 使用原生api创建出控件
    local btn = API.CreateButton(p[1].v, p[2].v, p[3].v, p[4].v, p[5].v, p[6].v, p[7].v, p[8].v)
    -- 除了创建时的参数,添加点击事件
    if p[9].v and p[10].v then
        API.RegisterEvent(btn, API.PointerClick, p[9].v, p[10].v)
    end
    -- 将其进行包装 添加原表
    btn = m.pack_ui(btn)
    return btn
end
  • 当修改或获取属性时
-- 回到前面的代码
local x = btn.x
-- 它的执行流程是
-- 寻找btn表中x的属性
-- 发现btn表中没有这个属性,从元表中获取
-- 元表中也没有这个属性,从而调用__index元表函数
-- 从而执行到m.pack_metatable方法中的这一行
if m.d.common.get[k] then
           return m.run_index_fun(my_tab.ui, m.d.common.get[k])
-- 它将找到数据表中的m.d.common.get.x方法
-- 然后执行原生的方法 并返回其结果 类似于
local x = API.GetPositionX(my_tab.ui)
-- 更进一步类似于
local x = API.GetPositionX(btn.ui)
-- 修改属性
btn.x = x + 10
-- 同样的类似于上面的流程
-- 执行到元表中的__newindex元表函数 执行到这一行
if m.d.common.set[k] then
           m.run_new_index_fun(my_tab.ui, m.d.common.set[k], v)
-- 从数据表中找到m.d.common.set.x方法
-- 最终类似于
API.SetPositionX(btn.ui,v)

最后

在创建那个数据表填入数据的时候,发现如果查看API文档一个一个填进去太过于麻烦,所以写了一个函数,遍历API的所有函数,通过正则表达式,将需要的内容晒选出来,然后打印,在复制粘贴到数据表中。

m.get_func_tab = function()
    local common = { get = {}, set = {} }
    local btn = { get = {}, set = {} }
	-- 筛选函数
    local func = function(k, str, tab)
        -- lua 正则不能匹配 | 所以get、set分别谢开
        local get_str = str .. 'Get'
        if string.match(k, '^' .. get_str) ~= nil then
            local tmp = {}
            tmp.func = 'API.' .. k
            k = string.match(k, '^' .. get_str .. '(.*)')
            k = k:gsub('_', '')
            k = k:gsub('%u+', function(s)
                return '_' .. s:lower()
            end)
            if k == '' then
                k = 'get'
            end
            k = k:gsub('^_', '')

            local collect_tab = tab.get
            if collect_tab.count == nil then
                collect_tab.count = 1
            else
                collect_tab.count = collect_tab.count + 1
            end
            collect_tab[k] = tmp
        end

        local set_str = str .. 'Set'
        if string.match(k, '^' .. set_str) ~= nil then
            local tmp = {}
            tmp.func = 'API.' .. k
            k = string.match(k, '^' .. set_str .. '(.*)')
            k = k:gsub('_', '')
            k = k:gsub('%u+', function(s)
                return '_' .. s:lower()
            end)
            if k == '' then
                k = 'set'
            end
            k = k:gsub('^_', '')

            local collect_tab = tab.set
            if collect_tab.count == nil then
                collect_tab.count = 1
            else
                collect_tab.count = collect_tab.count + 1
            end
            collect_tab[k] = tmp
        end

    end
    -- 执行筛选
    for k, v in pairs(API) do
        k = tostring(k)
        func(k, '', common)
        func(k, 'Button', btn)
    end
    -- 打印函数
    local p_func = function(tab, str)
    	-- 打印get、set数量
        print('getCount: ' .. tab.get.count .. ' setCount:' .. tab.set.count)
        tab.get.count = nil
        tab.set.count = nil
        -- inspect 是一个格式化table的函数
        print(str .. '.    get=' .. inspect(tab.get) .. ',')
        print(str .. '.    set=' .. inspect(tab.set) .. ',')
    end
    p_func(common, 'common')
    p_func(btn, 'btn')
end
  • 类似的打印结果
getCount: 2 setCount:2

get = {
    position_x = {
        func = "API.GetPositionX"
    },
    position_x = {
        func = "API.GetPositionX"
    },
},

set = {
    position_x = {
        func = "API.SetPositionX"
    },
    position_x = {
        func = "API.SetPositionX"
    },
},

注意到每个函数都被双引号"包着,需要去除下。使用vim的宏,录制一个宏

qajJJF"x;.q

根据打印出来的数量执行宏

2@a
2@a