课程社群 跟着课程一起练,问题当场问 飞书申请加入课程
4 / 8

传奇3 实战 · lua 实战 传3 版本制作

用变量管元宝:存取、类型与打印调试

点这里打开这节课的课程页浮生梦 主讲 · 2025.07.24 · 4 小时 59 分

这节课里,元宝的每一次进出都会落到变量上:买一件东西,某个变量加一笔;卖一件,同一个变量减一笔;界面上显示的数量,其实就是把那个变量读出来。这篇只讲变量这一条线,讲清课上用的全局自定义变量怎么存怎么取、变量名为什么是拼出来的、取回来的值到底是字符串还是数字、读不到值和数据对不上时按什么顺序查。素材全部来自这节妙记的现场记录,其中几处来回改了三四遍才通,正好拿来看排查顺序。

  1. 元宝的进出都要落到变量上:买入加一笔、卖出减一笔,界面上显示的数量就是变量读出来的值。
  2. 全局自定义变量分两步用,先写入再读取,空手去读一个没声明过的名字,拿回来的是空值。
  3. 变量名多半是拼出来的,固定前缀接物品名,前缀或物品名差一个字,读到的就是另一条记录。
  4. 取回来的值不一定是数字,字符串直接参与计算会出错,先打印类型再决定要不要转换。
  5. 加减方向由协议里的动作号决定,1、2 是买入方向加一,3、4 是卖出方向减一,别写反。
  6. 数据对不上时先怀疑改动没更新,再怀疑变量名,课上反复出现的「哪错了又对了」都是这两类原因。

01全局变量:声明取值是两步

课上做拍卖行的时候,每个物品旁边的数量不是单独存一份,而是全部落在变量里。写入和读取走的是同一套全局自定义变量:写的时候把变量名和值一起传进去,读的时候只给变量名,两个接口共用同一串名字。

真正上手第一件事是声明。一个名字从来没写过就去读,拿回来的是空值,所以初始化要放在前面,名字和值先写一遍,后面才有东西可读。老师把这一段叫初始化设置:传一个名字、传一个值,把它们存进去,再用取值接口把值拿回来验证一次。

取值那一步课上只传了名字。老师当场确认过一句「这个 self 用不上」,读取不需要对象,给名字就够了。写入和读取各管一头,写的时候叫这个名字,读的时候也必须叫这个名字,中间差一个字就是两个变量。

数量既然已经落在变量里,界面上显示的时候就从同一个变量读出来,不要再另外存一份。课上反复强调的就是这个单一来源:同一个数存在两个地方,早晚会对不上,后面几次数据打架,追下去都是因为有一处绕过了变量,自己记了一份。

全局自定义变量:写入给名字和值,读取只给名字lua
-- 写入:名字 + 值一起传进去
local function setVar(name, value)
    设置全局变量(name, value)
end

-- 读取:只给名字,够用
local function getVar(name)
    return 读取全局变量(name)
end

-- 先初始化,再取值,顺序别反
setVar("拍卖行是否100", 100)
release_print("取到的值:", getVar("拍卖行是否100"))
说明

声明和取值是两步。空手去读一个没声明过的名字,返回的是空值,别把它当成 0 用。课上处理数量的写法都是「读出来,是空的就按 0 算,再加一」,读和写之间不留悬念。

02变量名是拼出来的:前缀加物品名

拍卖行不是一个物品,是一整排物品,每一个都要有自己的数量。课上的做法是把变量名当键用:前面一段固定前缀,后面接物品名,拼成一整串当作变量名。前缀对得上,同一个物品每次读写的才是同一条记录。

拼接用两个点。老师把拼好的名字先存进一个局部变量,后面加一减一都拿这个名字去操作,不重复拼第二遍。一个名字只拼一次,读写都复用它,这样前缀要改也只需要改一处。

物品名本身也是要先定下来的。课上先用道具名去查物品的 ID,名字这一步先确认,变量名再拿这个名字去拼。物品名字写错的影响不止在显示上,它会连带读到另一条从来没写过的记录,因为名字本身就是键。

拼出变量名,读出来加一再写回lua
-- 一个物品一条记录,变量名 = 固定前缀 + 物品名
local varName = "拍卖行" .. itemName

-- 读出来、加一、写回去,名字只拼这一次
local num = getVar(varName) or 0
setVar(varName, num + 1)
release_print("当前数量:", getVar(varName))
踩坑记录

拼接出来的名字看不见,出错的时候也不报错,只是数据对不上。课上遇到这类问题的第一反应不是改逻辑,而是把拼好的变量名打印出来,跟预期的那一串逐字比一遍。

03取不到值:先确认改动更新进去了

课上第一次测 set 和 get 就没读到值。代码来回看了几遍都挑不出问题,老师反过来问了一句「我没更新 QF?」——改完脚本没更新,跑起来的还是旧的那一版,取值自然拿不到刚写进去的东西。更新之后再点一次,值就出来了。

所以取不到值的时候,别一上来就怀疑接口。先确认这一版改动真的生效了,再确认读写的名字是不是同一串,再去看值本身是不是空。更新这一步没做,后面的排查全是白费,这个毛病在课上出现过不止一次。

把排查顺序固定成一张表,遇到问题照着走,比凭感觉翻代码快。课上的这几类现象来回出现的频率很高,原因基本都落在这几行里。

变量取不到值时的排查顺序
现象常见原因课上怎么确认
读出来是空值改动没更新、读写名字不一致先更新再点一次,把键名打印出来
加一之后数量没变读到的是字符串,没做转换打印类型,看到字符串就先转数字
两个物品的数量串在一起前缀或物品名对不上打印拼好的变量名,逐字比对
名字尾巴多一条下划线变量名里混进了逗号打印变量名原文
说明

顺序很重要:先看生效没生效,再看名字对不对,才轮到看值。课上把这两条搞反过一次,先去改逻辑,结果改的都是好的那部分。

04类型不对:字符串数字别再混

值取到了,新的问题是它不一定是数字。课上有一次调用返回的值没法直接参与计算,老师判断是类型不对,要先做一次转换。他给这两个转换各取了一个字头来记:一个转字符串,一个转数字,用哪个取决于下一步要干什么。

转换之前最好先看类型。Lua 里能直接把类型名打印出来,课上就是这么干的:把值打出来,看到类型那一栏写的是字符串,才确认要先转成数字。数字转字符串和字符串转数字是两件不同的事,方向搞反了,值看着还在,但已经不是你要的那一个。

老师有句话讲得实在:他以为自己那边已经做过转换了。这个猜测当场就被推翻,打印出来一看,回来的还是字符串。类型这件事不靠推断,靠打印。数字和字符串放在一起比较,Lua 不会替你换,结果往往和预期相反,这类错最难从报错里看出来,因为它压根不报错。

先看类型,再决定要不要转lua
-- 取值之后先打印一次类型
local v = getVar("拍卖行金条")
release_print("值:", v, "类型:", type(v))

-- 确认是字符串,先转成数字,再参与计算
local num = tonumber(v) or 0
setVar("拍卖行金条", num + 1)
踩坑记录

字符串不会主动变成数字。课上出现过「值看着是个数,加完之后却不对」的情况,根子就在这里:读回来的是一串字符,参与运算的也是那串字符,屏幕上打印出来却和数字长得一模一样。

05买入加一、卖出减一:动作号定方向

变量在拍卖行里最直接的用途是记数量:买入加一,卖出减一。动作是靠协议里的编号带过来的,1 到 4 这四个值里面,一和二是买入方向,三和四是卖出方向。课上把这个判断写得很直白:一或者二就是加一,否则就是减一。

老师中间差点写成不管什么编号都加一,自己马上否了:「这么写不对,我要带它的数量,那应该是变量名字将这个值加一,将这个值减一」。也就是说,加减方向由动作号决定,变量名由物品决定,这两件事不能混着写。他还专门把这个判断从大段逻辑里拎出来单独放在前面,免得和后面的分支缠在一起。

数量算完必须立刻写回去。读、算、写是三步,只读不写,界面上看到的还是旧值。课上把这三步压在相邻两行里,就是为了不给自己留出忘记写回的机会。

按动作号决定加一还是减一lua
-- 1、2 是买入方向,3、4 是卖出方向,方向别写反
local delta = 1
if arg1 == 3 or arg1 == 4 then
    delta = -1
end

-- 变量名由物品决定,加减由动作号决定
local varName = "拍卖行" .. itemName
setVar(varName, (getVar(varName) or 0) + delta)
说明

判断动作号的时候别只看是不是买入,要把四个值都照顾到:等于 1、2 走加一,等于 3、4 走减一,其他值直接提示并返回,不给脏数据落库的机会。

06改一处测一处:让打印替你说实话

这节课里最耗时间的不是写功能,是查一个「一模一样却一个报错一个不报错」的问题。老师把怀疑过的地方挨个复现:取值、加一、读、写,看起来都对,可换一行就出错。屏幕上留下的是几句原话,「这太不科学了」「我哪错了」。他没有停在怀疑上,而是继续缩小范围。

缩小范围靠的是单独跑。把可疑的那一段拎出来,写死几个值,单独触发一次,只看这一段的结果;一次只动一处,动完立刻点一次。课上几处「哪错了又对了」都是这么定位的:不是逻辑问题,是改动没生效,或者取错了那一条变量。

打印要打在关键处。课上在取值和写回之前各放了一次打印,看的是同一个名字下的值前后有没有变,两个打印之间的差别就是答案。打印的目的不是确认代码跑没跑,是确认值有没有按你预期的方向变。做完一次有效定位之后,这几行打印可以先留着,后面调价格、调数量还会用到。

把可疑的一段单独跑一次lua
-- 写死值,单独触发,只看这一小段
local testName = "拍卖行金条"
setVar(testName, 1)
release_print("写进去的是:", getVar(testName))

-- 改动没生效时,先确认这一行有没有被执行到
release_print("走到这里了")
踩坑记录

别一次改三处再一起测。课上有一次同时动了取值和写回,报错消失了但数据还是不对,只能再拆回去重来。一次只动一处,动完立刻点一次,才能知道到底是哪一处起了作用。

07变量名里混进逗号,会多一条下划线

课上还抓到一个很隐蔽的问题:打印出来的变量名尾巴上莫名其妙多了一条下划线,本来叫「当前累计买入」,打印出来变成了带下划线的另一串。老师先怀疑是自己看错,回头翻代码才定下来,是这个变量名里带了逗号。

逗号在这里是参数分隔符。它混进变量名之后,名字在实际解析时被切了一刀,打印出来就多出一段。这种错误不会以「逗号」两个字的报错形式出现,只是数据一直对不上,所以只能靠把名字原文打出来比对。

变量名就是键,键错了读到的就是另一条记录,甚至是一条从来没写过的记录。课上的做法是把名字当成一个值对待:先用局部变量装好,用之前打印一次,确认它就是你以为的那一串,再去读写。顺带提一句同源的问题,名字前后多一个空格也不行,课上判等的时候就是因为两头有空格直接判成了不成立,看着一样的两个字符串,不一定相等。

用之前把名字原文打出来lua
-- 打印名字原文,逗号和空格都会在这里现形
local varName = "拍卖行" .. itemName
release_print("变量名原文:[" .. varName .. "]")

-- 名字定死之后再用,读和写都拿这一串
setVar(varName, (getVar(varName) or 0) + 1)
踩坑记录

变量名里别出现逗号、空格这一类字符。课上出现过名字里混逗号导致打印多一条下划线,也出现过名字两头带空格导致判等失败,两个问题长得都不像报错,只能靠打印名字原文来发现。

常见问题

全局变量为什么读出来是空值?

先看这一版改动有没有 更新 进去,再对一下读和写用的是不是同一串名字。课上第一回测就是改完没有更新,更新之后点一次值就出来了;名字差一个字,读到的就是另一条不存在的记录。

变量名可以自己拼接出来吗?

可以。课上就是拿一个固定前缀加物品名拼出一整串当变量名,每个物品一条记录。拼接用 .. 这个运算符,拼好的名字存进局部变量,读写都复用它,前缀要改也只需要改一处。

读出来是字符串,加一之后为什么不对?

取值之后先打印一次类型。字符串不会自己变成数字,拿它和数字放一起算,屏幕上的结果看着像数字,其实不是。先 tonumber 转一次,读、算、写三步都用转换后的值。

加一减一的方向是谁决定的?

由协议带过来的动作号决定,跟物品名分开算。课上把判断写在前面:等于 12 走加一,等于 3、4 走减一,其他值提示之后直接返回,不给脏数据落库的机会。

变量名里为什么多出一条下划线?

名字里混进了 逗号。逗号在参数列表里是分隔符,进到变量名里会把这一串切开,打印出来就多一段。用之前把名字原文打出来比一遍,逗号、空格都会现形。

改动没生效,为什么点一次又对了?

多半是这一版还没 更新 进去,跑起来的仍是旧脚本。课上「哪错了又对了」出现过好几次,查下来都不是逻辑,而是改动没生效或者读写用了两条不同的变量名。

关于本文

本文整理自 2025.07.24 的课程妙记「lua 实战 传3 版本制作」。这节课里浮生梦老师在做拍卖行的数量时,把全局自定义变量从头走了一遍:先写入再读取,变量名靠固定前缀加物品名拼出来,取回来的值还要先看类型。他讲课最实在的一点是不绕开坑,取不到值、类型不对、名字里混进逗号,全是当场打印给你看的,连「我没更新 QF?」这种自己犯的错也照直讲。正文里的写法取自课上的现场演示,接口名以 996 引擎说明书为准,逐字稿里对不上的细节以实际运行结果为准。

课程原页:飞书妙记 · lua 实战 传3 版本制作