Unity开发日记--Lua开发游戏UI界面

Posted 东冻东咚

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity开发日记--Lua开发游戏UI界面相关的知识,希望对你有一定的参考价值。

小白日记(3)——Lua开发游戏UI界面

本文主旨

先道歉解释下哦~ 因为笔者去年换了一家新的游戏公司,熟悉项目和融入新的环境花费了很长时间,还是有些懒惰了,忘记记录了;从现在开始开始继续分享自己新的学习之路。
这篇文章和大家分享近一年来在新公司一些新的学习经验和自己的变化,和对待项目制作过程中一些自身改变和学习之道。

正文

就我本身而言接触Lua,从熟悉基本语法规则和运用,是用了20多天;虽说Lua上手简单,就算不熟悉代码的人,看完Lua基础也可以写几个函数;因为Lua相对于Java、c#等,没用繁琐的格式、写法要求;关键字也不多,自由度还是很高的~ 但是还是需要编写者自己注意书写格式,好的代码规范是一个程序员最起码的“道德标准”。

一、Unity开发为什么要学习Lua~

这是很多初学者的疑问,我是Unity开发人员,我会C#就能满足日常的开发需求啊;这个确实没有错的,确切的说,如果不考虑某些特定的需求,c#能保证你完成游戏中100%的功能的哈~
所以这里就要说到另一个热更新方案——ILRuntime,这个是只需要在unity中安装特定的插件就可以实现同等的热更效果,不需要学习Lua;详情可参靠这个官方链接。“ILRuntime”更适用于小中型工作室,一些大公司会有些祖传老代码都是Lua写的,用的是经典的Tolua,虽说也听大佬们说也是过时的了,但是很多游戏都是基于Tolua开发的,越老越稳定嘛~
不要纠结哪种方案更好,效率更高这类问题;因为解决方案有很多,“Xlua”、“Ulua”等等各有优缺点,并且都被广泛应用,所以这不是纠结学习那个的问题

二.什么是热更新呀~

现在市面上的上线游戏没有不会热更新的。所谓热更新就是游戏在运行过程中就进行了代码的更新,美术资源的替换,在你游戏进行中时,代码更新就完成了,比如每款国民级手游的不停机跟新就是一种性质热更新,热更新在不影响用户体验的情况下,最小程度上完成代码的迭代,资源的替换,是现在最主流的一种更新方式;最近准备学一下打包的流程,前辈说苹果端的包审核更严格、而且有的包会通过技术手段被屏蔽,所以热更新会绕过一些流程,更快的将新的功能更新上去。

三.了解Lua

Lua的语法基础

我一直都在用的自学网站
1.菜鸟教程 传送门
2.编程狮 传送门

Lua和C#的交互方式

实现热更新三点要素:反射、虚拟机、代码绑定
1.ToLua插件的使用:网上搜索tolua插件,下载解压后将to-lua-master全部拽到Unity中的Asset下
2. Tolua脚本可以使用lua函数来处理事件,实例化CLR对象,访问属性。CLR——公共语言运行库(Common Language Runtime),只知道是微软的一套开源运行库。
3. Lua虚拟机的启动,感谢大佬提供学习思路ToLua源码分析,这个朋友写的详细哈

//虚拟机初始化启动
void Start() 

    lua = new LuaState();             
    lua.Start();          
    LuaBinder.Bind(lua);  

4.利用c#的反射的特性进行两种代码间的交互,在Lua脚本中对C#脚本中特定方法的调用

public Class FuncForCsharp

	//打印作者的特征
	public void DebugFunc()
	
		Debug.Log("You are so Cool ~~~");
	

local m_objectt = tolua
local m_fc = m_objectt .findtype("FuncForCsharp")
if not m_fc then
	m_objectt .loadassembly("Assembly_CSharp")
	m_fc = m_objectt .findtype("FuncForCsharp")
end
local m_func = m_objectt .getmethod(tpy,"LogMyName")

5.脚本的绑定,目前的解决思路是全为Lua控制生命周期,Awake.Updata、Start等生命周期函数等都可以在Lua脚本正常调用,其中还有Lua特有闭包的概念和一些知识,我会在深深的研究一下再来解答。

总结

希望这边文章可以记录自己的学习历程,会给别人一点帮助~
之后会研究一些打包流程不定时分享~

游戏开发解答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脚本,

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的人,共勉~

以上是关于Unity开发日记--Lua开发游戏UI界面的主要内容,如果未能解决你的问题,请参考以下文章

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

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

Unity游戏开发 | 浅谈Lua和C#中的闭包

Unity开发之UI与Lua知识汇总!

[Unity XLua]热更新XLua入门-俄罗斯方块实例篇

游戏UI界面框架设计系列视频课程