Unity中如何在Inspector选择枚举类型,避免编写多份序列化类?
Unity 无需多序列化类实现多枚举动态切换
核心思路
不用创建多个序列化类,通过自定义PropertyDrawer+反射实现:先在Inspector里选择要使用的枚举类型,再选择对应枚举的具体值,全程复用同一个字段容器。
实现步骤及代码
1. 定义自定义标记属性
用来标记需要动态切换枚举的字段,同时指定可选的枚举类型列表:
using UnityEngine; public class DynamicEnumAttribute : PropertyAttribute { public System.Type[] AllowedEnums; public DynamicEnumAttribute(params System.Type[] allowedEnums) { AllowedEnums = allowedEnums; // 校验传入的都是枚举类型 foreach (var type in allowedEnums) { if (!type.IsEnum) Debug.LogError($"{type.Name} 不是枚举类型!"); } } }
2. 定义可序列化的枚举容器类
用来存储选中的枚举类型名称和对应值(Unity无法直接序列化System.Type,所以存类型的全名):
using System; using UnityEngine; [Serializable] public class DynamicEnumContainer { [SerializeField] private string enumTypeName; [SerializeField] private int enumValue; // 获取当前选中的枚举类型 public Type GetEnumType() { return string.IsNullOrEmpty(enumTypeName) ? null : Type.GetType(enumTypeName); } // 设置选中的枚举类型 public void SetEnumType(Type enumType) { if (enumType?.IsEnum ?? false) { enumTypeName = enumType.AssemblyQualifiedName; // 默认选中第一个枚举值 enumValue = 0; } else { enumTypeName = null; enumValue = 0; } } // 获取当前选中的枚举值(转为object方便后续强转) public object GetEnumValue() { var enumType = GetEnumType(); return enumType != null ? Enum.ToObject(enumType, enumValue) : null; } // 设置枚举值 public void SetEnumValue(object enumValueObj) { if (enumValueObj != null && enumValueObj.GetType().IsEnum) { enumValue = (int)enumValueObj; enumTypeName = enumValueObj.GetType().AssemblyQualifiedName; } } }
3. 实现自定义PropertyDrawer
处理Inspector的绘制逻辑,实现先选枚举类型再选值的交互:
using UnityEditor; using UnityEngine; [CustomPropertyDrawer(typeof(DynamicEnumAttribute))] public class DynamicEnumDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { var dynamicEnumAttr = attribute as DynamicEnumAttribute; if (dynamicEnumAttr == null || dynamicEnumAttr.AllowedEnums.Length == 0) return; // 获取容器内的两个序列化字段 var enumTypeNameProp = property.FindPropertyRelative("enumTypeName"); var enumValueProp = property.FindPropertyRelative("enumValue"); // 分割绘制区域:上半部分选枚举类型,下半部分选枚举值 Rect typeRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); Rect valueRect = new Rect(position.x, position.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing, position.width, EditorGUIUtility.singleLineHeight); // 绘制枚举类型选择框 var currentEnumType = string.IsNullOrEmpty(enumTypeNameProp.stringValue) ? null : System.Type.GetType(enumTypeNameProp.stringValue); int selectedTypeIndex = -1; string[] enumNames = new string[dynamicEnumAttr.AllowedEnums.Length]; for (int i = 0; i < dynamicEnumAttr.AllowedEnums.Length; i++) { enumNames[i] = dynamicEnumAttr.AllowedEnums[i].Name; if (dynamicEnumAttr.AllowedEnums[i] == currentEnumType) selectedTypeIndex = i; } int newSelectedTypeIndex = EditorGUI.Popup(typeRect, label.text + " (枚举类型)", selectedTypeIndex, enumNames); if (newSelectedTypeIndex != selectedTypeIndex) { var newEnumType = dynamicEnumAttr.AllowedEnums[newSelectedTypeIndex]; enumTypeNameProp.stringValue = newEnumType.AssemblyQualifiedName; enumValueProp.intValue = 0; // 默认选中第一个值 } // 如果选中了枚举类型,绘制枚举值选择框 if (!string.IsNullOrEmpty(enumTypeNameProp.stringValue)) { var enumType = System.Type.GetType(enumTypeNameProp.stringValue); if (enumType != null && enumType.IsEnum) { var enumValues = System.Enum.GetValues(enumType); string[] enumValueNames = System.Enum.GetNames(enumType); int currentValueIndex = System.Array.IndexOf(enumValues, System.Enum.ToObject(enumType, enumValueProp.intValue)); int newValueIndex = EditorGUI.Popup(valueRect, "枚举值", currentValueIndex, enumValueNames); if (newValueIndex != currentValueIndex) { enumValueProp.intValue = (int)enumValues.GetValue(newValueIndex); } } } } // 调整Inspector中字段的高度 public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { return EditorGUIUtility.singleLineHeight * 2 + EditorGUIUtility.standardVerticalSpacing; } }
4. 在ScriptableObject中使用
假设你已经有TakeOverNeutralTurf和TakeOverEnemyTurf两个枚举,直接在ScriptableObject里用标记了自定义属性的容器字段:
using UnityEngine; // 示例枚举(可以放在静态类里) public static class MissionEnums { public enum TakeOverNeutralTurf { ClearNeutral, OccupyNeutral, HoldNeutral30s } public enum TakeOverEnemyTurf { BreakEnemyDefense, OccupyEnemyTurf, CounterEnemyReinforce } } [CreateAssetMenu(fileName = "MissionData", menuName = "ScriptableObjects/MissionData")] public class MissionData : ScriptableObject { // 指定可选的枚举类型,这里就是你拆分后的两个枚举 [DynamicEnum(typeof(MissionEnums.TakeOverNeutralTurf), typeof(MissionEnums.TakeOverEnemyTurf))] public DynamicEnumContainer SelectedMissionType; // 测试用的方法,获取选中的枚举值 public void PrintSelectedValue() { var enumValue = SelectedMissionType.GetEnumValue(); if (enumValue != null) { Debug.Log($"选中的类型:{enumValue.GetType().Name},值:{enumValue}"); } } }
注意事项
- 枚举如果放在静态类里,直接用
typeof(静态类名.枚举名)即可,代码里已经示例了这种情况 - 如果后续新增枚举类型,只需要在
DynamicEnum属性的参数里添加对应的typeof(新枚举)即可,无需修改其他代码 - 序列化的是类型全名和枚举的int值,兼容性较好,不会因为枚举顺序调整导致数据出错(只要枚举值的int值不变)
内容的提问来源于stack exchange,提问作者Absinthe
相关产品推荐
相关产品推荐

