【游戏开发解答】Unity使用lua将table转为树结构,以多级折叠内容列表的UI形式展现(树结构 | UGUI | 折叠展开 | lua)
文章目录
- 一、前言
- 二、Unity lua环境
- 三、树节点
- 1、创建脚本:TreeNode.lua
- 2、封装节点
- 四、树逻辑
- 1、创建脚本:TreeLogic.lua
- 2、构造测试数据
- 3、构造树
- 4、打印树
- 五、使用UGUI显示树
- 1、制作界面预设
- 2、创建界面脚本:TreePanel.lua
- 3、展开节点(递归)
- 4、关闭节点(递归)
- 六、测试
- 七、更新(2022/03/25)
本文最终效果
一、前言
嗨,大家好,我是新发。
有粉丝问了我这个问题,
我以为他是拿到一个纯文本数据,他不知道如何去解析,如果单纯是想解析数据,完全不需要使用树。
看格式的话是lua
的table
,我们如果拿到一个纯文本数据,并且格式是lua
的table
的格式的话,可以使用loadstring
方法去执行纯文本得到一个table
对象,例:
local data_str = '{ a = 1, b = 2, { c=3, d = 4}, { e = { x = 5 }}}'
local func = loadstring('return ' .. data_str)
local tb = func()
-- TODO 解析tb这个table
这里需要注意,loadstring
方法在lua 5.2
及以上版本改为了load
,所以如果你的lua
版本是5.2
及以上版本需要注意,我们可以查看lua
源码的lbaselib.c
文件,
经过确认,他那边已经转化成了lua
的table
了,只是不知道怎么用树去构造出来递归遍历,
好了,现在就来讲讲具体怎么做吧~
注:上面提到的
红点
是指我之前写的另一篇文章:【游戏开发实战】手把手教你在Unity中使用lua实现红点系统(前缀树 | 数据结构 | 设计模式 | 算法 | 含工程源码)
二、Unity lua环境
因为要使用lua
,而Unity
默认用的是C#
,所以我们得搞个lua
框架进来,正好,我之前自己搭建了一个游戏框架UnityXFramework
,里面集成了tolua
框架,详细可以查看我之前写的这篇博客:【游戏开发框架】自制Unity通用游戏框架UnityXFramework,详细教程(Unity3D技能树 | tolua | 框架 | 热更新)
框架开源地址:https://gitcode.net/linxinfa/UnityXFramework
这里我就在框架的环境中去写lua
逻辑吧~
三、树节点
1、创建脚本:TreeNode.lua
先在LuaFramework/Lua/Logic
目录中新建一个Tree
目录,
在Tree
目录中新建一个TreeNode.lua
脚本,
2、封装节点
先思考一下,一个节点需要的信息,画个图,
现在我们写下代码,TreeNode.lua
脚本代码如下,
-- TreeNode.lua 树节点TreeNode = TreeNode or {}
TreeNode.__index = TreeNodefunction TreeNode.New(name)local self = {}-- 节点名self.name = name-- 值self.value = nil-- 父节点self.parent = nil-- 子节点self.child = nil-- 缩进self.tab = 0-- 是否展开self.isopen = true-- UI对象self.uiObj = nilsetmetatable(self, TreeNode)return self
end
四、树逻辑
1、创建脚本:TreeLogic.lua
我们再在Tree
目录中新建一个TreeLogic.lua
脚本,
先写个Init
方法,留个TODO
,如下
-- TreeLogic.lua 树逻辑TreeLogic = TreeLogic or {}
local this = TreeLogic-- 根节点
this.root = nil-- 初始化
function TreeLogic.Init()-- TODOend
2、构造测试数据
我们要构造一棵树,得先有数据,根据需求,数据就是一个table
,简单写一个,
-- 测试数据
local data_table = {name = "林新发",university = "华南理工大学",major = '信息工程',job = 'Unity3D游戏开发工程师',blog = 'https://blog.csdn.net/linxinfa',hobby = {'吉他', '钢琴', '画画', '撸猫'},dream = {developer = {target = '成为一名优秀的独立游戏开发者',style = {'ARPG', 'FPS', 'SLG', 'MOBA'}},painter = {target = '成为一个独立画家',magnum_opus = {'暴走柯南', '皮皮猫', '光'}},musician = {target = '成为一个独立音乐人',magnum_opus = {'尘土', '树与风'}}}
}
3、构造树
接着我们用测试数据去构造一棵树,我们封装一个MakeTree
方法,其实构造过程我们只需要把注意力放在一个节点的构造上即可,设置节点的名称、值,设置节点的父子节点关系,然后递归执行。
代码如下
-- TreeLogic.lua-- 构造树
-- tb: 数据table
-- parent: 父节点
function TreeLogic.MakeTree(tb, parent)-- 遍历tablefor k, v in pairs(tb) do-- 新建一个节点local node = TreeNode.New(k)node.value = v-- 设置父节点node.parent = parent-- 子节点缩进+1node.tab = parent.tab + 1-- 父节点的child塞入nodeif nil == parent.child thenparent.child = {}endparent.child[k] = node-- 如果v是table,则递归遍历if type(v) == 'table' then-- 有子节点,默认不展开node.isopen = falsethis.MakeTree(v, node)endendreturn parent
end
接着,我们在Init
方法中调用MakeTree
方法,
-- 初始化
function TreeLogic.Init()-- 测试数据 data_table = {}-- ...-- 根节点this.root = TreeNode.New("Root")-- 构造树this.root = this.MakeTree(data_table, this.root)
end
到这里,我用了30
行左右的代码完成了节点的封装和树的构造,接下来就是树的UI
显示了。
4、打印树
在做UI
显示之前,我们不妨封装一个打印树的方法,验证一下我们的树结构,
-- 把树转为字符串
function TreeLogic.TreeToString(node, str)if nil ~= node.value thenlocal tabspace = ''for i = 1, node.tab dotabspace = tabspace .. ' 'endif 'table' == type(node.value) thenstr = str .. string.format('%s▼ %s :\n', tabspace, node.name)elsestr = str .. string.format('%s● %s : %s\n', tabspace, node.name, tostring(node.value))endendif nil ~= node.child thenfor _, child_node in pairs(node.child) do-- 递归str = this.TreeToString(child_node, str)endendreturn str
end
我们调用一下
-- 打印树
local str = ''
str = this.TreeToString(this.root, str)
log(str)
输出结果如下
输出正常,愉快地继续吧~
五、使用UGUI显示树
1、制作界面预设
制作一个TreePanel.prefab
界面预设,
如下
界面层级结构如下,其中我用了VerticalLayoutGroup
组件来做垂直布局,这样添加子节点的时候就会自动垂直排列了,
其中item
上挂一个Button
用于监听点击,子节点是一个Text
,缩进就通过Text
的坐标右移来实现即可,
2、创建界面脚本:TreePanel.lua
在LuaFramework/Lua/View
目录中创建一个Tree
文件夹,然后创建一个TreePanel.lua
脚本,编写界面代码,
界面代码我做了模板,可以参见我之前写的框架教程:【游戏开发框架】自制Unity通用游戏框架UnityXFramework,详细教程(Unity3D技能树 | tolua | 框架 | 热更新) 的8.2
小节:
下面我重点写下递归展开树节点和关闭节点的逻辑~
3、展开节点(递归)
封装一个张开节点的方法ExpanNode
,里面我用到了递归,代码我写了注释,这里就不多解释啦,
-- TreePanel.lua-- 展开节点
function TreePanel.ExpanNode(node)if nil == node.child thenreturnendlocal index = 1for _, child_node in pairs(node.child) do-- 创建节点的UI对象local uiObj = LuaUtil.CloneObj(this.tiemForClone)local text = uiObj.transform:GetChild(0):GetComponent("Text")child_node.uiObj = uiObjif not LuaUtil.IsNilOrNull(node.uiObj) then-- 子节点塞在父节点下面local siblingIndex = node.uiObj:GetComponent("RectTransform"):GetSiblingIndex()child_node.uiObj:GetComponent("RectTransform"):SetSiblingIndex(siblingIndex + index)index = index + 1endif type(child_node.value) == 'table' thentext.text = (child_node.isopen and '▼ ' or '► ') .. child_node.nameelsetext.text = '● ' .. child_node.name .. ': ' .. child_node.valueend-- 坐标缩进text.transform.localPosition = text.transform.localPosition + Vector3.New((child_node.tab-1)*50, 0,0)uiObj:GetComponent("Button").onClick:AddListener(function()if not child_node.isopen thenchild_node.isopen = true-- 递归, 展开子节点this.ExpanNode(child_node)else-- 关闭子节点this.CloseNode(child_node)endif type(child_node.value) == 'table' thentext.text = ( child_node.isopen and '▼ ' or '► ') .. child_node.nameendend)end
end
我们需要传入树的根节点,我们给TreeLogic.lua
添加一个GetTree
方法,
-- TreeLogic.luafunction TreeLogic.GetTree()return this.root
end
接着我们去调用ExpanNode
方法,如下
local tree = TreeLogic.GetTree()
this.ExpanNode(tree)
4、关闭节点(递归)
关闭节点也封装一个方法CloseNode
,依然使用了递归,如下
-- 关闭子节点
function TreePanel.CloseNode(node)if LuaUtil.IsNilOrNull(node.child) thenreturnendnode.isopen = falsefor _, child in pairs(node.child) dochild.isopen = falseLuaUtil.SafeDestroyObj(child.uiObj)if nil ~= child.child then-- 递归关闭子节点this.CloseNode(child)endend
end
六、测试
好啦,现在我们测试一下效果吧,
完美,收工~
代码我已经提交到框架上了,可下载工程进行查阅,https://gitcode.net/linxinfa/UnityXFramework
七、更新(2022/03/25)
文章发布后,这位同学自己顺利做出来了,
不过昨天他又发了消息问我如何保持上一次展开的节点或状态,
我改造了一下UI
层的代码,把节点的UI
对象缓存到table
里,展开时先从缓存中取UI
对象,关闭节点时隐藏UI
对象,保持子节点的isopen
数据状态,展开时进行递归,最后改版后的代码如下:
-- TreePanel.lua
-- ...
this.uiNodeTb = nilfunction TreePanel:OnShow(parent)-- ...this.uiNodeTb = {}
end-- ...-- 展开节点
function TreePanel.ExpanNode(node)if nil == node.child thenreturnendlocal index = 1for _, child_node in pairs(node.child) dolocal uiUnit = {}if this.uiNodeTb[child_node] then-- 从缓存中取ui对象uiUnit = this.uiNodeTb[child_node]-- 显示LuaUtil.SafeActiveObj(uiUnit.obj, true)if child_node.isopen then-- 递归, 展开子节点this.ExpanNode(child_node)endelse-- 创建节点的UI对象uiUnit.obj = LuaUtil.CloneObj(this.tiemForClone)uiUnit.text = uiUnit.obj.transform:GetChild(0):GetComponent("Text")uiUnit.btn = uiUnit.obj:GetComponent("Button")-- 坐标缩进uiUnit.text.transform.localPosition = uiUnit.text.transform.localPosition +Vector3.New((child_node.tab - 1) * 50, 0, 0)child_node.uiObj = uiUnit.objif not LuaUtil.IsNilOrNull(node.uiObj) then-- 子节点塞在父节点下面local siblingIndex = node.uiObj:GetComponent("RectTransform"):GetSiblingIndex()child_node.uiObj:GetComponent("RectTransform"):SetSiblingIndex(siblingIndex + index)index = index + 1enduiUnit.btn.onClick:AddListener(function()if not child_node.isopen thenchild_node.isopen = true-- 递归, 展开子节点this.ExpanNode(child_node)else-- 关闭子节点this.CloseNode(child_node, false)endif type(child_node.value) == 'table' thenuiUnit.text.text = (child_node.isopen and '▼ ' or '► ') .. child_node.nameendend)this.uiNodeTb[child_node] = uiUnitend-- 更新展开文本if type(child_node.value) == 'table' thenuiUnit.text.text = (child_node.isopen and '▼ ' or '► ') .. child_node.nameelseuiUnit.text.text = '● ' .. child_node.name .. ': ' .. child_node.valueendend
end-- 关闭子节点
function TreePanel.CloseNode(node, onlyHide)if LuaUtil.IsNilOrNull(node.child) thenreturnendif not onlyHide thennode.isopen = falseendfor _, child in pairs(node.child) doLuaUtil.SafeActiveObj(child.uiObj, false)if nil ~= child.child then-- 递归关闭子节点this.CloseNode(child, true)endend
end
运行效果如下:
展开全部节点和关闭全部节点的代码我没写,就当做一个小作业留给大家思考吧~
好了,我是林新发,https://blog.csdn.net/linxinfa
一个在小公司默默奋斗的Unity
开发者,希望可以帮助更多想学Unity
的人,共勉~
【游戏开发解答】Unity使用lua将table转为树结构,以多级折叠内容列表的UI形式展现(树结构 | UGUI | 折叠展开 | lua)相关推荐
- 王楠——海外华人程序员视角:解密移动游戏开发与Unity 4引擎特性
海外华人程序员视角:解密移动游戏开发与Unity 4引擎特性 发表于2012-09-07 17:24| 12534次阅读| 来源CSDN整理| 0 条评论| 作者杨依帆 游戏开发Unity游戏引擎王楠 ...
- Unity 2D游戏开发视频教程 Unity 2D Game Developer Course Farming RPG
Unity 2D游戏开发视频教程 Unity 2D Game Developer Course Farming RPG Unity 2D游戏开发课程农业RPG MP4 |视频:h264,1280×72 ...
- 游戏开发技术Unity开发引擎
Unity 是一个完全集成的开发引擎,提供了丰富的开箱即用的功能,用于创建游戏及其他互动式3D内容.您可以使用Unity 将艺术和资源组装到场景和环境:增加物理效果:同步播放测试和编辑您的游戏,并在准 ...
- 【2d游戏开发】unity实现UI框架搭建
前言 前面一直比较忙,然后到现在才继续接游戏的文章,那么本次将带大家去搭建一个ui框架,同样,需要更具体的教学,可以到b站搜索本人的关于2d游戏开发-unity实现xxx的系列视频. 步骤 其实大致的 ...
- 【从零开始游戏开发】Unity 前后端网络通信该如何搭建?注释解答 | 全面总结 |建议收藏
你知道的越多,你不知道的越多
- 【游戏开发】unity教程4 打飞碟小游戏
github传送门:https://github.com/dongzizhu/unity3DLearning/tree/master/hw4/Disk 视频传送门:https://space.bili ...
- 【游戏开发】unity教程8 用IMGUI和UGUI实现血条
github传送门:https://github.com/dongzizhu/unity3DLearning/tree/master/hw8/enhancedDisk 视频传送门:https://sp ...
- 【游戏开发】unity教程10 VR库 Vuforia
github传送门:https://github.com/dongzizhu/unity3DLearning/tree/master/hw10/ARtest B站传送门:https://space.b ...
- 【从零开始游戏开发】Unity优化:UI控件优化 | 全面总结 |建议收藏
你知道的越多,你不知道的越多
最新文章
- 服务器主机防御系统,主机入侵防御系统
- python基础知识资料-学习Python列表的基础知识汇总
- C语言经典例83-求0—7所能组成的奇数个数
- Android项目创建欢迎页
- python有多少关键字_Python挖词脚本,挖出几十万关键词不是梦带搜索量
- python管道符_Python实现处理管道的方法
- arm linux 开机电路_【技术角度看问题之一】ARM到底是个啥?
- 机器学习中的决策树算法
- 通俗地讲解傅立叶分析和小波分析间的关系
- pngimg 可以商用吗_避免侵权!这10个免费可商用的图片网站,请一定收好
- Java定时任务处理异常空指针,执行定时器报空指针异常
- oracle临时表教程,在oracle存储过程中创建临时表
- ParameterizedTypeReference使用
- 程序员35岁前成功的12条黄金法则
- Odoo产品分析 (三) -- 人力资源板块(6) -- 工资表(2)
- JavaScript中的表单编程
- Win10:Windows找不到文件‘Gpedit.msc‘。请确定文件名是否正确后,再试一次。
- OpenJudge NOI题库1.1答案
- VIM 用正则表达式,非贪婪匹配,匹配竖杠,竖线, 匹配中文,倒数第二列, 匹配任意一个字符 :...
- 我的Java学习之路(六)-- 银行卡系统
热门文章
- python::安装pylab模块
- Ubuntu16.04无法更新apt,一直报错
- android 实现在照片上涂鸦
- 绘画小白怎么画纹理?该怎么去处理画面中的纹理?
- SQL注入的简单案例
- 串口上升时间标准_自动控制理论中: 上升时间和峰值时间有什么不同
- WLAN二层旁挂组网与三层旁挂组网
- 华为交换机修改console密码
- php 跳转设置header,PHP跳转Header(“location:”)的注意事项
- smartforms rotate text 文字方向旋转