传奇3 实战 · lua 实战 传3 版本制作
列表容器怎么用:加载节点、绑定价格与榜单
点这里打开这节课的课程页→列表容器是这套界面里出现得最多的控件:材料商人的物品格子、排行榜的名次条、强化界面的按钮排,底下都是它在撑着。课上的做法是先在编辑器里挂好控件,回脚本里把它创建出来,再挂节点、设间隔与滚动方向。这篇按课上卡住的顺序来:创建函数带哪些参数、方向默认值给几、建不出来如何一眼看出、位置和大小为何拆两个方法、节点怎么挂、四按钮的价格与配色怎么分,以及引擎里取不到榜单数据时,怎么自己开一条协议要回来。
- 列表容器要两头一起动:编辑器里先挂好控件,脚本这边再写函数把它实例化出来。
- 方向参数留个默认值最省事:1 是垂直、0 是水平,默认给 1,建纵向列表就不用每次传。
- 创建失败要能一眼看出来:先判断父节点在不在,建不出来就打印失败再返回,别让空对象往下走。
- 位置和大小是两个方法:一个管坐标、一个管宽高;课上对象取错时,报错停在设置那一行,真正错的却是取对象那一行。
- 挂节点用增加子节点,第二个参数给 -1,意思是不指定位置、挂到末尾;一个挂通再循环挂一排。
- 滚动方向课上给 2 直接崩、报错落在 106,改回默认的 1 就正常;榜单取不到时,他干脆自己开了一条协议。
01列表容器:前端挂控件,脚本里创建
课上的做法是两头一起动。编辑器这边先在面板上放一个列表容器控件,脚本这边写一个函数把它实例化出来。前端那个控件本身就带着一串参数,翻它的创建方法时能看到要求传父节点和下标,说明里还写着可以拿它做下拉菜单和翻页。老师说他手边熟的是另一种写法,就把创建这一步收进自己写的函数里。
函数名照着他的习惯起,另外留了一个参数专门用于测试。需要哪些参数是一项一项补出来的:self、一个唯一 ID、x 和 y、宽和高,再加上一个方向。他的理由是有些控件在常用方法里没写带宽高,与其到处翻,不如在这里自己建,把要用的都摆齐。
传参这件事课上还岔开一句。他看到这个控件的传参方式时说,这种写法还能往里放视频,顺手就能做一段开场画面。这是界面层的能力,跟脚本怎么创建是两回事,但能看出这一类容器本身就是个能装东西的框。先想清楚这个框要装什么,再决定参数留哪几个。
02建不出来要写清:创建列表失败
函数里第一件事不是建控件,是判断父节点在不在。课上的顺序是节点要挂在父节点上边,那先确认父节点存在,再往下走。这个判断很短,就一两行,但少了它,后面拿到一个空对象再调用方法,报错就不好定位了。
建不出来的时候,老师给的处理是打印一句创建列表失败,然后返回。他顺手把测试参数也用上:如果 ID 里带着测试字样,就把这段判断再走一遍。这样跑起来的时候,界面没出来、日志里有话,一眼能看出是创建这一步没过,而不是后面某一处设置出了问题。
返回值也要留给外面。课上写的是创建成功返回实例,外面拿到实例再继续设置位置、大小、间隔。失败返回空、成功返回实例,调用方只看这一个返回值决定往不往下走,比在函数里面直接改界面干净得多。
ID 是后面找这个容器的凭据。课上把创建出来的节点存进这个 ID 名下的变量里,后面取节点、取列表加载节点都靠它,所以 ID 要唯一,别几个容器共用一个名字。
03位置和大小是两个方法,别混着用
创建出来只是有了一个框,摆在哪儿、多大,还得另外设。课上把这两件事分成两个方法:一个管坐标,一个管宽高。他先设位置,看它动没动,确认这一步通了,再去写设置大小。
设置大小那一步是现场翻出来的。老师翻到常用方法那一页,看到有个设置控件大小的接口,试了一次没成,报的是「这个对象是一个空的值」。查下来是取对象那一行写错了,把该写的父节点写成了别的东西。改过来以后再点击就通了,宽高这才真正设上去。对象取不到,后面所有设置都不会生效,报错还未必指向真正出错的那一行。
顺序上他把设置位置和设置控件大小挨着写,中间不插别的分支,理由是这两步是一组的,分开写很容易只改一处。位置和大小定下来之后,界面里那一整块才排得整齐。
-- 创建之后接着设位置和大小
local list = listVideo(self, "LV", 300, 400, 400, 200)
list:设置位置(300, 400) -- 先摆坐标
list:设置控件大小(400, 200) -- 再定宽高
-- 报「对象是空值」时,先回头确认拿对象那一行
release_print(list)课上在这里卡了两回:一次是设置大小的对象取错,一次是名字对不上。两次报错都停在设置那一行,真正错的却是取对象那一行。遇到这种报错,别只盯着报错行,往上翻一行看对象从哪来。
04往容器里挂节点:增加子节点
容器建好、位置摆正,接下来是把东西塞进去。课上找的是挂节点那个方法,名字叫增加子节点,第一个参数是要挂进去的节点,第二个参数给了一个 -1。他说这个 -1 就是不指定位置,挂到末尾去,省得每次算下标。
挂进去的节点可以是按钮,也可以是文本,创建方式跟平时一样,建完再挂。课上先拿一个按钮试,挂进去点击,看到列表里出现一个,再复制几份多挂几个,确认一排能排开。先把一个挂通,再谈一排,一个都出不来的时候,多挂几个只会更难查。
挂完是能看见效果的:界面里多出一排元素,排的方向由创建时的方向参数决定。老师在这一步顺手把测试参数用上,界面一出来就带了几个节点,后面调间隔、调回弹才有东西可看。
-- 建一个节点,再挂到容器末尾
local itemNode = 创建节点("BT1")
list:增加子节点(itemNode, -1) -- -1:不指定位置,挂到末尾
-- 一排节点就循环建,建一个挂一个
for i = 1, 4 do
local node = 创建节点("BT" .. i)
list:增加子节点(node, -1)
end课上这些创建节点的函数名是他自己起的,引擎侧的接口名以说明书为准。挂节点这个方法名是引擎里就有的,参数含义也是按说明书给的,这一点跟自建的函数要分开看。
05间隔、回弹和滚动方向:默认值最省事
节点挂上去以后,老师看着界面说了一句「怎么好像没有弹性」。他回列表的设置里去找,把跟滚动有关的几个都写进去:设置间隔、设置回弹、设置滚动方向,还有列表间距和列表的大小。课上把这一段叫设置列表间隔。
间隔这类值不用一次调到最好看,能给个默认先跑起来就行。课上给的是十和二十两个数,回弹直接给真。这两个值设完,界面里的节点就分开了,手感也上来了。能用默认值先跑通,就别为了一个数字卡住整条流程,参数后面再回来慢慢调。
滚动方向这一段出了个真实的坑。老师试着把方向设成 2,想试水平滚动,结果一点击就崩,日志里给的是 106。他回头对照了一下,创建时方向是一、这里设成二是对不上的,把这一处屏蔽掉、改回默认的 1 之后,点击就正常了。他当场把结论记下来:这个容器的方向,默认给 1。
-- 间隔、回弹、方向:都放在设置里一起写
list:设置间隔(10, 20) -- 课上先给的默认两个数
list:设置回弹(true) -- 有回弹手感更顺
list:设置方向(1) -- 默认 1;给 2 试水平时崩过
-- 列表本身的大小也可以在设置里给
list:设置控件大小(400, 200)方向设成 2 那一次,报错落在 106,但真正的原因是这一处跟创建时的方向对不上。改回默认的 1 就好了。遇到崩溃别急着改业务逻辑,先看最近动的是不是这些默认值。
06两个价格、四个按钮:买红卖绿怎么分
界面摆下来是这么分的:上面两个价格,下面四个按钮。老师说买价和卖价其实是同一个数,真正的差别在货币上,元宝这一套跟绑定那一套不一样,所以价格要摆两份,一份绑定价格、一份元宝价格。按钮的配色按方向分:元宝购买和绑定元宝购买用红色,绑定卖和元宝卖用绿色,四个按钮一列排开,买红卖绿一眼能分清。
按钮的位置还被专门调过。老师把元宝购买往下放了一段距离,理由是「免得他点错了」,顺手又把按钮加宽加高,说这样点起来不费劲。这一条看着是美术活,其实是在防误触:两个按钮挨得太近,玩家点买点到卖上这种事一定会发生。
按钮上还挂了时间。老师说要放的是「点击时间是绑定时间,循环时间」,也就是把时间内容加到按钮上。这一段他试了一次,先把原来那处添加干掉,再换成带时间内容的写法,界面上这才看到那一行时间。
四个按钮带出来的动作号是 1、2、3、4:1 元宝买、2 绑元买、3 元宝卖、4 绑元卖。判断写在最前面,等于这四个值就往下走,其他一律提示后返回。前端按钮负责好看,后端那个判断才是真正管住这件事的地方,所以四个值以外的分支一定要有。
-- 动作号只有 1、2、3、4,其他一律挡掉
if act == 1 or act == 2 then
-- 买入方向:先扣钱,再给东西
elseif act == 3 or act == 4 then
-- 卖出方向:先看背包,再给钱
else
player:提示("动作错误")
return
end容器本身还被调过一次。老师把容器给小了,节点挤在中间不好看,他就去设置对齐方式,让内容顶上对齐,原话是「别让他排中间去」。对齐这类属性调一次就够了,不用跟着数据走。
07榜单取不到:自己开一条协议
排行榜这一段课上卡得最久,卡的不是写法,是数据从哪来。正常的思路是服务端把排行数据下发成变量,前端去取。老师把能找的地方翻了一遍:客户端相关、服务端相关都翻过,还去找了原变量、下发变量、全局变量、脚本额外变量,翻的过程中自己都在念「这变量原变量在哪来着,我怎么想不起来了」。这一版里没有现成的下发。
他的处理很干脆:「没有。怎么办?没有。那就写一个协议了。」后来说得更直白——没招了,因为他那个东西不全,我们就得自己去写协议。这句原话比那段代码更值钱:引擎没给的,不要绕着猜,自己补一条能跑的通道。
自己开的这条协议走两步。打开排行榜界面的时候,客户端先向服务端发一个请求,课上报的是 101107;服务端这边按 107 处理,「这个带一下排行榜,这是获取排行榜的数据」,把数据转成字符串发回去。客户端收到的返回值按整数接,再把这一串 JSON 转成表。
转成表之后就是迭代。课上的写法是拿一对键值循环,外层放一个从 1 开始的局部变量,每进一个就在列表里挂一个文本节点。协议只负责把数据要回来,怎么摆还是列表那一套,所以前面几节的增加子节点在这里原样用得上。这一段有两个坑值得记:一个是节点挂不进去,查下来是列表方向这一处写的值跟创建时的对不上,一处水平一处垂直,老师当场说「这两个值不一样」,改回 1 就好了;另一个是节点其实挂上了却看不见,原因是忘了设置大小,把坐标给到 300、400 并且把大小补上之后,那一排才排得出来。
-- 打开排行榜界面时发请求;服务端按 107 把数据转成 JSON 字符串回来
-- 客户端先把这串 JSON 转成表,再迭代往里挂节点
local data = toTable(msg.data)
local n = 1
for a, v in pairs(data) do
local node = 创建文本节点(n .. ". " .. v)
list:增加子节点(node, -1)
n = n + 1
end列表方向在两个地方各写了一遍,创建时一处、设置时一处,值不一样的时候表现是节点加不进去,而不是明确的报错。改这类问题先回头把两处的默认值对齐,再怀疑数据。
课上自建的这些函数名和协议号是这套版本里的命名,引擎侧的接口名以说明书为准。JSON 转换、协议收发这一类方法在各个引擎版本里位置和名字都不太一样,照说明书上的写法替换即可,思路是一样的。
常见问题
列表容器为什么要在编辑器里先挂一个控件?
课上走的是两头一起动的路子:编辑器里先放好一个 列表容器 控件,脚本这边再写函数把它实例化。前端那个控件本身带着父节点和下标两个参数,脚本里要的只是把它按自己的参数摆出来。
列表容器的方向参数默认给几?
默认给 1。课上的备注写得清楚:1 为垂直、0 为水平。给 2 那一次直接崩,报错落在 106,改回默认的 1 就正常,所以这个值课上定成默认 1。
创建失败怎么一眼就看出来?
先判断父节点在不在,建不出来就打印一句 创建列表失败 再返回,成功才返回实例。界面没出来、日志里有话,就能定位到创建这一步,而不是在后面某处设置上瞎找。
位置和大小为什么要拆成两个方法?
课上把 设置位置 和 设置控件大小 分开写,因为这两个是不同的事,混在一起改很容易只改一处。设置大小那一步还踩过坑,对象取错时报的是「这个对象是一个空的值」,报错落点并不在真正出错的那一行。
增加子节点第二个参数给 -1 是什么意思?
表示不指定位置,直接挂到末尾。课上用 -1 这个值省掉了每次算下标的麻烦,先挂一个确认通了,再循环挂一排。
引擎里取不到排行榜数据怎么办?
自己开一条协议。课上把客户端相关、服务端相关、原变量、下发变量都翻遍了没有现成的,原话是没招了,因为他那个东西不全,我们就得自己去写协议:打开界面发一个请求,服务端按 107 把数据转成 JSON 回给客户端,客户端转表后迭代填进列表。