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

在Scriptable Object中声明动态枚举类型变量的技术问询

适配所有枚举类型的Scriptable Variable实现方案

直接在Unity里动态声明让Inspector自动识别的枚举字段是行不通的——Inspector依赖编译期的字段元数据,没办法动态生成可编辑的字段。不过用反射+自定义Editor可以完全实现你要的功能,核心思路是用字符串存储枚举类型,用int存储枚举值,再通过自定义Editor层做类型转换和界面适配。

1. 核心ScriptableObject类

这个类负责存储枚举类型标识、初始值和当前值,通过属性封装反射转换逻辑:

using UnityEngine;

[CreateAssetMenu(fileName = "New Enum Variable", menuName = "Variables/Enum Variable")]
public class ScriptableVariable_Enum : ScriptableObject
{
    [SerializeField] private string enumTypeName;
    [SerializeField] private int initialValue;
    [SerializeField] private int currentValue;

    // 获取当前选中的枚举类型
    public System.Type EnumType => !string.IsNullOrEmpty(enumTypeName) ? System.Type.GetType(enumTypeName) : null;
    
    // 封装初始值的枚举转换
    public System.Enum InitialValue => EnumType != null ? (System.Enum)System.Enum.ToObject(EnumType, initialValue) : null;
    
    // 封装当前值的枚举转换与赋值
    public System.Enum CurrentValue
    {
        get => EnumType != null ? (System.Enum)System.Enum.ToObject(EnumType, currentValue) : null;
        set
        {
            if (EnumType != null && value.GetType() == EnumType)
            {
                currentValue = (int)(object)value;
            }
        }
    }

    private void OnEnable()
    {
        // Play模式启动时重置当前值为初始值
        if (Application.isPlaying)
        {
            currentValue = initialValue;
        }
    }
}

2. 自定义Editor实现Inspector适配

通过自定义Editor,用反射遍历所有枚举类型生成选择下拉框,再把存储的int值转换成枚举下拉框展示给用户:

using UnityEditor;
using UnityEngine;
using System.Linq;

[CustomEditor(typeof(ScriptableVariable_Enum))]
public class ScriptableVariable_EnumEditor : Editor
{
    private ScriptableVariable_Enum targetVar;
    private System.Type selectedEnumType;

    private void OnEnable()
    {
        targetVar = (ScriptableVariable_Enum)target;
        selectedEnumType = targetVar.EnumType;
    }

    public override void OnInspectorGUI()
    {
        // 枚举类型选择区
        EditorGUILayout.LabelField("枚举类型", EditorStyles.boldLabel);
        var allEnums = System.AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(asm => asm.GetTypes())
            .Where(t => t.IsEnum)
            .ToList();

        string[] enumOptions = allEnums.Select(t => t.FullName).ToArray();
        int currentIndex = selectedEnumType != null ? enumOptions.ToList().IndexOf(selectedEnumType.FullName) : -1;
        int newIndex = EditorGUILayout.Popup(currentIndex, enumOptions);

        // 切换枚举类型时重置默认值
        if (newIndex != currentIndex && newIndex >= 0)
        {
            selectedEnumType = allEnums[newIndex];
            targetVar.enumTypeName = selectedEnumType.FullName;
            var firstEnumValue = System.Enum.GetValues(selectedEnumType).GetValue(0);
            targetVar.initialValue = (int)(object)firstEnumValue;
            targetVar.currentValue = (System.Enum)firstEnumValue;
            EditorUtility.SetDirty(targetVar);
        }

        // 已选择枚举类型时,显示初始值和当前值的枚举下拉框
        if (selectedEnumType != null)
        {
            EditorGUILayout.Space();
            EditorGUILayout.LabelField("初始值", EditorStyles.boldLabel);
            var currentInitial = System.Enum.ToObject(selectedEnumType, targetVar.initialValue);
            var newInitial = EditorGUILayout.EnumPopup(currentInitial);
            if (!newInitial.Equals(currentInitial))
            {
                targetVar.initialValue = (int)(object)newInitial;
                EditorUtility.SetDirty(targetVar);
            }

            EditorGUILayout.Space();
            EditorGUILayout.LabelField("当前值", EditorStyles.boldLabel);
            var currentValue = System.Enum.ToObject(selectedEnumType, targetVar.currentValue);
            var newValue = EditorGUILayout.EnumPopup(currentValue);
            if (!newValue.Equals(currentValue))
            {
                targetVar.currentValue = (System.Enum)newValue;
                EditorUtility.SetDirty(targetVar);
            }
        }

        if (GUI.changed)
        {
            serializedObject.ApplyModifiedProperties();
        }
    }
}

关键注意事项

  • 类型稳定性:当前用FullName存储枚举类型,若枚举重命名或移动程序集,会导致类型无法识别,可改用AssemblyQualifiedName存储更完整的类型标识(代价是字符串更长)。
  • 运行时校验:给CurrentValue赋值时,要确保传入的枚举类型与选中的类型一致,否则赋值会被忽略。
  • 性能考量:Editor里遍历所有程序集的枚举类型只在Inspector绘制时执行,不会影响运行时性能。

内容的提问来源于stack exchange,提问作者Christoph

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 13:58:14