文章目录

  • 一、前言
  • 二、Unity lua环境
  • 三、树节点
    • 1、创建脚本:TreeNode.lua
    • 2、封装节点
  • 四、树逻辑
    • 1、创建脚本:TreeLogic.lua
    • 2、构造测试数据
    • 3、构造树
    • 4、打印树
  • 五、使用UGUI显示树
    • 1、制作界面预设
    • 2、创建界面脚本:TreePanel.lua
    • 3、展开节点(递归)
    • 4、关闭节点(递归)
  • 六、测试
  • 七、更新(2022/03/25)

本文最终效果

一、前言

嗨,大家好,我是新发。
有粉丝问了我这个问题,

我以为他是拿到一个纯文本数据,他不知道如何去解析,如果单纯是想解析数据,完全不需要使用树。
看格式的话是luatable,我们如果拿到一个纯文本数据,并且格式是luatable的格式的话,可以使用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文件,

经过确认,他那边已经转化成了luatable了,只是不知道怎么用树去构造出来递归遍历,

好了,现在就来讲讲具体怎么做吧~

注:上面提到的红点是指我之前写的另一篇文章:【游戏开发实战】手把手教你在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)相关推荐

  1. 王楠——海外华人程序员视角:解密移动游戏开发与Unity 4引擎特性

    海外华人程序员视角:解密移动游戏开发与Unity 4引擎特性 发表于2012-09-07 17:24| 12534次阅读| 来源CSDN整理| 0 条评论| 作者杨依帆 游戏开发Unity游戏引擎王楠 ...

  2. 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 ...

  3. 游戏开发技术Unity开发引擎

    Unity 是一个完全集成的开发引擎,提供了丰富的开箱即用的功能,用于创建游戏及其他互动式3D内容.您可以使用Unity 将艺术和资源组装到场景和环境:增加物理效果:同步播放测试和编辑您的游戏,并在准 ...

  4. 【2d游戏开发】unity实现UI框架搭建

    前言 前面一直比较忙,然后到现在才继续接游戏的文章,那么本次将带大家去搭建一个ui框架,同样,需要更具体的教学,可以到b站搜索本人的关于2d游戏开发-unity实现xxx的系列视频. 步骤 其实大致的 ...

  5. 【从零开始游戏开发】Unity 前后端网络通信该如何搭建?注释解答 | 全面总结 |建议收藏

    你知道的越多,你不知道的越多

  6. 【游戏开发】unity教程4 打飞碟小游戏

    github传送门:https://github.com/dongzizhu/unity3DLearning/tree/master/hw4/Disk 视频传送门:https://space.bili ...

  7. 【游戏开发】unity教程8 用IMGUI和UGUI实现血条

    github传送门:https://github.com/dongzizhu/unity3DLearning/tree/master/hw8/enhancedDisk 视频传送门:https://sp ...

  8. 【游戏开发】unity教程10 VR库 Vuforia

    github传送门:https://github.com/dongzizhu/unity3DLearning/tree/master/hw10/ARtest B站传送门:https://space.b ...

  9. 【从零开始游戏开发】Unity优化:UI控件优化 | 全面总结 |建议收藏

    你知道的越多,你不知道的越多

最新文章

  1. 服务器主机防御系统,主机入侵防御系统
  2. python基础知识资料-学习Python列表的基础知识汇总
  3. C语言经典例83-求0—7所能组成的奇数个数
  4. Android项目创建欢迎页
  5. python有多少关键字_Python挖词脚本,挖出几十万关键词不是梦带搜索量
  6. python管道符_Python实现处理管道的方法
  7. arm linux 开机电路_【技术角度看问题之一】ARM到底是个啥?
  8. 机器学习中的决策树算法
  9. 通俗地讲解傅立叶分析和小波分析间的关系
  10. pngimg 可以商用吗_避免侵权!这10个免费可商用的图片网站,请一定收好
  11. Java定时任务处理异常空指针,执行定时器报空指针异常
  12. oracle临时表教程,在oracle存储过程中创建临时表
  13. ParameterizedTypeReference使用
  14. 程序员35岁前成功的12条黄金法则
  15. Odoo产品分析 (三) -- 人力资源板块(6) -- 工资表(2)
  16. JavaScript中的表单编程
  17. Win10:Windows找不到文件‘Gpedit.msc‘。请确定文件名是否正确后,再试一次。
  18. OpenJudge NOI题库1.1答案
  19. VIM 用正则表达式,非贪婪匹配,匹配竖杠,竖线, 匹配中文,倒数第二列, 匹配任意一个字符 :...
  20. 我的Java学习之路(六)-- 银行卡系统

热门文章

  1. python::安装pylab模块
  2. Ubuntu16.04无法更新apt,一直报错
  3. android 实现在照片上涂鸦
  4. 绘画小白怎么画纹理?该怎么去处理画面中的纹理?
  5. SQL注入的简单案例
  6. 串口上升时间标准_自动控制理论中: 上升时间和峰值时间有什么不同
  7. WLAN二层旁挂组网与三层旁挂组网
  8. 华为交换机修改console密码
  9. php 跳转设置header,PHP跳转Header(“location:”)的注意事项
  10. smartforms rotate text 文字方向旋转