TXT 与 Lua 脚本全面对比
在传奇 / 传世类引擎中,TXT 与 Lua 并不是「旧方案被新方案取代」的关系,而是「入口层」与「实现层」的分工:TXT 用引擎预置的命令表把能力直连成段落标签,零基础即可写出 NPC 对话与事件触发;Lua 则是嵌入引擎的通用编程语言,用 table、函数与模块承载复杂业务逻辑。实务中最稳妥的用法是让 TXT 负责触发与界面入口、Lua 负责算法与系统,二者通过 callscript 互调,并共享同一套全局变量。速度与效率上也各有胜场:TXT 入门速度极快、门槛极低,记住一张命令表就能动手改脚本,新手与策划都能直接用;Lua 则赢在复杂系统上的开发效率与执行效率,代价是先跨过编程门槛。
结论速览
把一套版本比作一栋楼,TXT 脚本相当于标准化的水电接口面板:规格统一、即插即用、谁都能看懂说明书;Lua 脚本相当于可以自行设计管线的施工能力:自由度极高,但对施工者的要求也高一档。二者并不是竞争关系,因为它们解决的问题不一样。
真正需要记住的判据只有一条:凡是「引擎已经提供现成命令」的事情,TXT 写起来更快;凡是「引擎没有现成命令、需要自己算、自己存、自己封装」的事情,只有 Lua 能写。引擎社区里被反复引用的一句话正是这个意思:TXT 做不到的,Lua 可以做出来;官方一旦开源,剩下的就是自己改、自己封装,而这些都离不开 Lua。
| 对比维度 | TXT 脚本 | Lua 脚本 | 对开发的实际影响 |
|---|---|---|---|
| 语言性质 | 引擎内置的配置型脚本,只能用引擎预置的指令名 | 嵌入引擎的标准 Lua 通用编程语言 | 决定整个系统的能力上限 |
| 运行方式 | 文本逐行解释,读到标签即执行 | 先编译为字节码,再由 Lua 虚拟机执行 | 决定执行效率与优化空间 |
| 语法风格 | 段落标签加条件段、执行段、对话段 | 结构化语句与函数 | 决定代码可读性与协作效率 |
| 流程控制 | 只有 GOTO 与 BREAK,没有 while 与 for | if、while、for、repeat、函数一应俱全 | 决定复杂逻辑能否写得出来 |
| 数据结构 | 只有整数变量与字符串变量 | table 可表达数组、字典、对象 | 决定能否表达复杂业务数据 |
| 复用方式 | 用 CALL 引用其他 txt 文件的段落 | 函数、模块、面向对象 | 决定改版速度与维护成本 |
| 变量规模 | 逻辑变量 1 至 1024、个人变量 499 个,属稀缺资源 | 局部变量无数量限制 | 决定大型系统的数据存放方式 |
| 触发入口 | QFunction-0.txt、QManage.txt 等 | QFunction-0.lua 等平行文件 | 两套入口平行存在,可混用 |
| 调试手段 | 基本只能靠 SENDMSG 打日志 | 日志更完整,报错含文件名与行号 | 决定排错效率 |
| 学习曲线 | 低,记住命令即可上手 | 中,需要编程思维与数据结构基础 | 决定团队的人手门槛 |
| 热更新 | 改文本即可生效,粒度到文件 | 改脚本即可生效,粒度到模块 | 两者都支持,Lua 粒度更细 |
| 安全风险 | link、goto、call、messagebox 等字段可被伪造调用 | 客户端同样可被修改 | 两者都必须在服务端做指纹判断 |
本质差异:指令表与编程语言
TXT 严格来说不是一门编程语言,而是引擎为策划准备的一套「对话式指令表」。引擎解析 txt 时按行读取:遇到方括号包的标签就登记一个入口,遇到条件段就开始收集判定条件,遇到执行段就按顺序调用引擎在底层注册好的指令。脚本里出现的每一条命令都必须由引擎预先注册,脚本无法自己创造新命令,这正是「TXT 做不到的 Lua 可以做出来」这句话的技术含义。
Lua 则是一门真正的语言。引擎把 Lua 虚拟机嵌进服务端,同时把游戏对象(人物、怪物、物品、地图)通过一套 API 暴露给脚本。脚本因此可以定义函数、引入自己的模块、构造任意结构的数据,甚至把一组常用逻辑封装成「自定义命令」给 TXT 调用。由于 Lua 本身语法简洁、执行效率高、嵌入友好,它长期以来就是游戏引擎最主流的脚本方案。
读 TXT 脚本时,不要把它当成代码,要把它当成配置文件:它的每一行都是一条「填好参数的指令」。而读 Lua 脚本时,它是代码:有变量作用域、有返回值、有控制流。这个心态差异决定了排查问题的入手方式完全不同。
图 1 两类脚本在引擎中的执行路径
QFunction-0.txt 等"] B --> D["Lua 入口
QFunction-0.lua 等"] C --> E["TXT 行解释器
逐行解析条件段与执行段"] E --> F["引擎预置命令表
CHECKITEM、GAMEGOLD、SENDMSG"] D --> G["Lua 虚拟机
编译为字节码后执行"] G --> H["引擎注册的 player 等 API"] F --> I["游戏世界状态变更"] H --> I
说明:TXT 走「解释一行、调用一条预置命令」的路径;Lua 走「编译字节码、调用暴露 API」的路径。
语法结构与可读性
TXT 的组织单位是「段落」。段落用方括号包住的名字声明,既可以被对话选项点击进入,也可以被 GOTO @段落名 直接跳转。每个段落内部再用三个关键字切分为条件区、执行区与对话区:条件区判定是否成立,执行区做实际动作,对话区把文字显示给玩家。行首的分号是注释符,该行既不显示也不参与解析。
下面用同一个需求做对照:玩家等级不低于 50 级、元宝不少于 1000 时,扣除 1000 元宝并奖励 25 万经验,否则提示条件不足。
TXT 写法
[@main]
#IF
CHECKLEVELEX > 49
CHECKGAMEGOLD > 999
#ACT
GAMEGOLD - 1000
CHANGEEXP + 250000
SENDMSG 6 兑换成功,获得 25 万经验!
#SAY
兑换成功,已扣除 1000 元宝。\
<返回/@main>\
#ELSESAY
条件不足:需要 50 级且 1000 元宝以上。\
<离开/@exit>\
Lua 写法
-- 996 引擎 Lua 版:等级与元宝双条件兑换
function main(actor) -- 入口参数就是玩家对象
local player = actor -- 别名成 player,之后统一调用
local lv = player:getLevel()
local gold = player:getGameGold()
if lv >= 50 and gold >= 1000 then
player:addGameGold(-1000)
player:addExp(250000)
player:send(ConstCfg.notice.own,
'{"Msg":"兑换成功,获得 25 万经验!","Type":9}')
return true
end
player:send(ConstCfg.notice.own,
'{"Msg":"条件不足:需 50 级且 1000 元宝以上","Type":9}')
return false
end
两段代码的信息量差距一眼可见。TXT 版把「条件」「动作」「文案」硬编码在三个区段里,条件之间是隐式的「与」关系(多行并列即为同时成立);Lua 版把条件写成表达式,把提示文案与判定逻辑放在同一个分支里,分支结构从缩进就能读出来。发送消息的写法也体现了这个差异:TXT 用一行 SENDMSG 6 文本 调用预置命令,Lua 则把玩家对象当成消息的主体,用 player:send(类型, JSON) 发送带 JSON 结构体的消息,可以携带颜色与消息类型。这里还有一条写法约定值得记住:入口函数拿到的第一个参数就是玩家对象,脚本里习惯先写 local player = actor 换个名字,之后取等级、扣元宝、发消息全部走 player: 的方法形式,代码读起来主体始终是「谁在做事」,而不是一串散落的对象标识。
| 语法点 | TXT 写法 | Lua 写法 |
|---|---|---|
| 单条件判断 | CHECKLEVELEX > 49 |
if lv > 49 then |
| 多条件「与」 | 条件区内连续多行并列,全部成立才执行 | if a and b then |
| 多条件「或」 | 无原生写法,需拆成多个段落用 GOTO 汇合 | if a or b then |
| 否则分支 | #ELSEACT / #ELSESAY |
else / elseif |
| 循环 | 只能用 GOTO 回跳到本段落 | while / for / repeat |
| 变量赋值 | MOV 变量 数值 |
x = 1 |
| 自增 | INC 变量 数值 |
x = x + n |
| 求和 | SUM 变量A 变量B |
x = a + b |
| 数值比较 | SMALL / LARGE / EQUAL |
< / > / == |
| 注释 | 行首分号 | -- |
| 调用子过程 | CALL [\路径\文件.txt] @标签 或 GOTO @标签 |
func() 或 require("模块") |
| 输出提示 | SENDMSG 6 文本 |
player:send(类型, JSON) |
| 字符串拼接 | 靠引擎内置的变量占位符拼接,能力有限 | a .. b |
| 集合数据 | 没有数组与字典,只能用编号变量硬凑 | local t = {1, 2, name = "x"} |
| 定义可复用逻辑 | 没有函数概念,靠 CALL 复用整个段落 | local function f() end |
| 作用域 | 全部全局,没有局部变量 | local 局部与全局并存 |
| 错误处理 | 没有,写错即中断或产生意外跳转 | pcall / assert |
社区里的总结很直白:if 套 if 比 goto 好用。这恰好点中了 TXT 的结构性瓶颈。当分支超过三层,TXT 只能靠 GOTO 在段落之间来回跳,一份脚本从「从上往下读」退化成「在文件里反复横跳」;而 Lua 的缩进天然表达了层级,嵌套再深也还能读。
流程控制能力
TXT 的 GOTO 是转向指令,把执行权交给指定段落。它是 TXT 唯一的流程控制手段:要做循环,就让 GOTO 跳回自己;要做分支,就让不同条件跳向不同段落。这带来一个直接后果,循环次数不可预测,条件一写错就形成死循环。
引擎为此保留了保护机制:在服务端的 !setup.txt 中查找 ScriptGotoCountLimit,把等号后面的数值调到 10000 至 50000 之间,可以缓解脚本死循环导致的卡服。但调高上限只是让它更难触发,并不能修复逻辑错误。通行的建议是:写脚本时尽量少用 goto @XXX 这类跳转命令。
TXT 用 GOTO 模拟循环
[@main]
#IF
CHECKITEM 金创药 10
#ACT
TAKE 金创药 10
GAMEGOLD + 100
GOTO @main ; 回收后跳回本段,形成循环,条件写错即死循环
Lua 用循环语句表达同一件事
-- 回收背包内所有满 10 个的指定物品,回收次数由数据决定
-- 前提:player 即当前玩家对象,也就是入口参数 actor 的别名
local count = 0
while player:getItemCount("金创药") >= 10 do
player:takeItem("金创药", 10)
player:addGameGold(100)
count = count + 1
if count >= 100 then break end -- 显式设置上限,天然安全
end
对比之下可以看出,Lua 的循环有明确的开始与结束边界,还能用 break 主动设上限;TXT 的循环完全依赖条件在下一轮跳转前失效,一旦条件永远为真,就只能等引擎的跳数上限兜底。
变量体系与数据承载
TXT 的变量是额度制资源,这是它与 Lua 最容易被低估的差距。逻辑变量 [1] 至 [1024] 属于单个人物的临时开关,用来记录对话分支走到哪一步这类会话内的状态;真正需要跨登录保留的数值,则要交给个人变量/个人标识 [001] 至 [499]——它随角色创建自动生成、初始值为 0,且不会因为人物下线或服务器重启而重置,适合记录会员积分、任务进度这类需要长期保留的数据。
G 变量是全服唯一的共享变量,它不属于任何人物而属于服务器,任何人的运算都会产生影响,数据存放在 Mir200\GlobalVal.ini,取值范围与 P 变量相同。此外还有一类只读的系统变量,例如 <$USERNAME>、<$GUILDNAME>、<$LEVEL>、<$HP>、<$DC> 等,用来在对话文本里直接显示人物信息。如果预置变量不够用,还可以用 SAVEVAR HUMAN 变量名 ..\QuestDiary\路径\文件.txt 保存自定义变量、用 LOADVAR HUMAN 读回。
| 变量类型 | 写法示例 | 作用域 | 是否持久化 | 典型用途 |
|---|---|---|---|---|
| TXT 逻辑变量 | [1] 至 [1024] |
单个人物 | 否 | 一次性开关、对话分支标记 |
| TXT 个人变量 | [001] 至 [499] |
单个人物 | 是 | 会员积分、任务进度 |
| TXT N / P / D 变量 | N0 至 N9、P0 至 P9、D0 至 D9 |
单个人物 | 否 | 临时计算,数量极少 |
| TXT 字符串变量 | S0 至 S9 |
单个人物 | 否 | 存放文本内容 |
| TXT 全局变量 | G0 至 G999 |
全服共享 | 是,存 GlobalVal.ini | 全服活动进度、排行榜基数 |
| TXT 自定义人物变量 | SAVEVAR HUMAN 名称 文件路径 |
单个人物 | 是,存自定义文件 | 版本自建属性 |
| TXT 系统变量 | <$USERNAME>、<$LEVEL> 等 |
当前人物 | 只读 | 对话文本中显示人物信息 |
| Lua 局部变量 | local x = 1 |
函数内 | 否 | 任意计算,无数量上限 |
| Lua 表 | local t = {a = 1, "x"} |
任意 | 否 | 数组、字典、对象式结构 |
| Lua 共享变量 | s.share.LoadGlobalVar(类型, 名, 文件, 模式) |
跨脚本 | 取决于参数 | 模块间共享配置与数据 |
一个稍复杂的玩法,往往要同时跟踪十几个状态:今天是否已领取、累计充值档位、上一次挑战的层数、队伍成员编号、冷却剩余时间……在 TXT 里这些都要挤进编号变量,而且因为变量是全局的,两个功能抢用 [001] 就会互相覆盖,产生极难复现的偶发 Bug。
Lua 用 local 把状态关在函数里,用 table 把它们组织成一个整体,作用域天然隔离。这就是为什么「系统做到一定规模就必须换 Lua」不是风格偏好,而是变量容量与作用域机制决定的硬约束。
文件组织与触发体系
两套脚本共用同一个 Mir200\Envir 目录树,但走的文件不同。TXT 侧以 Market_def\QFunction-0.txt 作为物品使用、技能释放这类事件的主触发文件,以 MapQuest_Def\QManage.txt 承担登录、计时器这类管理型触发,MapQuest_Def\MapQuest.txt 则按「地图编号 + 怪物名」的格式登记杀怪条件,在指定怪物死亡时触发对应脚本段;每个 NPC 的对话脚本各自放在 Market_Def 下。
Lua 侧的文件是与之平行的一套:触发文件同样叫 QFunction-0.lua,NPC 逻辑放进 Market_Def\npc 目录,例如 sabukNpc.lua 负责沙巴克相关 NPC 逻辑、sabukWarSayStr.lua 存放对应文案,召唤这类 NPC 时直接指定 Envir\Market_Def\npc\sabukNpc.lua 即可。所以从目录结构看,Lua 并不是 TXT 的替代品,而是与它并排的另一个入口,这也解释了两者为什么能混写。
TXT 侧目录结构
Mir200\Envir\
├─ Market_def\
│ ├─ QFunction-0.txt 主触发:物品、技能
│ └─ <NPC 名>.txt 各 NPC 对话脚本
└─ MapQuest_Def\
├─ QManage.txt 登录、计时器触发
└─ MapQuest.txt 杀怪触发(按地图与怪物名登记)
Lua 侧目录结构
Mir200\Envir\
├─ Market_Def\
│ ├─ QFunction-0.lua 与 TXT 平行的主触发
│ └─ npc\
│ ├─ sabukNpc.lua NPC 逻辑模块
│ └─ sabukWarSayStr.lua 文案与配置
└─ 功能模块\ 自定义模块,可被 callscript 调用
图 2 TXT 与 Lua 的触发体系对照
物品、技能等主触发"] T2["QManage.txt
登录、计时器管理触发"] T3["MapQuest.txt
杀怪触发(怪物死亡)"] T4["Market_Def 下各 NPC 对话脚本"] end subgraph LUASET["Lua 触发体系"] L1["QFunction-0.lua
与 TXT 平行的主触发"] L2["QManage.lua
登录、计时器触发"] L3["Market_Def 下的 npc 目录
各 NPC 逻辑模块"] L4["自定义功能模块
可被 callscript 调用"] end T1 -.功能对应.-> L1 T2 -.功能对应.-> L2 T4 -.功能对应.-> L3
说明:两侧文件在不同引擎中的命名可能略有差异,但「主触发、管理触发、NPC 脚本」这三层结构是共通的。
| 触发类型 | TXT 位置 | Lua 位置 | 触发时机 |
|---|---|---|---|
| 主触发 | Market_def\QFunction-0.txt |
QFunction-0.lua |
物品使用、技能释放等 |
| 管理触发 | MapQuest_Def\QManage.txt |
QManage.lua |
人物登录、定时器到点 |
| 杀怪触发 | MapQuest_Def\MapQuest.txt |
杀怪事件函数或钩子 | 指定怪物在地图上被击杀时 |
| NPC 对话 | Market_Def\<NPC 名>.txt |
Market_Def\npc\<NPC 名>.lua |
玩家点击 NPC 打开对话 |
| 事件段落 | 如登录段、杀怪段、定时段 | 同名事件函数或注册的钩子 | 对应引擎事件被触发时 |
混写与互调
两套脚本能共存的关键,是引擎提供了一条从 TXT 调用 Lua 的通道。996 引擎给出的用法是 callscript(player, "功能/调用", "@调试功能调用"),并有三条容易踩坑的约定:这行要放在 MarkerDef 下面;调用 txt 文件时不要用大括号把路径括起来;代码里写的路径不带 .txt 后缀。至于数据层面则不需要额外处理,因为全局变量默认就是两边都能用的。
路径可以写成相对形式。例如把主界面基础按钮的功能写成可调用脚本后,在登录时用 callscript(player, "../QuestDiary/主界面基础按钮/主界面基础按钮QM", "@基础按钮QM") 直接触发,就等于在登录流程里执行了那一整段逻辑。
图 3 TXT 与 Lua 的互调关系
说明:全局变量是两侧共享的数据总线,自定义命令是 Lua 能力向 TXT 的回流通道。
反向的回流同样成立。把 Lua 逻辑封装好之后,它实际上相当于给引擎增加了一条新的命令,TXT 侧可以直接调用,常见的说法是「封装是真好使,相当于自己定义命令」。这就形成了最实用的架构:TXT 保留触发与界面入口,Lua 承担实现,Lua 再把自己暴露成 TXT 能调用的命令。
两套脚本的安全问题是一致的:玩家对功能执行时会试图获取对应的二进制执行功能,因此在对 link、goto、call、messagebox 这类带 @ 字段的调用做接口时,一定要做好指纹判断;用 Lua 做面板时,客户端虽然能做常规检测,但客户端本身就是可修改的,服务端必须同时做判断。
调试与可维护性
TXT 脚本的可观测性很有限。它没有断点,也没有变量查看器,最常用的手段是插入一条 SENDMSG 把中间值发给玩家看,靠肉眼看输出推断执行到了哪一步。由于一段对话会随着条件跳转在多个段落之间流转,出错时往往要在整份文件里追踪跳转链。
Lua 侧的观测手段更接近常规编程:可以用 release_print 把消息打印到控制台,也可以用 player:send 发给玩家,报错信息能带上文件名与行号。更重要的是模块化带来的隔离效果:一个功能出问题,排查范围通常局限在对应模块内,而不是整份触发文件。
| 对比项 | TXT 脚本 | Lua 脚本 |
|---|---|---|
| 日志输出 | 主要靠 SENDMSG 发给玩家,服务端侧记录有限 | release_print 输出到控制台,player:send 输出给玩家 |
| 断点调试 | 无 | 通用引擎一般也无 IDE 断点,但可用日志分层定位 |
| 错误定位 | 按行号与标签推断,信息有限 | 报错含文件名与行号,定位更直接 |
| 死循环保护 | 依赖 ScriptGotoCountLimit 兜底 | 循环边界由代码显式控制 |
| 故障影响面 | CALL 复用导致改动容易波及多处 | 模块与函数隔离,影响面可控 |
| 静态检查 | 命令与参数靠人工核对,难做语法检查 | 可做语法检查与代码规范检查 |
| 版本管理 | 纯文本可比较差异,但改动常跨多个段落 | 纯文本可比较差异,模块粒度清晰 |
性能特征
脚本语言普遍比宿主程序慢,这是解释执行的固有代价,普遍的经验量级是比同场景的 C# 慢约 5 至 10 倍。但在这个前提下,TXT 与 Lua 的相对优劣取决于场景,而不是绝对速度。
单点判断是 TXT 的舒适区:逐行读到条件就返回,没有虚拟机启动与函数调用开销。一旦进入循环与数据操作,天平就倒向 Lua:在数值计算、循环与函数调用这类场景下,借助即时编译技术,它的速度有时甚至能接近 C 语言编译后的代码。另外在文件读写这类高频操作上,引擎还提供了把数据文件预加载进内存的机制,让脚本读取时不必反复访问硬盘,这对拾取、掉落这类会反复读取配置的触发尤其有用。
| 场景 | TXT 表现 | Lua 表现 | 结论 |
|---|---|---|---|
| 单点条件判定 | 极快,逐行命中即返回 | 快,但有函数调用开销 | 短逻辑 TXT 略优 |
| 大量循环遍历 | 只能靠 GOTO 回跳,代价高且有死循环风险 | 原生 for 与 while,边界可控 | Lua 明显更优 |
| 复杂数据结构 | 没有数组与字典,无法表达 | table 支持快速查找与嵌套 | 只有 Lua 可行 |
| 高频触发 | 开销随脚本长度线性增长 | 字节码执行,同逻辑代码更短 | 高频场景 Lua 更省 |
| 高频文件读写 | 需依赖引擎的内存加载命令配合 | 同样可用预加载机制 | 取决于是否用上内存缓存 |
| 极限优化 | 没有优化空间 | 可借助即时编译等技术 | Lua 上限更高 |
因此「TXT 比 Lua 快」这个说法只在很短的单点逻辑上成立。当需求变成遍历、聚合、排序与状态机时,TXT 需要指数级更长的脚本才能勉强表达,而更长的脚本意味着更多的解释开销与更高的死循环概率,实际表现反而更差。
速度、效率与上手门槛
讨论 TXT 与 Lua 谁更快,最容易犯的错是把三种完全不同的「快」混为一谈:执行快指同一段逻辑在运行时耗掉多少 CPU,开发快指把需求变成可用脚本要花多少时间,上手快指一个新人从零到能独立改脚本需要多久。上一节的性能对照回答的是第一种;而实际选型中最容易被忽略的恰恰是后两种,尤其是第三种,它正是 TXT 最大的、也几乎不会被 Lua 取代的优势。
TXT 的上手速度是它最容易被低估、也最难被取代的优势,原因很直接:要学的不是一门编程语言,而是一张命令表。命令与效果一一对应,CHECKITEM 金创药 10 就是「检查背包里有没有 10 个金创药」,GAMEGOLD + 100 就是「元宝加 100」,中间没有变量类型、没有作用域、没有函数、没有返回值,也没有任何需要预先建立的抽象概念。看懂一条就能写一条,记住常用的一批命令,就能独立改出可用的 NPC 对话与触发脚本。
门槛低还体现在工程侧:写 TXT 不需要搭开发环境、不需要编译、不需要理解模块与依赖,一个文本编辑器就是全部工具链,改完保存即生效。语法容错也很宽,写错一行最常见的后果是那一条命令没执行、那一段对话没生效,而不是整个服务端报错,所以新手的试错代价很小,可以边看边改、改完立刻看效果,学习过程本身就是即时反馈的。
这些特性决定了 TXT 最适合的人与场景:没有编程基础的策划、刚接触引擎的新手,以及只需要做 NPC 对话、任务奖励、活动公告、简单检测与数值增减的人。对这些需求来说,学 Lua 的时间成本远远大于收益:同一个「交 10 个金创药换 100 元宝」,TXT 三行就写完,Lua 却要先理解函数、参数、返回值与对象方法。再加上 TXT 脚本在社区里大量流通,新手可以把别人的脚本复制过来改几个数字就能用,这种「抄改」式的路径进一步拉平了入门曲线。
把「新手友好」理解成「技术落后」是常见误判。TXT 的门槛低,不是因为它的机制简单,而是因为它被设计成一张与引擎能力一一对应的命令表:只要引擎提供了对应命令,写起来就是所见即所得,中间没有任何需要额外理解的层次。
这也是为什么大量成熟版本至今仍把 NPC 对话、任务奖励、活动公告这类「入口层」逻辑留在 TXT 里,只把算不出来、存不下的部分交给 Lua。TXT 的定位从来不是入门练习,而是引擎最省事的那一层接口。
Lua 的付出则在跨过门槛之后回本。它第一次实现某个功能确实更慢:要先想清楚数据结构、函数边界与错误处理。但需求一旦开始重复,比如第二个奖励配置、第三套活动规则、第四种掉落判定,Lua 的效率优势就显现出来:写一次的函数可以被反复调用,table 能让配置驱动逻辑,改需求时动的是数据而不是脚本结构。在执行侧同样如此,循环、遍历与数据操作这些 TXT 只能用 GOTO 硬凑的场景,Lua 用原生语句就能完成,代码更短,解释开销反而更小;文件读写这类高频操作还能借助引擎的预加载机制,避开反复访问硬盘。
| 考察角度 | TXT 脚本 | Lua 脚本 | 结论 |
|---|---|---|---|
| 上手速度 | 记一张命令表即可开写,不需要编程基础 | 需要变量、作用域、函数与数据结构基础 | TXT 明显更快,最适合新手入门 |
| 入门门槛 | 无环境、无编译、无依赖,编辑器即工具链 | 需理解模块加载、调用约定与调试方式 | TXT 门槛最低 |
| 试错成本 | 写错通常只影响那一段,改完即见效果 | 语法错误会导致整个模块无法加载 | TXT 更适合边学边改 |
| 简单需求开发速度 | 引擎有现成命令时,两三行就能写完 | 要定义函数、组织参数与返回值 | 需求简单时 TXT 更快 |
| 重复需求开发效率 | 只能 CALL 整段,改一处要核对多处 | 函数与模块复用,第二个同类需求起大幅提速 | 需求反复迭代时 Lua 更快 |
| 单点执行速度 | 逐行解析,命中即返回,开销极低 | 需进入虚拟机并有函数调用开销 | 短逻辑 TXT 略优 |
| 循环与数据执行速度 | 靠 GOTO 回跳,脚本越长越慢 | 原生循环与 table,同逻辑代码更短 | Lua 明显更优 |
把三条线合起来看,选型判断就清楚了:如果需求全部落在引擎已有命令的覆盖范围内,TXT 就是最省事的选择,新手也能直接上手,没有任何理由引入 Lua;一旦需求里出现「引擎没有现成命令、需要自己算、自己存、自己封装」的部分,那就是投入学习 Lua 的临界点,此时多付出的上手成本,会在第二个同类功能上由开发效率与执行效率一起赚回来。成熟版本通常不走极端:TXT 守住门槛最低的入口层,Lua 承担实现层,两者各用所长。