You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为泛型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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 14:24:20