游戏蛮牛学习群(纯技术交流,不闲聊):159852603
游戏蛮牛 手机端
开启辅助访问
 找回密码
 注册帐号

扫一扫,访问微社区

开发者专栏

关注:2335

当前位置:游戏蛮牛 技术专区 开发者专栏

__________________________________________________________________________________
开发者干货区版块规则:

  1、文章必须是图文形式。(至少2幅图)
      2、文章字数必须保持在1500字节以上。(编辑器右下角有字数检查)
      3、本版块只支持在游戏蛮牛原创首发,不支持转载。
      4、本版块回复不得无意义,如:顶、呵呵、不错......【真的会扣分的哦】
      5、......
__________________________________________________________________________________
查看: 2085|回复: 26

[士郎] Lua写UI的一些使用心得

[复制链接]  [移动端链接]
排名
3
昨日变化

6887

主题

7411

帖子

2万

积分

Rank: 16

UID
1231
好友
185
蛮牛币
9920
威望
30
注册时间
2013-7-29
在线时间
3539 小时
最后登录
2018-10-22

社区QQ达人活力之星原创精华达人突出贡献奖财富之证游戏蛮牛QQ群会员蛮牛妹VIP

发表于 2018-8-7 16:21:06 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
之前提到,根据我们的项目需求,战斗部分,用c#来写。其他我们都用lua来写,保证在ios平台可热更新的部分更多。


lua在游戏中,很多时候,主要做的是UI部分。所以,我们今天只说说在UI上的一点小心得。我们使用的是UGUI,以下,均以UGUI来做说明。
在UI部分,除了具体的逻辑,开始很大一部分工作,是如何在代码中,获得我们需要的对应的各个Gameobject或者component。


我们先简单回顾以下,如果用C#来写UI逻辑,大体上有几种办法获得component:

  • 在代码中,通过路径读取。例如:

1.jpg

GetComponentByPath和GetComponentByName是我们简单扩展的方法
这种方法,最原始,麻烦之处就不用细说,都能理解。
2. 这种方法是将脚本挂在prefab上,直接拖对应的component到field上


2.jpg



这种方式简单明了,但是我们如果用lua做,不可能给每个界面,额外加一个c#的类。
3. 有些项目会选择给prefab上的资源以一定的命名规则,然后写逻辑自动生成。


3.jpg

根据命名前缀或者后缀来标记
这种办法好处是,写一个通用的解析代码,后面就省事了。坏处是,在prefab元素的命名上,限制有些多,如果需要更多一些的逻辑,string的描述能力是不足的。


4. 还有一种,类似3,给元素打上标记,然后离线自动生成读取的代码片段,类似很多的excel配表转数据class的处理方式。 这种方式和上面一种的问题类似。





以上是c#来写UI,常见的几种处理方式。肯定还有很多不同的方式,也许更优,我就不了解了,希望大家分享。
回到lua,我们用lua来做UI component引用的自动获得,以上4种办法,都有变种可以处理。最后,我们选择第二种方式来处理,在prefab上挂一个通用的c#脚本(此脚本仅做配置表述,变动很小,极少数不做热更新的之一。关于热更新c#脚本,参见之前文章。),然后将c#脚本配置的数据,运行时自动生成lua table的key,如果我们给一个table class化,其实也就像c#的属性了。


具体步骤如下:

  • 制作prefab,在prefab上挂一个FieldCfger的脚本
  • [AppleScript] 纯文本查看 复制代码
    using System;
    using UnityEngine;
    using System.Collections.Generic;
    using AdvancedInspector;
    
    public class FieldCfger : MonoBehaviour
    {
        public enum FildType
        {
            GameObject = 0,
            RectTransform = 1,
            Button = 2,
            Text = 3,
            Image = 9,
            Slider = 4,
            Toggle = 5,
            DropDown =6,
            Input = 7,
            ScrollRect = 8,
            
            ScrollBar = 10,
            RawImage = 11,
            ToggleGroup = 12,
            CanvasGroup = 13,
            SpriteRenderer = 14,
            LayoutGroup = 15
        }
    
        public enum LuaFildType
        {
            None,
            Normal,
            LuaMonoBase,
            CustomTable,
            CustomTableWithMono,
        }
    
        [Serializable]
        public class UILuaField
        {
            public FildType type;
    
            [Inspect("showLuaType")]
            public LuaFildType luaType;
    
            [Inspect("showLuaTableName")]
            public string tableName;
    
            private bool showLuaType()
            {
                return type == FildType.GameObject;
            }
    
            private bool showLuaTableName()
            {
                return luaType == LuaFildType.CustomTable || luaType == LuaFildType.CustomTableWithMono;
            }
    
            public GameObject obj;
    
            public string customName;
        }
    
        public List<UILuaField> fields = new List<UILuaField>();
    
        
    }


4.jpg

在prefab上挂脚本,存数据
[AppleScript] 纯文本查看 复制代码
public static void InitCfgField(GameObject obj, LuaTable table)
    {
        FieldCfger fieldCfger = obj.GetComponent<FieldCfger>();
        table["_go"] = obj;
        RectTransform t = obj.GetComponent<RectTransform>();
        if (t == null)
        {
            table["_tf"] = obj.transform;
        }
        else
        {
            table["_tf"] = t;
        }
        if (fieldCfger != null)
        {
            fieldCfger.enabled = false;
            for (int i = 0; i < fieldCfger.fields.Count; i++)
            {
                FieldCfger.UILuaField field = fieldCfger.fields;
                if (field.obj  == null)
                {
                    Loger.LogError(string.Format("{0} 下 序列 {1} gameobject没有绑定", obj.name, i));
                    continue;
                }
                string fieldName = field.customName;
                if (string.IsNullOrEmpty(fieldName))
                {
                    fieldName = field.obj.name;
                }
                table[string.Format("{0}_go", fieldName)] = field.obj;
                table[string.Format("{0}_tf",fieldName)] = field.obj.transform;
                switch (field.type)
                {
                    case FieldCfger.FildType.GameObject:
                        if (field.luaType == FieldCfger.LuaFildType.None)
                        {
                            table[fieldName] = field.obj;
                        }
                        else if (field.luaType == FieldCfger.LuaFildType.Normal)
                        {
                            table[fieldName] = BindLua(field.obj, LuaMgr.me.CreateLuaTable());
                        }
                        else if (field.luaType == FieldCfger.LuaFildType.LuaMonoBase)
                        {
                            table[fieldName] = BindLuaMono(field.obj, LuaMgr.me.CreateLuaTable("LuaMonoBase"));
                        }
                        else if (field.luaType == FieldCfger.LuaFildType.CustomTable)
                        {
                            table[fieldName] = BindLua(field.obj, LuaMgr.me.CreateLuaTable(field.tableName));
                        }
                        else if (field.luaType == FieldCfger.LuaFildType.CustomTableWithMono)
                        {
                            table[fieldName] = BindLuaMono(field.obj, LuaMgr.me.CreateLuaTable(field.tableName));
                        }
                        break;
                    case FieldCfger.FildType.Button:
                        table[fieldName] = field.obj.GetComponent<Button>();
                        break;
                    case FieldCfger.FildType.Image:
                        table[fieldName] = field.obj.GetComponent<Image>();
                        break;
                    case FieldCfger.FildType.RectTransform:
                        table[fieldName] = field.obj.GetComponent<RectTransform>();
                        break;
                    case FieldCfger.FildType.ScrollRect:
                        table[fieldName] = field.obj.GetComponent<ScrollRect>();
                        break;
                    case FieldCfger.FildType.Input:
                        table[fieldName] = field.obj.GetComponent<InputField>();
                        break;
                    case FieldCfger.FildType.Slider:
                        table[fieldName] = field.obj.GetComponent<Slider>();
                        break;
                    case FieldCfger.FildType.Text:
                        table[fieldName] = field.obj.GetComponent<Text>();
                        break;
                    case FieldCfger.FildType.Toggle:
                        table[fieldName] = field.obj.GetComponent<Toggle>();
                        break;
                    case FieldCfger.FildType.DropDown:
                        table[fieldName] = field.obj.GetComponent<Dropdown>();
                        break;
                    case FieldCfger.FildType.ScrollBar:
                        table[fieldName] = field.obj.GetComponent<Scrollbar>();
                        break;
                    case FieldCfger.FildType.RawImage:
                        table[fieldName] = field.obj.GetComponent<RawImage>();
                        break;
                    case FieldCfger.FildType.ToggleGroup:
                        table[fieldName] = field.obj.GetComponent<ToggleGroup>();
                        break;
                    case FieldCfger.FildType.CanvasGroup:
                        table[fieldName] = field.obj.GetComponent<CanvasGroup>();
                        break;
                    case FieldCfger.FildType.SpriteRenderer:
                        table[fieldName] = field.obj.GetComponent<SpriteRenderer>();
                        break;
                }
            }
        }

Type:选择obj的类型,不同的类型,会有不同的处理
LuaType:我们会做一些自动的lua绑定,后续介绍。

Custom Name:如果不想用prefab上元素自身的名字,可以另外取名字,少数情况会用到。
2. 接下来,当我们打开一个UI界面的时候,会根据这个配置,初始化好table的数据,部分代码如下:
以上是示例代码,这样,我们在lua中,对一个属性的访问,就非常简单了:


5.jpg



直接通过self.xxxx 来直接获取对应的component,并且可以self._go来获取gameobject,self._tf来获得transform,self.xxxx_go获得对应component的gameobject。


这里,完成了大部分元素的获取,做UI的时候,在fieldCfger脚本上拖拽上需要的元素,选择好type,在lua部分,就可以直接获取对应type的component了,省去了大部分烦杂的getComponent的脚本,工作效率和c#原生开发一样,不自动生成脚本,不需要对取名做特殊处理,还可以根据项目需求,修改这个脚本,增加更多对元素的描述和配置。


上面两步,已经可以解决大部分问题,但是,还有一些方面,我们做了一些加强,答案就在:


6.jpg



当我们给元素选择类型是Gameobject的时候,会额有一个luaFieldType的选项,这里会额外做一些处理,主要是为了解决下面这个问题。
我们做UI的时候,经常需要分层处理逻辑或显示。例如,一个战斗UI界面,可能有攻击方头像区域,技能按钮区域,被攻击方或Boss的血条,连击数显示等等多个模块。我们肯定不希望所有逻辑写在一个类里面(lua实现的类),而是希望有个层级结构。所以,我们希望能得到table[key] = table,属性也是一个table。例如:

7.jpg

一个UI的层级例子

那么,battleDlg是一个table,selfHeadCtrl,comboCtrl,inputCtrl也是一个table,每个子table,就有自己的属性。例如,我要访问操作按钮a,就用self.inputCtrl.btn_a就可以了,这是简单的将属性分层。也就是LuaFieldType中的Normal类型。对应prefab就如这样配置:

8.jpg

这里指明按一层处理,即生成一个table



9.jpg

下一层挂自己的元素



当然,还可以生成我们定义的,包含逻辑的一个table(lua class),这种一般用在我们需要对这个子模块,写模块内的逻辑。
10.jpg

这里填一个自定义的table



11.jpg

挂sell ctrl自己属性元素

12.jpg

对应的lua table中,就可以直接获得属性
通过这种方式,很方便地就自动处理好对lua中各个component属性的赋值。
另外几种LuaFieldType,都是对这种使用方式的扩展。

还有一种需求,比如我们有个角色列表的UI,一次展示三个角色,我不希望在prefab上一次创建好三个角色信息子UI,而是在prefab里面,做一个模板,运行时去Instantiate三个子UI,这是常见的处理方式。
为了方便处理,我们可以在这个模板上挂FieldCfger,然后在运行时,Instantiate出这个模板,并自动绑定好对应的lua table, 如下:
13.jpg

拥有逻辑的table
14.jpg

空table
15.jpg

对应打的工具方法


对lua这块的介绍,基本就到这里了,这个方法,能减少大量的低技术含量的工作量,并且在运行时使用也很方便。按照这个思路,也方便进行扩展,在非UI的逻辑下,也可以运用。目前用下来,非常方便,大大提高了开发的速度,让我们写lua,轻松了不少。



最后,lua的使用,必定没有c#写起来舒爽,但是当lua必不可少的情况下(个人觉得ILRuntime还不够成熟,我们稳妥为主,还是用lua),能让我们用起来更轻松点,也是值得项目努力的。


此方案还有不少可以优化之处,就不一一列举。这次就分享这个小的点吧,希望让一些还没用lua的项目,减少一些对lua的恐惧,其实,并没有那么难。
最后,说明一下:


我们的lua class,用的云风写的一个class,网上可以搜到。
在vscode 上的插件luaide,可以做到不错的代码提示,也可以断点debug,方便了我们的开发,值得推荐。

知乎@Gordon


评分

参与人数 2鲜花 +3 收起 理由
zpp123 + 1 很给力!
咸菜馒头粥 + 2 很给力!

查看全部评分

[发帖际遇]: 清风 在论坛发帖时没有注意,被小偷偷去了 1 蛮牛币. 幸运榜 / 衰神榜

跟我念“站长妹纸萌萌哒!”我说站长,你说YO!爱你们么么哒~
回复

使用道具 举报

4四处流浪
395/500
排名
18519
昨日变化

0

主题

199

帖子

395

积分

Rank: 4

UID
220417
好友
0
蛮牛币
153
威望
0
注册时间
2017-5-3
在线时间
168 小时
最后登录
2018-10-22
发表于 2018-8-7 17:42:31 | 显示全部楼层
学习了大佬

回复

使用道具 举报

7日久生情
3659/5000
排名
179
昨日变化

0

主题

363

帖子

3659

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
2484
好友
2
蛮牛币
3527
威望
0
注册时间
2013-8-23
在线时间
1180 小时
最后登录
2018-10-22
发表于 2018-8-7 19:58:49 | 显示全部楼层
非常不错!希望大佬能多做一些这样非常好的心得体会的有助于新手的帖子!

回复 支持 反对

使用道具 举报

4四处流浪
429/500
排名
8044
昨日变化

0

主题

223

帖子

429

积分

Rank: 4

UID
281167
好友
1
蛮牛币
858
威望
0
注册时间
2018-5-14
在线时间
64 小时
最后登录
2018-9-27
发表于 2018-8-7 20:44:30 | 显示全部楼层
不错不错,感谢分享
[发帖际遇]: 3084 乐于助人,奖励 1 蛮牛币. 幸运榜 / 衰神榜

回复 支持 反对

使用道具 举报

7日久生情
3536/5000
排名
90
昨日变化

1

主题

295

帖子

3536

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
28522
好友
8
蛮牛币
8728
威望
0
注册时间
2014-6-6
在线时间
760 小时
最后登录
2018-10-22

活力之星

QQ
发表于 2018-8-7 21:21:18 | 显示全部楼层
啊,还需要单独挂C#哇,我以为直接用lua控制就好了,  emmylua 插件非常好用,idea community就可以用。

回复 支持 反对

使用道具 举报

7日久生情
1873/5000
排名
3045
昨日变化

0

主题

1116

帖子

1873

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
189581
好友
0
蛮牛币
2621
威望
0
注册时间
2016-12-5
在线时间
301 小时
最后登录
2018-10-22
发表于 2018-8-8 08:57:30 | 显示全部楼层
很赞的东西啊啊

回复

使用道具 举报

7日久生情
1634/5000
排名
3066
昨日变化

3

主题

867

帖子

1634

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
246489
好友
1
蛮牛币
2713
威望
0
注册时间
2017-9-28
在线时间
312 小时
最后登录
2018-10-22

活力之星

发表于 2018-8-8 09:18:04 | 显示全部楼层
牛牛牛牛牛

回复

使用道具 举报

4四处流浪
425/500
排名
5944
昨日变化

0

主题

125

帖子

425

积分

Rank: 4

UID
245227
好友
0
蛮牛币
486
威望
0
注册时间
2017-9-21
在线时间
82 小时
最后登录
2018-10-22
发表于 2018-8-8 09:27:13 | 显示全部楼层
感觉很绕啊, 这样项目来新人的学习成本很高的样子
我的话就在你挂载的C#脚本上做文章, C#脚本作为加载lua脚本和创建luatable实例的功能, 然后给luatable设置一个this变量指向C#脚本实例或者GameObject不就行了

这样你在lua那边写的代码基本跟C#的写法一样了{:106:}

回复 支持 反对

使用道具 举报

6蛮牛粉丝
1449/1500
排名
2701
昨日变化

0

主题

563

帖子

1449

积分

Rank: 6Rank: 6Rank: 6

UID
182268
好友
2
蛮牛币
765
威望
0
注册时间
2016-11-9
在线时间
382 小时
最后登录
2018-10-22
发表于 2018-8-8 09:40:30 | 显示全部楼层
还有tolua比较好用吧

回复 支持 反对

使用道具 举报

排名
15519
昨日变化

0

主题

13

帖子

68

积分

Rank: 2Rank: 2

UID
292037
好友
0
蛮牛币
154
威望
0
注册时间
2018-8-1
在线时间
13 小时
最后登录
2018-8-30
发表于 2018-8-8 09:45:18 | 显示全部楼层
之前直接用的被主程魔改过的uLua,使用自带的toLua,里面有一大堆配好了的工具类,拿来就用了,还真没深入思考过,
感谢楼主分享,又长见识了!

回复 支持 反对

使用道具 举报

7日久生情
2137/5000
排名
2815
昨日变化

1

主题

1375

帖子

2137

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
119154
好友
0
蛮牛币
2282
威望
0
注册时间
2015-8-21
在线时间
271 小时
最后登录
2018-10-22
发表于 2018-8-8 10:10:30 | 显示全部楼层
谢谢楼主大大。
[发帖际遇]: boy840102 被钱袋砸中进医院,看病花了 1 蛮牛币. 幸运榜 / 衰神榜

回复

使用道具 举报

7日久生情
3809/5000
排名
106
昨日变化

0

主题

243

帖子

3809

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
25947
好友
12
蛮牛币
6786
威望
0
注册时间
2014-5-20
在线时间
1176 小时
最后登录
2018-10-22
QQ
发表于 2018-8-8 10:28:41 | 显示全部楼层
tiancaiwlk 发表于 2018-8-8 09:27
感觉很绕啊, 这样项目来新人的学习成本很高的样子
我的话就在你挂载的C#脚本上做文章, C#脚本作为加载lua脚 ...

你这是通用做法,但是lua中要写大量的getcomponent,文章说的应该是指在lua端不用写大量的getComponent,自动获取自己想要的类型。

回复 支持 反对

使用道具 举报

排名
9071
昨日变化

1

主题

186

帖子

445

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
30550
好友
0
蛮牛币
161
威望
0
注册时间
2014-6-20
在线时间
139 小时
最后登录
2018-9-28
发表于 2018-8-8 10:33:51 | 显示全部楼层
弱爆了

回复

使用道具 举报

5熟悉之中
502/1000
排名
4236
昨日变化

0

主题

80

帖子

502

积分

Rank: 5Rank: 5

UID
276682
好友
0
蛮牛币
1133
威望
0
注册时间
2018-4-11
在线时间
98 小时
最后登录
2018-10-22
发表于 2018-8-8 12:17:23 | 显示全部楼层
谢谢分享,很有帮助

回复 支持 反对

使用道具 举报

排名
46139
昨日变化

0

主题

4

帖子

9

积分

Rank: 1

UID
287956
好友
0
蛮牛币
18
威望
0
注册时间
2018-6-30
在线时间
1 小时
最后登录
2018-8-8
发表于 2018-8-8 13:27:36 | 显示全部楼层
感谢楼主分享

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

快速回复 返回顶部 返回列表