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

Unity:如何在ScriptableObject中使用泛型并实现高度泛化?

解答你的Unity ScriptableObject泛序列化问题

问题1:为什么无法直接使用List<PlayerPrefsDefault<int>>并在Inspector暴露?

这本质上是Unity内置序列化系统的限制:Unity的Inspector无法直接处理自定义泛型类的实例,哪怕你指定了具体的类型参数(比如int)。Unity的序列化逻辑需要明确的、非泛型的类型定义才能生成对应的Inspector绘制界面——它没办法自动为PlayerPrefsDefault<int>这种泛型特化类型创建编辑器控件。

如果你想尝试接近这种写法,有两种可行方案:

  • 保留当前子类方案(最简便):就像你现在做的那样,为每种需要的类型创建非泛型子类(比如PlayerPrefsDefaultInt继承PlayerPrefsDefault<int>),这是Unity官方推荐的兼容做法,不需要额外编写编辑器代码。
  • 自定义PropertyDrawer(进阶):如果你不想创建大量子类,可以为PlayerPrefsDefault<T>编写自定义的PropertyDrawer,手动处理序列化和Inspector绘制逻辑。不过这种方式需要熟悉Unity的SerializedProperty API,实现成本较高。

问题2:如何实现支持任意类型的PlayerPrefsDefault类?

你想要的“任意类型存储+Inspector适配”需求,核心难点在于Unity无法直接序列化object类型。这里有两种成熟的解决方案:

方案A:利用SerializeReference实现多态序列化(Unity 2019.3+)

这是最优雅的方式,通过多态基类+SerializeReference属性,让Unity自动处理不同类型的序列化和Inspector显示:

// 基类定义
[Serializable]
public abstract class PlayerPrefsValueBase
{
    public abstract object GetValue();
    public abstract void SetValue(object value);
    public abstract System.Type GetValueType();
}

// 每种类型的具体实现
[Serializable]
public class PlayerPrefsIntValue : PlayerPrefsValueBase
{
    public int value;
    public override object GetValue() => value;
    public override void SetValue(object val) => value = (int)val;
    public override System.Type GetValueType() => typeof(int);
}

[Serializable]
public class PlayerPrefsFloatValue : PlayerPrefsValueBase
{
    public float value;
    public override object GetValue() => value;
    public override void SetValue(object val) => value = (float)val;
    public override System.Type GetValueType() => typeof(float);
}

// 同理可以扩展bool、string、Vector3等类型

// 最终的PlayerPrefsDefault类
[Serializable]
public class PlayerPrefsDefault
{
    public string key;
    [SerializeReference]
    public PlayerPrefsValueBase value;
}

在Inspector中,value字段会显示一个下拉菜单,让你选择具体的类型(比如PlayerPrefsIntValue),然后就能直接编辑对应类型的值。你可以通过value.GetValueType()获取类型,GetValue()获取实际值,完美适配你的需求。

方案B:手动序列化+自定义Editor(兼容旧版本)

如果你的项目用的是Unity 2019.3之前的版本,可以通过ISerializationCallbackReceiver手动处理不同类型的序列化,再配合自定义Editor实现Inspector适配:

[Serializable]
public class PlayerPrefsDefault : ISerializationCallbackReceiver
{
    public System.Type type;
    public string key;

    // 隐藏的序列化字段,对应不同类型
    [HideInInspector] public int intValue;
    [HideInInspector] public float floatValue;
    [HideInInspector] public bool boolValue;
    [HideInInspector] public string stringValue;
    [HideInInspector] public Vector3 vector3Value;

    // 运行时使用的object值
    [NonSerialized] public object value;

    public void OnBeforeSerialize()
    {
        if (value == null || type == null) return;
        
        // 根据类型把value同步到对应的序列化字段
        switch (System.Type.GetTypeCode(type))
        {
            case System.TypeCode.Int32:
                intValue = (int)value;
                break;
            case System.TypeCode.Single:
                floatValue = (float)value;
                break;
            case System.TypeCode.Boolean:
                boolValue = (bool)value;
                break;
            case System.TypeCode.String:
                stringValue = (string)value;
                break;
            default:
                if (type == typeof(UnityEngine.Vector3))
                {
                    vector3Value = (UnityEngine.Vector3)value;
                }
                break;
        }
    }

    public void OnAfterDeserialize()
    {
        if (type == null) return;
        
        // 根据类型把序列化字段的值恢复到value
        switch (System.Type.GetTypeCode(type))
        {
            case System.TypeCode.Int32:
                value = intValue;
                break;
            case System.TypeCode.Single:
                value = floatValue;
                break;
            case System.TypeCode.Boolean:
                value = boolValue;
                break;
            case System.TypeCode.String:
                value = stringValue;
                break;
            default:
                if (type == typeof(UnityEngine.Vector3))
                {
                    value = vector3Value;
                }
                break;
        }
    }
}

然后你需要编写自定义Editor类,在Inspector中先让用户选择type,再根据类型绘制对应的字段(比如选int就显示intValue),并实时同步到value变量。这种方式不需要创建太多子类,但需要手动维护每种类型的序列化逻辑。

如何让ScriptableObject尽可能泛化?

结合上面的方案,你可以把ScriptableObject改成单一列表存储所有类型的默认值,彻底摆脱“为每种类型创建单独列表”的限制:

[CreateAssetMenu(fileName = "PlayerPrefsDefaults", menuName = "Base/PlayerPrefsDefaults", order = 1)]
public class PlayerPrefsDefaults : ScriptableObject
{
    // 一个列表就能存储所有类型的PlayerPrefs默认值
    public List<PlayerPrefsDefault> playerPrefsDefaults = new List<PlayerPrefsDefault>();
}

不管用SerializeReference的多态方案还是手动序列化方案,这个ScriptableObject都能灵活存储任意类型的默认值,完全满足泛化需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 09:05:39