首页 / 技术文章地图 / 正文

【框架设计】事件总线 EventBus:模块解耦的胶水层

发布:2026-09-20 07:58 | 作者:996 技术组 | 3 阅读
完整课程入口:996 全套课程体系Lua 学习路径幂尔框架 mirs.cn

直调的痛:牵一发动全身

业务初期,模块之间直接互相调用:充值成功后,充值模块去调背包加道具、调公告、调成就统计。三个月后加"充值返利",又得改充值模块。问题不在业务,在于依赖方向错了——上游模块不应该知道下游有谁。

事件总线(EventBus)的解法:模块只负责"发布事件",不关心谁在听;关心者自行"订阅事件"。充值模块只发一条 RECHARGE_OK,背包、公告、返利各自监听处理。新增需求 = 新增订阅者,上游零改动。

lua
local Bus = { listeners = {} }

function Bus.on(event, fn)
    Bus.listeners[event] = Bus.listeners[event] or {}
    table.insert(Bus.listeners[event], fn)
end

function Bus.emit(event, ...)
    local list = Bus.listeners[event]
    if not list then return end
    for _, fn in ipairs(list) do fn(...) end
end

-- 充值模块只发事件
Bus.emit("RECHARGE_OK", playerId, amount)
-- 各模块各自订阅
Bus.on("RECHARGE_OK", GiveItems)
Bus.on("RECHARGE_OK", SendAnnounce)

四条工程纪律

事件名集中定义。 用一张常量表管理事件名,防止"RechargeOk / RECHARGE_OK / recharge_ok"三套拼写并存——字符串事件名裸写是事件系统腐化的开始。

订阅必须能反注册。 Bus.off(event, fn)on 成对存在,界面销毁、对象删除时调用。否则监听闭包抓着已销毁的界面引用,轻则空指针报错,重则内存泄漏。上一轮讲过的闭包泄漏,事件总线是最常见的泄漏源头。

处理器内不得抛错炸全场。 emit 时用 pcall 包住每个监听器,单个处理器出错记日志、继续执行其余监听,保证一个模块的 bug 不阻断整条事件链。

同步为默认,异步要显式。 emit 是同步派发,处理器立即执行;需要延迟/分帧的用 emitLater 走协程或定时器。混用而不标记,时序问题将极难排查。

边界在哪

事件总线不是银弹:事件链一长,"谁动了数据"会变难查。约定两条线——强依赖、必达的逻辑走直接调用;广播性质、可有可无的副作用走事件。把事件名、参数结构写进文档,事件总线才是解耦神器而不是谜团制造机。

注册消息

regnpcmsg(msgId, NPCIndex)

给NPC注册Lua消息,事件总线的对外入口,外部只认消息名而不是函数名

参数类型说明
msgIdint消息ID(必填参数)
NPCIndexintNPC索引(NPC配置表中的ID)(必填参数)

handlerequest(actor, msgid, param1, param2, param3, sMsg)

监听消息,引擎侧消息也接到同一张总线上

参数类型说明
actorobject玩家对象(必填参数)
msgidinteger消息ID(必填参数)
param1integer参数1(必填参数)
param2integer参数2(必填参数)
param3integer参数3(必填参数)
sMsgstring消息内容(必填参数)

触发派发

gotolabel(actor,type,label,range)

调用触发,总线的一层兜底派发

参数类型说明
actorobject玩家对象(必填参数)
typeinteger触发模式:0小组成员触发1行会成员触发2当前地图的人物触发3当前角色范围的人物触发8当前国家的人物触发
labelstring跳转后的接口(必填参数)
rangeinteger触发模式=3时,该参数为指定的范围大小

startup(sysobj)

触发开始 服务端启动触发,启动阶段完成订阅注册,避免运行期再改订阅表

参数类型说明
sysobjsysobj系统对象(无法操作)

跨模块调用

callfunbynpc(actor, npcidx, delaytime, func, sParam)

调用其他NPC的lua函数(注意:如果开启多线程,会导致执行失败),总线解耦后仍需要的直接调用通道,谨慎开放

参数类型说明
actorobject玩家对象(必填参数)
npcidxintNPC索引(NPC配置表中的ID),特殊npcid:QF=999999999,QM=999999996,LuaCond=999999995,LuaFunc=999999994(必填参数)
delaytimeint延迟时间ms,0立即执行(必填参数)
funcstring函数名(必填参数)
sParamstring参数(必填参数)

广播

sendrefluamsg(actor, msgid, param1, param2, param3, sMsg)

发送视野内广播消息,总线事件需要同步到前端时的出口

参数类型说明
actorobject玩家对象(必填参数)
msgidinteger消息ID(必填参数)
param1integer参数1
param2integer参数2
param3integer参数3
sMsgstring消息内容
作者履历与出处
本文由 996 技术组基于 996 引擎官方知识库与浮生梦老师课程体系整理,讲解体系出自多年商业端开发生产一线。作者团队长期从事传奇类引擎 Lua 后端逻辑、客户端界面与版本交付,内容以官方知识库与真实项目为出处,按版本持续修订。
© 威海旷世互娱 · 返回文章地图 · 课程体系 · 幂尔框架