Lua实现事件派发器

为了降低模块间的耦合, 很多系统使用事件派发机制, 接收方无需知道派发者是谁.在Qt中,这个系统被称作Slot&Signal, 需要配合moc代码生成机制, 但是系统本身是线程安全的.

这里我们讨论的是lua的事件派发机制, 我将此写成lua模块, 方便配合cocos2dx进行逻辑处理

 

local Global = _G
local package = _G.package
local setmetatable = _G.setmetatable
local assert = _G.assert
local table = _G.table
local pairs = _G.pairs
local ipairs = _G.ipairs
 
 
module "Core.EventDispatcher"
 
--[[
数据层次
 
["EventName1"] =
{
    ["_StaticFunc"] = { Func1, Func2 },
    
    [Object1] = { Func1, Func2 },
    [Object2] = { Func1, Func2 },
},
 
["EventName2"] =
{
    ...
}
 
]]
 
-- 默认调用函数
local function PreInvoke( EventName, Func, Object, UserData, ... )
    
    if Object then
        Func( Object, EventName, ... )
    else
        Func( EventName, ... )
    end
 
end
 
function New( )    
    
    local NewObj = setmetatable( {}, { __index = package.loaded["Core.EventDispatcher"] } )
    
    -- 对象成员初始化
    NewObj.mPreInvokeFunc = PreInvoke
    NewObj.mEventTable = {}
    
    return NewObj
end
 
-- 添加
function Add( Self, EventName, Func, Object, UserData )
 
    assert( Func )
 
    Self.mEventTable[ EventName ] = Self.mEventTable[ EventName ] or {}
    
    local Event = Self.mEventTable[ EventName ]
    
    if not Object then
        Object = "_StaticFunc"
    end
    
    Event[Object] = Event[Object] or {}
    local ObjectEvent = Event[Object]
 
    ObjectEvent[Func] = UserData or true
    
end
 
-- 设置调用前回调
function SetDispatchHook( Self, HookFunc )
    
    Self.mPreInvokeFunc = HookFunc
end
 
 
-- 派发
function Dispatch( Self, EventName, ... )
 
    assert( EventName )
    
    local Event = Self.mEventTable[ EventName ]
    
    for Object,ObjectFunc in pairs( Event ) do
        
        if Object == "_StaticFunc" then
                
            for Func, UserData in pairs( ObjectFunc ) do
                Self.mPreInvokeFunc( EventName, Func, nil, UserData, ... )    
            end
            
        else
        
            for Func, UserData in pairs( ObjectFunc ) do
                Self.mPreInvokeFunc( EventName, Func, Object, UserData, ... )
            end
        
        end
 
    end
 
end
 
-- 回调是否存在
function Exist( Self, EventName )
 
    assert( EventName )
    
    local Event = Self.mEventTable[ EventName ]
    
    if not Event then
        return false
    end
    
    -- 需要遍历下map, 可能有事件名存在, 但是没有任何回调的
    for Object,ObjectFunc in pairs( Event ) do
    
        for Func, _ in pairs( ObjectFunc ) do
            -- 居然有一个
            return true
        end
    
    end
    
    
    return false
    
end
 
-- 清除
function Remove( Self, EventName, Func, Object )
    
    assert( Func )
    
    local Event = Self.mEventTable[ EventName ]
    
    if not Event then
        return
    end
    
    if not Object then
        Object = "_StaticFunc"
    end
    
    
    local ObjectEvent = Event[Object]
    
    if not ObjectEvent then
        return
    end
    
    ObjectEvent[Func] = nil
 
        
end
 
-- 清除对象的所有回调
function RemoveObjectAllFunc( Self, EventName, Object )
 
    assert( Object )
    
    local Event = Self.mEventTable[ EventName ]
    
    if not Event then
        return
    end
    
    Event[Object] = nil
 
end
 

这里注意下, 我是将EventDispatcher.lua放置在Core目录下, 因此需要使用require “Core.EventDispatcher”进行调用

使用用例

 

    local EventDispatcher = require 'Core.EventDispatcher'
 
    local E = EventDispatcher.New()
 
 
    E:Add( "a", function( a, b )   print( a, b ) end )
 
    local Func = function( a )   print( a ) end 
    E:Add( "a", Func )
 
 
    E:Dispatch("a", 1, 2 )
    print( E:Exist("a"), E:Exist("b"))
 
    E:Remove("a", Func )
 
    E:Dispatch("a", 1, 2 )
    print( E:Exist("a"), E:Exist("b"))
<think>我们正在讨论FGUI框架中使用Lua语言为按钮点击事件传递参数的方法。在FGUI中,按钮点击事件通常通过事件回调函数来处理。但是,FGUI的事件回调默认不直接支持传递自定义参数。因此,我们需要使用一些技巧来实现参数传递。常见的方法有:1.使用闭包(Closure)来捕获外部变量作为参数。2.将参数存储在按钮的自定义属性中,在回调中读取。3.使用全局变量或模块变量(不推荐,因为容易造成混乱)。下面我们分别介绍这些方法。###方法1:使用闭包在Lua中,闭包可以捕获其外部局部变量。因此,我们可以在为按钮设置点击事件回调时,创建一个闭包,该闭包内部使用外部变量。示例代码:```lualocalbutton=...--获取按钮对象,例如通过FairyGUI.UIPackage.CreateObject("包名","组件名"):GetChild("按钮名")localfunctiononClick(context)--在这里可以使用外部变量paramprint("参数值为:",param)endlocalparam=123--要传递的参数button.onClick:Set(function()--这里创建了一个闭包,捕获了外部的paramonClick(param)end)```但是注意,上述代码中,我们每次点击按钮都会传递相同的参数(123)。如果我们想为不同的按钮实例传递不同的参数,可以在循环中为每个按钮创建独立的闭包。示例(多个按钮):```lualocalbuttons={...}--假设有多个按钮fori,btninipairs(buttons)dolocalparam=i--每个按钮的参数不同btn.onClick:Set(function()print("按钮"..i.."被点击,参数为:",param)end)end```###方法2:使用自定义数据(通过data属性)FGUI的GObject类(按钮的基类)有一个data属性,可以存储任意用户数据。我们可以将参数存储在按钮的data属性中,然后在回调中读取。示例:```lualocalbutton=...button.data={param1="hello",param2=123}--设置自定义数据button.onClick:Set(function(context)localsender=context.sender--获取事件发送者,即按钮localdata=sender.dataprint("参数1:",data.param1,"参数2:",data.param2)end)```或者,如果只需要一个简单的值(如数字、字符串),也可以直接赋值:```luabutton.data=100button.onClick:Set(function(context)localsender=context.senderprint("参数:",sender.data)end)```###方法3:使用成员变量(如果按钮是组件的一部分)如果按钮在一个组件内,我们可以在组件的Lua脚本中定义成员变量,然后在按钮回调中访问这些成员变量。这种方法适用于参数与组件状态相关的情况。示例(在组件脚本中):```lualocalM=class('MyComponent',FairyGUI.GComponent)functionM:ctor()--在构造函数中初始化变量self.param="test"endfunctionM:onButtonClick(context)print("参数:",self.param)end--在组件初始化时绑定functionM:constructFromXML(xml)self.button=self:GetChild("button")self.button.onClick:Add(self.onButtonClick,self)--注意这里使用了Add方法,并传递了self作为第二个参数(即回调的this参数)end```注意:在FGUI的Lua绑定中,通常使用`Add`方法绑定事件,并可以指定一个额外的参数作为回调函数的第一个参数(即self)。这样在回调函数中就可以通过self访问组件实例。以上三种方法可以根据实际场景选择使用。现在,根据要求生成相关问题:§§相关问题§§1.在FGUI中,如何为多个按钮绑定同一个回调函数并区分它们?2.使用闭包传参有什么需要注意的地方?3.在FGUILua中,如何避免因事件绑定导致的内存泄漏?4.除了按钮点击事件,这些传参方法是否适用于其他FGUI事件?</think>### FGUI Lua 按钮点击事件传参方法详解 在FGUI(FairyGUI)框架中使用Lua处理按钮点击事件传参,主要有以下三种实用方法: #### 方法1:闭包捕获参数(推荐) 通过Lua闭包特性捕获外部变量作为参数: ```lua local btn = component:GetChild("btnName") -- 获取按钮对象 local function setupButton(param1, param2) btn.onClick:Set(function() -- 直接使用闭包捕获的变量 print("参数1:", param1, "参数2:", param2) -- 执行业务逻辑 end) end setupButton(100, "test") -- 绑定时传入参数 ``` **优点**:简洁高效,无需额外数据结构 **适用场景**:参数固定的简单事件处理 #### 方法2:使用`data`属性存储参数 通过FGUI对象的`data`属性存储自定义数据: ```lua local btn = component:GetChild("btnName") -- 设置参数到data属性 btn.data = { id = 2023, type = "attack", callback = function() print("执行回调") end } btn.onClick:Set(function(context) local sender = context.sender -- 获取事件发送者 local params = sender.data -- 读取存储的参数 print("按钮ID:", params.id) print("动作类型:", params.type) params.callback() -- 执行存储的回调函数 end) ``` **优点**:参数与UI对象绑定,支持动态修改 **适用场景**:需要动态更新参数的复杂逻辑 #### 方法3:事件分发器传递参数(高级) 通过自定义事件携带参数: ```lua -- 定义带参数的事件 local EventType = { CustomClick = "CustomClick" } btn.onClick:Set(function() -- 派发自定义事件并携带参数 btn:DispatchEvent(EventType.CustomClick, { username = "player01", score = 1500 }) end) -- 在监听处接收参数 btn:AddEventListener(EventType.CustomClick, function(context) local data = context.data -- 获取传递的参数 print("玩家:", data.username, "分数:", data.score) end) ``` **优点**:解耦事件触发与处理逻辑 **适用场景**:跨组件通信或复杂系统架构 ### 关键注意事项 1. **内存管理**: - 避免在闭包中捕获大型对象 - 移除事件监听时需显式调用`RemoveEventListener` ```lua btn:RemoveEventListener(EventType.CustomClick) ``` 2. **参数生命周期**: - `data`属性存储的参数会随UI对象销毁而释放 - 闭包捕获的参数需注意循环引用问题 3. **性能优化**: - 高频触发事件避免复杂数据结构 - 使用轻量级参数(如整数代替字符串) > **最佳实践**:简单场景用闭包,动态参数用`data`属性,模块解耦用事件分发。实际开发中可组合使用这些方法[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值