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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 16:52:14