别人提供了一些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}
- 传入参数是一个表
- 每个参数都有key也就是名字,这样就可以乱序传入,也知道每个参数的含义
- 参数具有默认值,如果默认值符合我创建时的要求,我就可以不用传入从而修改这个默认值
- 可以传入一些非创建所需要的参数,而是调整控件的参数,一般来说创建一个节点后还需各种修饰代码,我这样的话可以一行搞定
- 创建出一个节点后,可以围绕这个节点类似链式的创建其他节点
-- ...表示省略的参数
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 简化的数据类型),使用它调用方法以及链式创建其他节点也是通过元表的方法实现。
- 首先弄好数据,来个表
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={...}},
...
},
}
- 写一个函数返回这个简化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
- 定义创建节点的方法 核心部分
- 先定义几个需要的函数
-- 获取节点属性时 使用的方法
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
、