【游戏开发解答】Unity使用lua将table转为树结构,以多级折叠内容列表的UI形式展现(树结构 | UGUI | 折叠展开 | lua)

2023-05-16

文章目录

      • 一、前言
      • 二、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 = TreeNode

function 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 = nil

    setmetatable(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()
    -- TODO
    
end

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)
    -- 遍历table
    for k, v in pairs(tb) do
        -- 新建一个节点
        local node = TreeNode.New(k)
        node.value = v
        -- 设置父节点
        node.parent = parent
        -- 子节点缩进+1
        node.tab = parent.tab + 1
        -- 父节点的child塞入node
        if nil == parent.child then
            parent.child = {}
        end
        parent.child[k] = node
        -- 如果v是table,则递归遍历
        if type(v) == 'table' then
            -- 有子节点,默认不展开
            node.isopen = false
            this.MakeTree(v, node)
        end
    end
    return 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 then
        local tabspace = ''
        for i = 1, node.tab do
            tabspace = tabspace .. '    '
        end
        if 'table' == type(node.value) then
            str = str .. string.format('%s▼ %s :\n', tabspace, node.name)
        else
            str = str .. string.format('%s● %s : %s\n', tabspace, node.name, tostring(node.value))
        end
    end

    if nil ~= node.child then
        for _, child_node in pairs(node.child) do
            -- 递归
            str = this.TreeToString(child_node, str)
        end
    end
    return 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 then
        return
    end
    local index = 1
    for _, 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 = uiObj

        if not LuaUtil.IsNilOrNull(node.uiObj) then
            -- 子节点塞在父节点下面
            local siblingIndex = node.uiObj:GetComponent("RectTransform"):GetSiblingIndex()
            child_node.uiObj:GetComponent("RectTransform"):SetSiblingIndex(siblingIndex + index)
            index = index + 1
        end
        if type(child_node.value) == 'table' then
            text.text = (child_node.isopen and '▼ ' or '► ') .. child_node.name
        else
            text.text = '● ' .. child_node.name .. ': ' .. child_node.value
        end
        -- 坐标缩进
        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 then
                child_node.isopen = true
                -- 递归, 展开子节点
                this.ExpanNode(child_node)
            else
                -- 关闭子节点
                this.CloseNode(child_node)
            end

            if type(child_node.value) == 'table' then
                text.text =  ( child_node.isopen and '▼ ' or '► ') .. child_node.name
            end
        end)
    end
end

我们需要传入树的根节点,我们给TreeLogic.lua添加一个GetTree方法,

-- TreeLogic.lua

function 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) then
        return
    end
    node.isopen = false

    for _, child in pairs(node.child) do
        child.isopen = false
        LuaUtil.SafeDestroyObj(child.uiObj)
        if nil ~= child.child then
            -- 递归关闭子节点
            this.CloseNode(child)
        end
    end
end

六、测试

好啦,现在我们测试一下效果吧,请添加图片描述
完美,收工~
代码我已经提交到框架上了,可下载工程进行查阅,https://gitcode.net/linxinfa/UnityXFramework
在这里插入图片描述

七、更新(2022/03/25)

文章发布后,这位同学自己顺利做出来了,
在这里插入图片描述

不过昨天他又发了消息问我如何保持上一次展开的节点或状态,
在这里插入图片描述
我改造了一下UI层的代码,把节点的UI对象缓存到table里,展开时先从缓存中取UI对象,关闭节点时隐藏UI对象,保持子节点的isopen数据状态,展开时进行递归,最后改版后的代码如下:

-- TreePanel.lua
-- ...
this.uiNodeTb = nil

function TreePanel:OnShow(parent)
	-- ...
    this.uiNodeTb = {}
end

-- ...

-- 展开节点
function TreePanel.ExpanNode(node)
    if nil == node.child then
        return
    end
    local index = 1
    for _, child_node in pairs(node.child) do
        local 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)
            end
        else
            -- 创建节点的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.obj

            if not LuaUtil.IsNilOrNull(node.uiObj) then
                -- 子节点塞在父节点下面
                local siblingIndex = node.uiObj:GetComponent("RectTransform"):GetSiblingIndex()
                child_node.uiObj:GetComponent("RectTransform"):SetSiblingIndex(siblingIndex + index)
                index = index + 1
            end

            uiUnit.btn.onClick:AddListener(function()

                if not child_node.isopen then
                    child_node.isopen = true
                    -- 递归, 展开子节点
                    this.ExpanNode(child_node)
                else
                    -- 关闭子节点
                    this.CloseNode(child_node, false)
                end

                if type(child_node.value) == 'table' then
                    uiUnit.text.text = (child_node.isopen and '▼ ' or '► ') .. child_node.name
                end
            end)
            this.uiNodeTb[child_node] = uiUnit
        end
        -- 更新展开文本
        if type(child_node.value) == 'table' then
            uiUnit.text.text = (child_node.isopen and '▼ ' or '► ') .. child_node.name
        else
            uiUnit.text.text = '● ' .. child_node.name .. ': ' .. child_node.value
        end
    end
end

-- 关闭子节点
function TreePanel.CloseNode(node, onlyHide)
    if LuaUtil.IsNilOrNull(node.child) then
        return
    end
    if not onlyHide then
        node.isopen = false
    end

    for _, child in pairs(node.child) do
        LuaUtil.SafeActiveObj(child.uiObj, false)
        if nil ~= child.child then
            -- 递归关闭子节点
            this.CloseNode(child, true)
        end
    end
end

运行效果如下:
请添加图片描述
展开全部节点和关闭全部节点的代码我没写,就当做一个小作业留给大家思考吧~

好了,我是林新发,https://blog.csdn.net/linxinfa
一个在小公司默默奋斗的Unity开发者,希望可以帮助更多想学Unity的人,共勉~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【游戏开发解答】Unity使用lua将table转为树结构,以多级折叠内容列表的UI形式展现(树结构 | UGUI | 折叠展开 | lua) 的相关文章

  • [区间DP]洛谷P1063 能量项链

    目录 题意样例样例输入 xff1a 样例输出 思路总结代码 题意 样例 样例输入 xff1a 4 2 3 5 10 样例输出 710 思路 1 经典区间DP题 算是合并石子的变种 只不过由一个点变成了一个区间 不过我们也可以用结构体存储 当
  • Linux命令行初接触-1 操作文件和目录

    操作文件 amp 目录 1 通配符含义常用通配符常用字符类类型匹配范例 2 mkdir 创建目录3 cp 复制文件和目录工作方式常用选项 4 mv 移动和重命名文件工作方式常用选项 5 rm 删除文件和目录工作方式常用选项注意事项 6 ln
  • 机器学习入入入入门(1)机器学习基本概念、引出深度学习

    机器学习入入入入门 xff08 1 xff09 0 前言1 基本步骤2 基本概念2 1 Hyperparameters2 2 local minima 3 linear model3 1 基础概念 4 piecewise linear cu
  • 深度学习蒟蒻入门——从0安装pytorch(CPU版)

    从0安装pytorch 1 检查自己的电脑有没有GPU2 安装CPU版的pytorch3 测试pytorch 1 检查自己的电脑有没有GPU 首先打开任务管理器 xff0c 选择性能栏 然后滑到最下 xff0c 看是否有GPU一项 xff0
  • 系统学习iOS动画 —— Stroke和路径动画

    这是要完成的动画 xff1a 先添加需要的代码 xff0c 这里需要将storyboard的ViewController换成TableViewController xff0c 将Under Top Bars 和 Under Bottom B
  • 不知道这些网站还做什么程序员呀!

    今天我就来总结一些程序员必备的网站 xff0c 囊括开源项目 解决bug 技术分享 一线资源和自我提升的网站 xff0c 希望能对广大程序猿有所帮助 xff0c 赶紧给我收藏起来 xff0c 下次刷不到了可别说我没提醒你 我们首先来看一下国
  • (音视频开发)WebRTC进阶流媒体服务器开发-多人互动架构

    一 xff1a 多人互动架构方案 xff08 一 xff09 WebRTC回顾 xff0c 两层含义 xff1a 1 WebRTC是google开源的流媒体客户端 xff0c 可以进行实时通讯 xff0c 主要应用于浏览器之间进行实时通讯
  • 10种linux下磁盘快照方式恢复系统

    导读大家都知道windows系统有一个磁盘快照的功能 xff0c 在windows2003中系统恢复开始依赖于一个叫做硬盘快照服务 Volume Snapshot Service 的服务 xff0c 他能够自动创建系统快照 包括正在使用的文
  • ubuntu安装go开发环境

    一 为ubuntu20 04更新源 给root用户设置密码 xff1a 命令 xff1a sudo passwd root 备份原来的源 xff0c 命令 xff1a sudo cp etc apt sources list etc apt
  • 如何修复Ubuntu中包缓存文件被毁问题

    导读今天 xff0c 我尝试更新我的 Ubuntu 18 04 LTS 的仓库列表 xff0c 但收到了一条错误消息 xff1a E The package cache file is corrupted it has the wrong
  • 1002 A+B for Polynomials (25分) 详解+易错点

    注意点 xff1a 系数为0 xff0c 则不输出 xff0c 例 xff1a 其中 1和1相加为0 xff0c 则在输出时避免这一项 xff0c 而且要注意结果的K值 xff0c 不要包括这一项 xff0c 思路 xff0c 利用结构体存
  • Linux远程桌面的选择

    Linux的远程桌面主要分两个部分 xff1a Linux客户机连Linux服务器和Windows客户机连Linux服务器 xff0c 还有现在用平板电脑连远程桌面 Linux客户机连Windows服务器比较简单没啥可说的 xff0c rd
  • Kali Linux mdk3WiFi洪水攻击 攻击路由器 生成虚假WiFi WiFi身份验证攻击可使连接WiFi的手机掉线重连抓包

    将无线网卡转换为监听模式 airmon ng start wlan0 查找附近无线网络 airodump ng wlan0mon Authentication DoS xff1a xff08 洪水攻击 xff0c 又叫做身份验证攻击 xff
  • 大一java程序设计的某次作业题解

    题目描述 xff1a 设计程序实现输入日期及机票张数 xff0c 计算出应付金额 假设北京至上海的机票全价为 1200 元 张 xff0c 以 2017 年为例进行程序编写 xff0c 所有的法定假日 xff0c 机票无折扣 xff1b 除
  • D. Make It Round(1759D)

    要求n k后缀0数量最多 xff08 k lt 61 m xff09 xff0c 且n k尽可能大 比赛时思路 xff08 错误 xff09 xff1a 10是由2和5组成 xff0c 故先统计n的因子包含2的个数num2 包含5的个数nu
  • Codeforces Round #841 (Div. 2)

    B Kill Demodogs 分析 显然要选择和两斜线的元素相加 所以答案可以表示为 xff1a 即 xff1a 根据公式 得答案为 但答案不能直接得这个 因为n的范围为1e9 xff0c 而ull的范围为1e20 xff0c 答案的第一
  • Educational Codeforces Round 141 Editorial C~D

    1783C Yet Another Tournament 分析 正解思路是贪心 开始自己也想的贪心 xff1a 首先显然打败的人数越多越好 xff0c 然后选择权值最小的人打败 这个思路前半部分没问题 xff0c 后半部分过不了样例的第二个
  • Codeforces Round #844 (Div. 1 + Div. 2, based on VK Cup 2022 - Elimination Round) D

    1781D Many Perfect Squares 分析 对于每组 xff0c 若和均为完全平方数 xff0c 则存在 xff1a 所以枚举所有 xff0c 对于每个 xff0c 枚举其所有 双因子对 xff0c 若两个因子之差为偶数 x
  • 匹配已有字符串

    生活小妙招 通过set和substr函数 xff0c 方便快捷地写出匹配已有字符串的代码 前置芝士 xff1a set使用详解 题目 xff1a G Perfect Word 代码实现 通过set 43 string的substr的使用快速
  • 在vue中使用rules的定义和校验规则

    表单内容里面定义属性 lt Form ref 61 34 rulesForm 34 model 61 34 rulesForm 34 label width 61 34 100 34 rules 61 34 rules 34 gt lt F

随机推荐