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

本文最终效果
请添加图片描述

一、前言

嗨,大家好,我是新发。
有粉丝问了我这个问题,
在这里插入图片描述
我以为他是拿到一个纯文本数据,他不知道如何去解析,如果单纯是想解析数据,完全不需要使用树。
看格式的话是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呢,到这里我以为他是做网页,所以问了下是不是用javascript
在这里插入图片描述

经过确认,是用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脚本,
在这里插入图片描述

扫描二维码关注公众号,回复: 13713351 查看本文章

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
在这里插入图片描述

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

猜你喜欢

转载自blog.csdn.net/linxinfa/article/details/123549096