Unity自定义SimpleCallback属性抽屉:List<object>参数类型丢失问题求助
解决Unity自定义Callback中参数Inspector绘制的类型丢失与修改追踪问题
问题背景
我为替代UnityEvent(对多参数方法、异步逻辑支持不足),基于ScriptableObject实现了SimpleCallback类,但编写自定义PropertyDrawer时遇到两个核心问题:
- 用
List<object>存储方法参数时,通过SerializedProperty访问会丢失类型信息,无法正确绘制对应字段 - 手动将参数转为
UnityEngine.Object生成SerializedObject后,无法追踪修改值(EditorGUI.PropertyField不返回值,依赖SerializedObject.ApplyModifiedProperties但无法关联回原List<object>)
需要找到能根据System.Type获取对应PropertyDrawer的方法,或更优实现方案。
可行解决方案
方案1:直接获取对应类型的PropertyDrawer(推荐)
UnityEditor内置EditorGUIUtility.GetDrawerForType方法,可直接获取指定System.Type对应的PropertyDrawer,复用Unity内置的字段绘制逻辑,同时手动处理值的更新与保存。
核心实现代码
using UnityEngine; using UnityEditor; using System; using System.Collections.Generic; using System.Reflection; using System.Linq; [CustomPropertyDrawer(typeof(SimpleCallback))] public class SimpleCallbackDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(position, label, property); // 绘制Target选择框 Rect targetRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); SerializedProperty targetProp = property.FindPropertyRelative("target"); EditorGUI.PropertyField(targetRect, targetProp); // 绘制Method选择框(实际项目需替换为自定义方法选择UI) Rect methodRect = new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight + 2, position.width, EditorGUIUtility.singleLineHeight); EditorGUI.LabelField(methodRect, "Method", property.FindPropertyRelative("method").stringValue); var callback = property.serializedObject.targetObject as SimpleCallback; if (callback.target == null || callback.method == null) { EditorGUI.EndProperty(); return; } // 过滤可绘制的参数 ParameterInfo[] parameters = callback.method.GetParameters(); List<ParameterInfo> drawableParams = parameters.Where(p => IsDrawableType(p.ParameterType)).ToList(); // 绘制参数字段 Rect paramRect = new Rect(position.x, methodRect.y + EditorGUIUtility.singleLineHeight + 4, position.width, EditorGUIUtility.singleLineHeight); for (int i = 0; i < drawableParams.Count; i++) { ParameterInfo param = drawableParams[i]; Type paramType = param.ParameterType; GUIContent paramLabel = new GUIContent($"{param.Name} ({paramType.Name})"); // 确保参数列表长度匹配 while (callback.arguments.Count <= i) { callback.arguments.Add(GetDefaultValue(paramType)); } object currentValue = callback.arguments[i]; // 获取对应类型的PropertyDrawer PropertyDrawer drawer = EditorGUIUtility.GetDrawerForType(paramType); if (drawer != null) { // 用临时对象包装值,生成SerializedProperty供Drawer使用 TempWrapper tempWrapper = new TempWrapper { Value = currentValue }; SerializedObject tempSO = new SerializedObject(tempWrapper); SerializedProperty tempProp = tempSO.FindProperty("Value"); // 绘制字段并同步修改 drawer.OnGUI(paramRect, tempProp, paramLabel); tempSO.ApplyModifiedProperties(); if (!Equals(tempWrapper.Value, currentValue)) { callback.arguments[i] = tempWrapper.Value; EditorUtility.SetDirty(callback); } } else { // 处理无Drawer的基础类型 object newValue = DrawBasicTypeField(paramRect, paramLabel, currentValue, paramType); if (!Equals(newValue, currentValue)) { callback.arguments[i] = newValue; EditorUtility.SetDirty(callback); } } paramRect.y += EditorGUIUtility.singleLineHeight + 2; } EditorGUI.EndProperty(); } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { var callback = property.serializedObject.targetObject as SimpleCallback; if (callback.target == null || callback.method == null) { return EditorGUIUtility.singleLineHeight * 2 + 2; } int paramCount = callback.method.GetParameters().Count(p => IsDrawableType(p.ParameterType)); return EditorGUIUtility.singleLineHeight * (2 + paramCount) + 2 * (paramCount + 1); } // 判断类型是否可绘制 private bool IsDrawableType(Type type) { return type.IsValueType || type == typeof(string) || typeof(UnityEngine.Object).IsAssignableFrom(type); } // 获取类型默认值 private object GetDefaultValue(Type type) { return type.IsValueType ? Activator.CreateInstance(type) : null; } // 绘制基础类型字段 private object DrawBasicTypeField(Rect rect, GUIContent label, object value, Type type) { if (type == typeof(int)) return EditorGUI.IntField(rect, label, (int)value); if (type == typeof(float)) return EditorGUI.FloatField(rect, label, (float)value); if (type == typeof(bool)) return EditorGUI.Toggle(rect, label, (bool)value); if (type == typeof(string)) return EditorGUI.TextField(rect, label, (string)value); if (typeof(UnityEngine.Object).IsAssignableFrom(type)) { return EditorGUI.ObjectField(rect, label, value as UnityEngine.Object, type, true); } return value; } // 临时包装类,用于生成SerializedProperty private class TempWrapper { public object Value; } }
方案2:优化参数存储结构
放弃List<object>,改用可序列化的参数容器类,将每个参数的类型信息与值分开存储(比如用string存储类型全名,用JsonUtility序列化/反序列化值)。这种方式能完全利用Unity的序列化系统,但需要额外处理类型转换逻辑,适合对序列化兼容性要求高的场景。
方案3:手动处理常见类型绘制
如果支持的参数类型有限,可直接通过switch-case处理基础类型(int、float、bool、string、UnityEngine.Object等),自行绘制对应字段并更新值。这种方式实现简单,但扩展性较差。
关键注意事项
- 用
EditorUtility.SetDirty标记SimpleCallback对象为脏,确保修改能被Unity保存 - 处理参数列表长度与方法参数数量的匹配逻辑,避免索引越界
- 对于异步方法,可在
SimpleCallback.Invoke中判断方法返回值是否为IEnumerator,通过MonoBehaviour.StartCoroutine执行协程
内容的提问来源于stack exchange,提问作者Nianyi Wang
相关产品推荐
相关产品推荐

