如何为泛型UnityEvent添加带动态参数的持久化事件监听器
问题根因
你之前的实现走不通是因为对UnityEvent持久化监听的API逻辑存在误解:
UnityEventTools.AddObjectPersistentListener是静态固定传参专用API,第三个参数需要传入硬编码的固定值,对应检视面板里手动选了固定传入对象/值的条目,和你要实现的动态传参条目完全无关,用这个API无法实现动态参数绑定。- 非泛型
UnityEvent和带参数的泛型事件(UnityEvent<T>/UnityEvent<T0,T1>等)是完全独立的类型,直接用as UnityEvent强转带参事件必然返回null,这是你之前强转失败的核心原因。 - 动态参数条目不需要手动指定传入值:它的执行逻辑是事件触发时,自动把
Invoke传入的实参传给绑定的回调,只要回调方法的签名和事件的泛型参数签名完全匹配即可。
正确实现方案
Unity内置的泛型UnityEvent最多支持4个动态参数,你需要根据目标事件的参数个数,匹配对应签名的UnityAction委托类型,调用对应参数数量的AddPersistentListener泛型重载,最后将监听条目标记为动态调用模式即可。
完整实现代码如下:
using UnityEditor; using UnityEditor.Events; using UnityEngine.Events; using System.Reflection; using System.Linq; public static class UnityEventEditorHelper { public static void SetUnityEvent<T>(T targetEvent, UnityEventEntry entry) where T : UnityEventBase { Type eventType = targetEvent.GetType(); Type[] genericArgs = eventType.GetGenericArguments(); int argCount = genericArgs.Length; switch (argCount) { case 0: // 处理无参事件 UnityAction voidAction = (UnityAction)Delegate.CreateDelegate( typeof(UnityAction), entry.component, entry.function ); UnityEventTools.AddVoidPersistentListener(targetEvent, voidAction); return; case 1: // 处理1个参数的动态事件 Type action1Type = typeof(UnityAction<>).MakeGenericType(genericArgs); Delegate action1 = Delegate.CreateDelegate(action1Type, entry.component, entry.function); MethodInfo addMethod1 = typeof(UnityEventTools).GetMethods(BindingFlags.Static | BindingFlags.Public) .First(m => m.Name == "AddPersistentListener" && m.GetGenericArguments().Length == 1) .MakeGenericMethod(genericArgs); addMethod1.Invoke(null, new object[] { targetEvent, action1 }); MarkLastListenerAsDynamic(targetEvent); return; case 2: // 处理2个参数的动态事件 Type action2Type = typeof(UnityAction<,>).MakeGenericType(genericArgs); Delegate action2 = Delegate.CreateDelegate(action2Type, entry.component, entry.function); MethodInfo addMethod2 = typeof(UnityEventTools).GetMethods(BindingFlags.Static | BindingFlags.Public) .First(m => m.Name == "AddPersistentListener" && m.GetGenericArguments().Length == 2) .MakeGenericMethod(genericArgs); addMethod2.Invoke(null, new object[] { targetEvent, action2 }); MarkLastListenerAsDynamic(targetEvent); return; case 3: // 处理3个参数的动态事件 Type action3Type = typeof(UnityAction<,,>).MakeGenericType(genericArgs); Delegate action3 = Delegate.CreateDelegate(action3Type, entry.component, entry.function); MethodInfo addMethod3 = typeof(UnityEventTools).GetMethods(BindingFlags.Static | BindingFlags.Public) .First(m => m.Name == "AddPersistentListener" && m.GetGenericArguments().Length == 3) .MakeGenericMethod(genericArgs); addMethod3.Invoke(null, new object[] { targetEvent, action3 }); MarkLastListenerAsDynamic(targetEvent); return; case 4: // 处理4个参数的动态事件 Type action4Type = typeof(UnityAction<,,,>).MakeGenericType(genericArgs); Delegate action4 = Delegate.CreateDelegate(action4Type, entry.component, entry.function); MethodInfo addMethod4 = typeof(UnityEventTools).GetMethods(BindingFlags.Static | BindingFlags.Public) .First(m => m.Name == "AddPersistentListener" && m.GetGenericArguments().Length == 4) .MakeGenericMethod(genericArgs); addMethod4.Invoke(null, new object[] { targetEvent, action4 }); MarkLastListenerAsDynamic(targetEvent); return; default: throw new NotSupportedException("UnityEvent最多支持4个动态参数"); } } /// <summary> /// 将最后添加的持久化监听标记为动态调用模式 /// </summary> private static void MarkLastListenerAsDynamic(UnityEventBase targetEvent) { // 通过反射获取UnityEvent内部的持久化调用列表 PropertyInfo persistentCallsProp = typeof(UnityEventBase).GetProperty("m_PersistentCalls", BindingFlags.Instance | BindingFlags.NonPublic); object persistentCalls = persistentCallsProp.GetValue(targetEvent); FieldInfo callsField = persistentCalls.GetType().GetField("m_Calls", BindingFlags.Instance | BindingFlags.NonPublic); System.Collections.IList callsList = callsField.GetValue(persistentCalls) as System.Collections.IList; // 拿到刚添加的最后一条监听 object lastCall = callsList[callsList.Count - 1]; // 设置调用模式为动态 FieldInfo modeField = lastCall.GetType().GetField("m_Mode", BindingFlags.Instance | BindingFlags.NonPublic); modeField.SetValue(lastCall, PersistentListenerMode.Dynamic); // 清空静态传参残留值 FieldInfo argsField = lastCall.GetType().GetField("m_Arguments", BindingFlags.Instance | BindingFlags.NonPublic); object args = argsField.GetValue(lastCall); FieldInfo objectArgField = args.GetType().GetField("m_ObjectArgument", BindingFlags.Instance | BindingFlags.NonPublic); objectArgField.SetValue(args, null); } }
注意事项
- 绑定的回调方法(即
entry.function指向的方法)的参数类型、顺序必须和目标UnityEvent的泛型参数完全匹配,否则会出现委托创建失败的报错。 - 上述代码依赖
UnityEventTools类,该类属于Editor专属API,只能放在Editor文件夹下的编辑器脚本中使用,无法在打包后的运行时代码中调用。 - 绑定完成后打开检视面板,就能看到对应事件条目显示为动态参数项,和手动在编辑器里添加的动态条目效果完全一致。
内容的提问来源于stack exchange,提问作者wesleywh
相关产品推荐
相关产品推荐

