资讯动态

RE-UE4SS Lua 指南:MulticastDelegateProperty 多播委托属性的绑定、广播与调用链原理

发布时间:2026/10/4 10:00:49 来源:尧图企业网站定制
游戏开发逆向工程【免费下载链接】RE-UE4SSInjectable LUA scripting system, SDK generator, live property editor and other dumping utilities for UE4/5 games项目地址https://gitcode.com/gh_mirrors/re/RE-UE4SS点击查看免费下载导读本文围绕 RE-UE4SS 的 Lua API 文档中的MulticastDelegateProperty类型展开讲解如何在 Lua 脚本中通过Object.PropertyName拿到多播委托的属性包装器并使用Add/Remove/Clear/GetBindings/Broadcast五个方法完成对 UE4/5 多播委托FMulticastDelegateProperty内联存储调用列表的完整操作。读完本文你将掌握在 Lua 中为PlayerController.OnPossessedPawnChanged、Actor.OnEndPlay等常见多播委托绑定/解绑回调、枚举现有绑定并主动触发广播的完整实战方案并理解这些方法在 LuaXDelegateProperty.cpp 与 LuaUObject.cpp 中的底层实现原理。概述UE 多播委托与 UE4SS 的 Lua 封装MulticastDelegateProperty是 UE4SS Lua API 中对 Unreal EngineFMulticastDelegateProperty的封装。在 UE 中多播委托Multicast Delegate与单播委托Single-cast Delegate的核心区别在于多播委托可以同时绑定多个“对象 函数”对当委托被广播时调用列表中的所有绑定函数会被依次触发。UE4SS 的该类封装对应引擎中的**内联存储inline storage**变体即委托的调用列表Invocation List直接存放在 UObject 的属性值中是蓝图与 C 中最常见的多播委托形态。在类继承关系上MulticastDelegateProperty继承自 Property而Property又继承自RemoteObject。这意味着该类型天然拥有基类提供的能力GetFullName()返回属性的完整名称与路径stringGetFName()返回属性的FName按值拷贝IsA(PropertyTypes)判断属性是否属于指定类型boolGetClass()返回属性类PropertyClassContainerPtrToValuePtr(Container, ArrayIndex)等价于 UE 的FProperty::ContainerPtrToValuePtruint8返回LightUserdataImportText(Buffer, Data, PortFlags, OwnerObject)等价于FProperty::ImportText从源码结构看LuaXDelegateProperty.hpp 中XMulticastDelegateProperty继承自RemoteObjectBaseUnreal::FMulticastDelegateProperty, FMulticastDelegatePropertyName其Super即XProperty并且在内部持有两个关键成员m_base委托所属的容器对象UObject*m_property对应的FMulticastDelegateProperty*这两个成员是理解后续所有方法的关键包装器在被创建时就隐式记住了容器对象因此调用Add、Remove、Clear等方法时无需再传入容器。与单播、稀疏多播委托的对比为准确使用本类型应先厘清三类 delegate 属性在 UE4SS Lua API 中的差异类型引擎类存储方式绑定能力Lua 访问方式DelegatePropertyFDelegateProperty单播直接内联仅 1 个对象函数对直接返回/赋值{Object, FunctionName}表MulticastDelegateProperty本文FMulticastDelegateProperty多播内联调用列表多个对象函数对返回属性包装器用方法修改MulticastSparseDelegatePropertyFMulticastSparseDelegateProperty多播稀疏存储多个对象函数对返回属性包装器API 与本文完全一致三点重要差异单播委托的“值”是普通 Lua 表访问Object.PropertyName会得到一个{Object UObject, FunctionName FName}的 Lua table可以直接整体赋值传入 table 绑定传入nil清空不存在Add/Remove/Clear方法。多播委托含稀疏变体的“值”是包装器对象访问Object.PropertyName返回的是 property wrapper userdata而非委托值本身对它的修改必须走方法调用。**稀疏多播委托FMulticastSparseDelegateProperty**在引擎中继承自FMulticastDelegateProperty虽然存储机制不同对象内仅占 1 字节标记实际调用列表放全局存储对“经常未绑定”的委托更省内存但所有 vtable 函数AddDelegate、RemoveDelegate、ClearDelegate完全相同因此对 Lua 完全透明API 与本文描述逐字一致。访问多播委托属性在 Lua 中访问任意 UObject 的成员属性走的是 UObject 的__index元方法UObject.ObjectMemberName或UObject[ObjectMemberName]两种写法等价。当目标属性是FMulticastDelegateProperty类型时UE4SS 会返回一个MulticastDelegateProperty包装器local PlayerController GetPlayerController() -- 访问多播委托属性返回 property wrapper local delegateProp PlayerController.OnPossessedPawnChanged -- 包装器上可直接调用 Add / Remove / Clear / GetBindings / Broadcast delegateProp:Add(MyCustomObject, FName(OnPawnChanged))对应底层逻辑位于 LuaUObject.cpp 的push_multicastdelegateproperty函数在Operation::Get分支下它直接把params.property强转为FMulticastDelegateProperty*并调用XMulticastDelegateProperty::construct(params)从而生成一个携带容器对象与属性指针的包装器。这也印证了文档中的说明返回的是包装器而不是委托的绑定值。值得注意多播委托的绑定值并非不可读——UE4SS 在GetNonTrivialLocal分支下会把当前所有绑定展开为一个 Lua 数组返回这与包装器的GetBindings()方法行为一致但在Operation::Set分支下会直接抛出错误Multicast delegate values are read-only. Use property methods: GetPropertyByName():Add/Remove/Clear也就是说多播委托不能像普通属性那样整体赋值所有修改必须通过包装器方法完成这是它与单播委托最本质的使用区别。方法详解包装器共提供 5 个方法其注册代码位于 LuaXDelegateProperty.cpp 的setup_member_functions中XMulticastSparseDelegateProperty的实现与之完全平行见同文件 L424 之后。Add(targetObject, functionName)将一个新的“对象 函数”绑定追加到多播委托的调用列表末尾。参数targetObjectUObject要绑定的对象functionNameFName|string要在目标对象上调用的函数名local prop PlayerController.OnPossessedPawnChanged -- 使用 FName 绑定 prop:Add(MyCustomObject, FName(OnPawnChanged)) -- 直接使用字符串绑定 prop:Add(MyCustomObject, OnPawnChanged)底层实现LuaXDelegateProperty.cppAdd的 C 回调先取出包装器中缓存的m_property与m_base随后对第二个参数做类型分支——若是 Luastring则先经to_wstring转宽字符并构造FNameFNAME_Add语义若是FNameuserdata 则直接取用接着创建FScriptDelegate并调用BindUFunction(target_object, fname)完成绑定最后通过property-ContainerPtrToValuePtrvoid(container)定位属性值内存调用引擎的property-AddDelegate(script_delegate, container, property_value)真正把绑定写入调用列表。Remove(targetObject, functionName)从调用列表中移除一个特定的绑定。注意它是按“对象 函数名”精确匹配删除的移除不存在的绑定不会报错。参数targetObjectUObject绑定所在的对象functionNameFName|string已绑定函数的名称-- 移除指定绑定 prop:Remove(MyCustomObject, FName(OnPawnChanged)) -- 或使用字符串 prop:Remove(MyCustomObject, OnPawnChanged)底层实现LuaXDelegateProperty.cpp与Add对称——同样构造FScriptDelegate并BindUFunction然后调用引擎的property-RemoveDelegate(script_delegate, container, property_value)。从源码实现方式可以推断参数解析逻辑与Add完全一致同样支持FName与字符串两种写法因此使用时务必传入与Add时完全相同的对象与函数名才能精准命中。Clear()清空调用列表中的所有绑定。-- 清空所有绑定 prop:Clear()底层实现LuaXDelegateProperty.cpp无参数直接通过property-ClearDelegate(container, property_value)清空整个调用列表。GetBindings()返回当前所有绑定的数组快照若没有任何绑定则返回nil。返回由 Lua table 组成的数组每个元素含ObjectUObject与FunctionNameFName两个字段无绑定时为nillocal bindings prop:GetBindings() if bindings then print(Delegate has, #bindings, bindings) for i, binding in ipairs(bindings) do print(string.format([%d] %s::%s, i, binding.Object:GetFullName(), binding.FunctionName:ToString())) end end底层实现LuaXDelegateProperty.cpp先通过GetMulticastDelegate拿到FMulticastScriptDelegate*若为空直接压入nil否则以delegate_value-Num()为长度遍历InvocationList为每个绑定构造一个{Object, FunctionName}表——Object由auto_construct_object包装为UObjectFunctionName由FName::construct包装为FName并按下标i 1存入数组Lua 下标从 1 开始。由于返回的FunctionName是 FName 类型打印时建议使用其ToString()方法GetComparisonIndex()则返回全局名称数组中的索引。Broadcast(...)触发多播委托按调用列表顺序调用所有已绑定函数并把 Lua 侧传入的参数转发给每个绑定函数。参数变参取决于委托签名函数的参数定义注意参数的类型与顺序必须与委托签名的期望严格一致-- 无参数委托 prop:Broadcast() -- 带参数委托例如 OnDamage(float Amount, AActor* Causer) prop:Broadcast(25.0, CauserActor)底层实现LuaXDelegateProperty.cpp是整个包装器中最复杂的一条链路也是理解 UE 反射参数传递的绝佳样例通过GetMulticastDelegate获取委托值若为空则直接返回无绑定、无需广播通过property-GetSignatureFunction()拿到委托的签名函数UFunction若为空则抛错Delegate signature function not found按签名函数的GetStructureSize()分配参数缓冲区并调用InitializeStruct初始化遍历签名函数的所有FProperty参数跳过非CPF_Parm标志或带CPF_ReturnParm返回值的参数对每个剩余参数取GetOffset_Internal()定位缓冲区偏移并利用全局的StaticState::m_property_value_pushers注册表按属性类型的FName比较索引查找把 Lua 栈上对应位置的参数按Operation::Set语义写入缓冲区调用delegate_value-ProcessMulticastDelegateUObject(params_buffer.data())触发所有绑定最后DestroyStruct释放参数缓冲区。这段实现也解释了两个使用要点参数按签名函数的参数顺序在 Lua 栈上从左到右排列Lua 侧传入的参数个数与类型必须匹配返回值参数会被自动跳过因此Broadcast总是返回空不能指望它带回函数返回值。实用组合示例把上面五个方法组合起来即可在 Lua 中完成一次完整的“绑定 → 校验 → 触发 → 解绑”生命周期local PlayerController GetPlayerController() local prop PlayerController.OnPossessedPawnChanged -- 1. 绑定自定义回调 prop:Add(MyCustomObject, OnPawnChanged) -- 2. 校验绑定是否生效 local bindings prop:GetBindings() if bindings then for _, b in ipairs(bindings) do print(bound:, b.Object:GetFullName(), ::, b.FunctionName:ToString()) end end -- 3. 触发广播依据该委托签名传参 prop:Broadcast() -- 4. 精确解绑后再次确认 prop:Remove(MyCustomObject, OnPawnChanged) print(after remove:, prop:GetBindings() ~ nil and #prop:GetBindings() or 0) -- 5. 或者直接全部清空 prop:Clear()若目标委托是稀疏多播变体如常见的Actor.OnEndPlay属于 MulticastSparseDelegateProperty以上代码无需任何改动即可复用。注意事项与限制综合文档与源码使用MulticastDelegateProperty时请牢记以下约束访问Object.PropertyName得到的是属性包装器不是绑定值想要读取当前绑定列表必须调用GetBindings()。容器对象被隐式存储在包装器内所有方法都不需要也不接受额外传入容器对象。函数名参数同时接受FName与 Lua 字符串FName走 userdata 直接取值字符串则会在 C 侧被转成FNameFNAME_Add语义二者语义等价。多播委托是“只读赋值”的直接Object.PropertyName value会在Operation::Set分支抛出错误必须改用Add/Remove/Clear方法LuaUObject.cpp。Broadcast参数必须匹配签名参数类型与顺序由委托的签名UFunction决定多传、少传或类型不符都会导致异常或未定义行为。GetBindings在无绑定时返回nil而非空表遍历前务必判空。绑定回调函数应是被目标对象反射暴露的 UFunctionBindUFunction按名查找普通 Lua 函数无法直接作为委托回调目标。如需进一步对比单播委托的值式读写table 赋值 /nil清空可阅读 DelegateProperty若想深入了解基类提供的能力GetFullName、IsA、ContainerPtrToValuePtr等请参阅 Property。赞分享游戏开发逆向工程【免费下载链接】RE-UE4SSInjectable LUA scripting system, SDK generator, live property editor and other dumping utilities for UE4/5 games项目地址https://gitcode.com/gh_mirrors/re/RE-UE4SS点击查看免费下载相关推荐UE4SS Lua API 详解DelegateProperty 单播委托属性的读写与绑定实战UE4SS Lua API 详解DelegateProperty 单播委托属性的读写与绑定实战 导读 DelegateProperty 是 UE4SS 为 U游戏开发逆向工程UE4SS UObject Lua 绑定实战指南属性读写、UFunction 调用与对象查询UE4SS UObject Lua 绑定实战指南属性读写、UFunction 调用与对象查询 导读 UObject 是虚幻引擎UE4/UE5中几乎所有游戏游戏开发逆向工程RE-UE4SS Lua 键位绑定实战RegisterKeyBind 完整指南RE UE4SS Lua 键位绑定实战RegisterKeyBind 完整指南 RegisterKeyBind 是 RE UE4SS 为 Lua 模组提供的基游戏开发逆向工程上一篇京东NutUI商品分类组件3分钟搞定电商分类页面下一篇Apache Traffic Control扩展开发指南插件系统与自定义模块实现创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑