GameEvent = {}
local _listeners = {}
function GameEvent.add(eventName, func, tag, priority)
assert(tag, "Tag must not be nil")
if not _listeners[eventName] then
_listeners[eventName] = {}
end
local eventListeners = _listeners[eventName]
后端Lua尊者:我是lua后端老师!
小仙,你这套事件系统代码写得挺有模样的,我来帮你翻译一下,并教你如何在996引擎里用它来管理NPC创建和点击这类事件。
首先,这是你代码的翻译和解释:
```lua
-- 事件系统模块
GameEvent = {}
-- 内部存储监听器的表
local _listeners = {}
-- 功能:添加事件监听器
-- eventName: 事件名称 (字符串)
-- func: 当事件触发时要执行的函数
-- tag: 标签,用于标识和移除监听器,不能为nil
-- priority (可选): 优先级,数字越小优先级越高,决定执行顺序
function GameEvent.add(eventName, func, tag, priority)
assert(tag, "Tag must not be nil") -- 断言:标签不能为空
-- 如果这个事件还没有监听器列表,就创建一个
if not _listeners[eventName] then
_listeners[eventName] = {}
end
local eventListeners = _listeners[eventName]
local eventListenerSize = #eventListeners
-- 检查是否已经用相同的tag监听过这个事件,避免重复添加
for i = 1, eventListenerSize do
if tag == eventListeners[i][2] then
return -- 如果已存在,直接返回,不添加
end
end
-- 根据优先级插入监听器,或者直接添加到末尾
if priority and eventListenerSize >= priority then
table.insert(eventListeners, priority, {func, tag})
else
table.insert(eventListeners, {func, tag})
end
end
-- 功能:通过函数引用移除监听器(遍历所有事件)
-- func: 要移除的监听函数
function GameEvent.remove(func)
for eventName, eventListeners in pairs(_listeners) do
for i = 1, #eventListeners do
if eventListeners[i][1] == func then
table.remove(eventListeners, i)
-- 如果这个事件没有监听器了,就清理掉这个事件
if 0 == #_listeners[eventName] then
_listeners[eventName] = nil
end
return
end
end
end
end
-- 功能:通过事件名和标签移除特定的监听器
-- eventName: 事件名称
-- tag: 标签
function GameEvent.removeByNameAndTag(eventName, tag)
assert(tag, "Tag must not be nil")
local eventListeners = _listeners[eventName]
if not eventListeners then return end -- 如果事件不存在,直接返回
-- 从后往前遍历,避免移除时索引错乱
for i = #eventListeners, 1, -1 do
if eventListeners[i][2] == tag then
table.remove(eventListeners, i)
break -- 找到一个就跳出(根据你的逻辑,一个tag在一个事件里应该只有一个?)
end
end
-- 清理空的事件
if 0 == #eventListeners then
_listeners[eventName] = nil
end
end
-- 功能:通过标签移除所有事件中的监听器
-- tag: 标签
function GameEvent.removeByTag(tag)
assert(tag, "Tag must not be nil")
for eventName, eventListeners in pairs(_listeners) do
GameEvent.removeByNameAndTag(eventName, tag) -- 这里调用了自身,但原代码是self,我修正为GameEvent
end
end
-- 功能:移除所有监听器,清空系统
function GameEvent.removeAll()
_listeners = {}
end
-- 功能:触发(派发)一个事件
-- eventName: 要触发的事件名称
-- ...: 可变参数,会传递给监听函数
function GameEvent.push(eventName, ...)
local eventListeners = _listeners[eventName]
if not eventListeners then
return -- 没有监听器,直接返回
end
-- 按添加顺序(或优先级)遍历执行所有监听函数
for index, listeners in ipairs(eventListeners) do
-- 使用pcall安全调用,避免一个监听器出错影响后面的
local result, stop = pcall(listeners[1], ...)
if result then
-- 如果监听函数执行成功,并返回了true(或其他真值),可以中断后续执行(根据你的逻辑)
if stop then break end
else
-- 如果执行出错,打印错误信息。这里假设tag可能是一个表,有ID字段。
local tag = listeners[2]
local tarid = tag.ID or 0
local err = "派发事件发生错误:事件名=" .. eventName .. " 模块ID=" .. tarid .. " 错误信息:" .. tostring(stop)
release_print(err) -- 使用引擎的打印函数
end
end
end
return GameEvent
```
**如何使用这套系统(以NPC创建和点击为例):**
小仙,在996引擎里,我们通常用QF触发(比如`clicknpc`)来响应NPC点击。你的这套事件系统可以作为一个**中心化的管理器**,让不同的脚本模块能监听同一个引擎事件,而不用互相干扰。
**第一步:初始化事件系统**
在你主要的脚本文件(比如`Main.lua`)里引入并初始化:
```lua
-- 假设你的GameEvent.lua放在同一目录
local GameEvent = require("GameEvent")
-- 你可以把它设为全局方便使用(或者保持局部,通过参数传递)
_G.GameEvent = GameEvent
```
**第二步:在QF触发中派发事件**
在`QFunction-0.txt`(或你的QF脚本)里,当引擎事件发生时,调用`GameEvent.push`来通知所有监听器。
例如,处理NPC点击:
```lua
[@clicknpc]
#act
-- clicknpc触发时,引擎会传递参数(通常是玩家对象和NPC信息)
-- 我们需要在Lua里捕获并派发
-- 假设我们有一个Lua脚本绑定了这个触发
local player = ...
local npcIndex = ...
-- 派发一个名为"OnClickNpc"的事件,并传递玩家和NPC索引
GameEvent.push("OnClickNpc", player, npcIndex)
break
```
**第三步:在不同的脚本模块中监听事件**
假设你有一个任务系统和一个活动系统,都需要知道玩家点击了某个NPC。
*任务系统脚本 (TaskSystem.lua)*:
```lua
local function onNpcClickedForTask(player, npcIndex)
release_print("任务系统:玩家" .. tostring(player) .. "点击了NPC,索引=" .. npcIndex)
-- 这里写你的任务逻辑,比如检查NPC是否是任务NPC,更新任务进度等
-- 使用引擎函数,例如检查任务状态
local taskId = 12
newchangetask(player, taskId) -- 刷新任务状态
end
-- 在任务系统初始化时,添加监听器。tag可以是模块名或一个唯一标识表。
GameEvent.add("OnClickNpc", onNpcClickedForTask, {ID = 1001, name = "TaskSystem"})
```
*活动系统脚本 (ActivitySystem.lua)*:
```lua
local function onNpcClickedForActivity(player, npcIndex)
release_print("活动系统:玩家" .. tostring(player) .. "点击了NPC,索引=" .. npcIndex)
-- 活动逻辑,比如点击特定NPC打开活动面板
if npcIndex == 101 then -- 假设101是活动使者
opennpcshow(player, 101, 5) -- 打开NPC面板,范围5
end
end
GameEvent.add("OnClickNpc", onNpcClickedForActivity, {ID = 1002, name = "ActivitySystem"})
```
**第四步:创建NPC时触发事件**
创建NPC的逻辑可能写在某个地图脚本或管理脚本里。你也可以定义自己的事件。
```lua
-- 某个地图初始化或条件触发时
local mapID = "3"
local x, y = 100, 100
local npcInfo = {
["Idx"] = 9999, -- 自定义ID,不要和配置表重复
["npcname"] = "动态NPC",
["appr"] = 7,
["script"] = 'DynamicNPC'
}
createnpc(mapID, x, y, tbl2json(npcInfo))
-- 派发一个NPC创建事件,通知其他系统(比如地图怪物管理系统、特效系统)
GameEvent.push("OnNpcCreated", mapID, x, y, npcInfo)
```
**第五步:移除监听器(当模块卸载时)**
```lua
-- 当任务系统关闭或重载时,移除它的监听器,避免内存泄漏和错误调用
GameEvent.removeByTag({ID = 1001, name = "TaskSystem"})
-- 或者更精确地移除某个事件下的监听
GameEvent.removeByNameAndTag("OnClickNpc", {ID = 1001, name = "TaskSystem"})
```
**核心要点:**
1. **事件名 (`eventName`)**:是你自定义的字符串,比如`"OnClickNpc"`, `"OnPlayerLogin"`, `"OnMonsterKilled"`。用来区分不同的事件。
2. **监听函数 (`func`)**:当事件发生时被调用的函数。它的参数由`GameEvent.push`时传递。
3. **标签 (`tag`)**:**非常重要**!通常用一个表(如`{ID=1001, name="ModuleName"}`)来唯一标识一个监听器。它用于防止重复添加和精准移除。原代码断言它不能为nil。
4. **优先级 (`priority`)**:可选。数字越小越先执行。如果你不关心顺序,可以不用。
5. **派发 (`push`)**:在引擎事件发生的地方(QF触发、定时器、条件判断后)调用`GameEvent.push`,并传递事件名和参数。
6. **错误处理**:你的`push`函数用了`pcall`,这很好,能防止一个模块的错误导致整个事件系统崩溃。
**给你的建议:**
小仙,这套系统在模块化开发中很有用。但要注意,在996引擎中,很多功能直接通过QF触发和Lua脚本配合就能完成。事件系统更适合**大型、多模块**的项目,用于解耦。如果只是简单的功能,直接写QF脚本可能更直接。
另外,你原代码`GameEvent.removeByTag`里调用了`self.removeListenerByNameAndTag`,这里应该是`GameEvent.removeByNameAndTag`,我已在翻译中修正。`self`在那个上下文里可能未定义。
还有问题随时问我,不懂的也可以请教浮生梦尊师哦!