完整课程入口:996 全套课程体系 | Lua 学习路径 | 幂尔框架 mirs.cn
Lua 的协程(coroutine)是协作式"用户态线程":同一时刻只有一个协程在跑,切换点完全由你自己用 coroutine.yield 声明。它没有锁、没有竞态,是游戏脚本里做"分帧执行、异步流程"最安全的工具——这也是它和真线程的本质区别。
四个核心 API 构成全部生命周期:create 创建后返回一个 thread 对象但不执行;resume 启动或恢复它,一路跑到 yield 或结束;yield 挂起并把值带回给 resume;status 查看当前是 suspended、running 还是 dead。
local co = coroutine.create(function(total)
for i = 1, total do
coroutine.yield(i)
end
return "done"
end)
release_print(coroutine.resume(co)) -- true 1
release_print(coroutine.resume(co)) -- true 2
release_print(coroutine.resume(co)) -- true done 字符串
批量刷 5000 个怪物的数据初始化如果一口气做完,主循环会卡死一帧。用协程把工作切片,每帧只处理一批:
local worker = coroutine.create(function(list)
for i, cfg in ipairs(list) do
initMonster(cfg)
if i % 200 == 0 then coroutine.yield() end -- 每 200 个让出一次
end
end)
-- 挂进引擎的循环触发器里,每帧 resume 一次
local ok = coroutine.resume(worker, monsterList)
if coroutine.status(worker) == "dead" then
release_print("初始化完成")
end
这就是最朴素的"分帧器"骨架:进度天然保存在协程内部,不需要额外的游标变量,中断与恢复都由调度器一句话完成。
协程的另一个高价值场景是把"回调地狱"拉直。把"等待玩家确认""等待服务器返回"封装成 yield 点,整段业务就能写成顺序代码,可读性远高于层层嵌套的回调。
错误处理要记住两条:协程内的 Lua 错误不会向外抛,而是通过 resume 的第一个返回值(false)和错误消息带回,所以每次 resume 都要检查 ok;需要完整栈信息时,在协程体内用 xpcall(主逻辑, debug.traceback) 包一层,resume 拿到的错误串就带着出错位置,排查联动功能时能省一半时间。
最后一个实用细节:协程对象本身有开销,但很小,按任务粒度创建即可;真正要避免的是在 yield 之后把宿主数据悄悄改掉——恢复点之间数据结构发生了变化,是协程逻辑出 bug 的最大来源,写代码时保持"yield 前后假设世界会变"的警惕即可。
setontimerex(id, tick)
添加全局定时器,协程分帧最常用的驱动器
| 参数 | 类型 | 说明 |
|---|---|---|
| id | integer | 定时器ID(必填参数) |
| tick | integer | 执行间隔,秒(必填参数) |
setontimerex(23, 5)
setofftimerex(id)
移除全局定时器,协程跑完必须摘掉,否则下一帧继续进回调
| 参数 | 类型 | 说明 |
|---|---|---|
| id | integer | 定时器ID(必填参数) |
ontimerexX(empty)
全局定时器触发(X=触发器ID),在触发里推进协程,注意单帧预算
| 参数 | 类型 | 说明 |
|---|---|---|
| empty | empty | 无参数 |
delaygoto(actor, time, func, del)
延时跳转,需要等待一段时间的协程用延时回调衔接
| 参数 | 类型 | 说明 |
|---|---|---|
| actor | object | 玩家对象(必填参数) |
| time | integer | 时间(毫秒)(必填参数) |
| func | string | 触发函数(必填参数) |
| del | integer | 换地图是否删除此延时(0或为空时=不删除 1=删除)(必填参数) |
cleardelaygoto(actor, func)
删除延迟,协程被取消时同步清掉挂着的延时
| 参数 | 类型 | 说明 |
|---|---|---|
| actor | object | 玩家对象(必填参数) |
| func | string | 需要删除的延时函数不填为清除全部(必填参数) |
printusetime(play, on, off)
打印脚本总耗时(微秒),验证分帧是否真的把单帧耗时压下来了
| 参数 | 类型 | 说明 |
|---|---|---|
| play | object | 玩家对象 |
| on | integer | 1=开始技术2=结束计时并打印耗时 |
| off | integer | 2=结束计时 |