Unity限制UnityEvent单监听器或指定调用内部方法的实现方案
问题描述
我正在开发一款游戏通用资源,用来避免重复实现游戏里的常规功能。在ColorFollower脚本中定义了包含Custom1到Custom10的CustomComponentType枚举,ColorManager脚本持有颜色属性,并声明了public UnityEvent<ColorFollower>[] CustomFunctions;数组。需求是根据选中的枚举值,调用用户自定义脚本里的对应方法。
目前用UnityEvent数组做临时方案,通过枚举转索引调用对应的UnityEvent,但每个UnityEvent允许添加多个方法监听器,不符合我仅需单个方法引用的需求。需要解决以下任一问题:
- 限制数组中每个UnityEvent仅能添加一个方法引用,并移除Inspector面板中每个元素的“+”“-”按钮;
- 实现直接调用UnityEvent中的指定单个方法。
现有代码示例
ColorFollower脚本枚举代码
public enum CustomComponentType { Custom1, Custom2, Custom3, Custom4, Custom5, Custom6, Custom7, Custom8, Custom9, Custom10 } public CustomComponentType customComponentType = CustomComponentType.Custom1;
当前调用逻辑代码
if (colorManager.CustomFunctions.Length > (int) customComponentType) { UnityEvent<ColorFollower> customFunction = colorManager.CustomFunctions[(int) customComponentType]; customFunction?.Invoke((ColorFollower) this); }
解决方案
方案1:自定义单方法事件类型并定制Inspector
通过创建自定义事件类+自定义PropertyDrawer,强制每个事件只能绑定一个方法,并隐藏Inspector中的加减按钮。
步骤1:创建自定义单参数事件类
using UnityEngine.Events; [System.Serializable] public class SingleUnityEvent_ColorFollower : UnityEvent<ColorFollower> { }
步骤2:修改ColorManager中的数组声明
把原有的UnityEvent数组替换成自定义事件类型:
public SingleUnityEvent_ColorFollower[] CustomFunctions;
步骤3:编写自定义PropertyDrawer(需放在Editor文件夹下)
这个Drawer会强制事件只能保留一个方法绑定,并且直接绘制单方法选择框,完全隐藏加减按钮:
using UnityEditor; using UnityEngine; using UnityEngine.Events; [CustomPropertyDrawer(typeof(SingleUnityEvent_ColorFollower))] public class SingleUnityEventDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(position, label, property); // 获取事件的持久化调用列表 SerializedProperty persistentCalls = property.FindPropertyRelative("m_PersistentCalls"); SerializedProperty callsList = persistentCalls.FindPropertyRelative("m_Calls"); // 确保列表有且仅有一个调用项 if (callsList.arraySize == 0) callsList.arraySize = 1; else if (callsList.arraySize > 1) callsList.arraySize = 1; // 获取唯一的调用项,绘制目标对象和方法选择框 SerializedProperty call = callsList.GetArrayElementAtIndex(0); Rect methodRect = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label); EditorGUI.PropertyField(methodRect, call.FindPropertyRelative("m_Target"), GUIContent.none); EditorGUI.PropertyField(methodRect, call.FindPropertyRelative("m_MethodName"), GUIContent.none); EditorGUI.EndProperty(); } }
方案2:直接调用UnityEvent中的单个方法
如果不想修改Inspector,可通过代码直接提取UnityEvent中的第一个方法并调用,分为两种情况:
情况1:仅调用Inspector中绑定的持久化方法
if (colorManager.CustomFunctions.Length > (int)customComponentType) { UnityEvent<ColorFollower> customFunction = colorManager.CustomFunctions[(int)customComponentType]; if (customFunction != null) { var persistentCalls = customFunction.GetPersistentCalls(); if (persistentCalls.Count > 0) { var firstCall = persistentCalls[0]; if (firstCall.target != null && !string.IsNullOrEmpty(firstCall.methodName)) { // 通过反射调用第一个绑定的方法 firstCall.target.GetType().InvokeMethod(firstCall.methodName, new object[] { this }); } } } }
情况2:调用所有注册方法(包括代码动态AddListener的方法)
此方法依赖Unity私有字段,版本更新可能失效,需谨慎使用:
using System.Reflection; // ... if (colorManager.CustomFunctions.Length > (int)customComponentType) { UnityEvent<ColorFollower> customFunction = colorManager.CustomFunctions[(int)customComponentType]; if (customFunction != null) { // 反射获取UnityEvent内部的调用列表 FieldInfo callsField = typeof(UnityEventBase).GetField("m_Calls", BindingFlags.Instance | BindingFlags.NonPublic); if (callsField != null) { var callsList = callsField.GetValue(customFunction) as System.Collections.IList; if (callsList != null && callsList.Count > 0) { var firstCall = callsList[0]; // 反射获取调用项的目标对象和方法 FieldInfo targetField = firstCall.GetType().GetField("m_Target", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo methodField = firstCall.GetType().GetField("m_Method", BindingFlags.Instance | BindingFlags.NonPublic); if (targetField != null && methodField != null) { Object target = targetField.GetValue(firstCall) as Object; MethodInfo method = methodField.GetValue(firstCall) as MethodInfo; if (target != null && method != null) { method.Invoke(target, new object[] { this }); } } } } } }
内容的提问来源于stack exchange,提问作者Jaimin Vashi
相关产品推荐
相关产品推荐

