【问题标题】:ScriptableObject generic list makes all derived instances to base type when serializing in unityScriptableObject 泛型列表在统一序列化时使所有派生实例成为基类型
【发布时间】:2019-06-08 17:13:52
【问题描述】:

我正在尝试制作一个统计系统库,以便将来在尽可能多的游戏中使用,但统一的序列化阻碍了。

知道统一序列化有多糟糕,并且派生实例在序列化期间转换为基类型,我决定让基类派生自 ScriptableObject,但它不起作用。 在这一点上,我的代码变得有点混乱,所以我决定按照本书从头开始制作一个更简单的测试版本,如下所述:

https://forum.unity.com/threads/serialization-best-practices-megapost.155352/

但这也没有用。

基类:

[System.Serializable]
public class BaseClass : ScriptableObject
{
    [SerializeField]
    private string m_Name;

    [SerializeField]
    public string Name { get => m_Name; set => m_Name = value; }

    public static BaseClass NewInstance()
    {
        BaseClass b = CreateInstance<BaseClass>();
        b.Name = string.Empty;
        return b;
    }
    public static BaseClass NewInstance(string name)
    {
        BaseClass b = CreateInstance<BaseClass>();
        b.Name = name;
        return b;
    }
}

派生类:

[System.Serializable]
public class DerivedClass : BaseClass
{
    [SerializeField]
    private string m_Value;

    [SerializeField]
    public string Value { get => m_Value; set => m_Value = value; }

    public new static DerivedClass NewInstance()
    {
        DerivedClass d = CreateInstance<DerivedClass>();
        d.Name = string.Empty;
        d.Value = string.Empty;
        return d;
    }
    public static DerivedClass NewInstance(string name, string value)
    {
        DerivedClass d = CreateInstance<DerivedClass>();
        d.Name = name;
        d.Value = value;
        return d;
    }
}

最后是集合类:

[System.Serializable] [CreateAssetMenu(menuName = "CollectionA")]
public class CollectionA : ScriptableObject
{
    [SerializeField]
    private List<BaseClass> m_TestList;

    [SerializeField]
    public List<BaseClass> TestList { get => m_TestList; set => m_TestList = value; }

    public static CollectionA NewInstance()
    {
        CollectionA c = CreateInstance<CollectionA>();
        c.TestList = new List<BaseClass>();
        return c;
    }

    public List<T> GetAllWithType<T>()
    {
        try { return TestList.OfType<T>().ToList<T>(); }
        catch { return new List<T>(); }
    }
}

我正在创建实例并检查如下列表:

private void Update()
{
    if (Input.GetKeyDown("k"))
    {
        print("Derived stats:");
        List<DerivedClass> derived = collection.GetAllWithType<DerivedClass>();
        foreach (DerivedClass t in derived)
        { print(t.Name + " | " + t.Value); }
    }
    if (Input.GetKeyDown("p"))
    {
        DerivedClass d = DerivedClass.NewInstance("Hey", "Hello");
        collection.TestList.Add(d);
    }
}

我正在使用的自定义编辑器:

[CustomEditor(typeof(CollectionA))]
public class CollectionAEditor : Editor
{
    private CollectionA collection;
    private List<DerivedClass> derived;
    struct derivedValues
    {
        public string name, value;
    }

    derivedValues addDerived = new derivedValues();

    public override void OnInspectorGUI()
    {
        if (target is CollectionA)
            collection = (CollectionA)target;
        if (collection != null)
        {
            DrawInspector();
        }
    }

    private void DrawInspector()
    {
        derived = collection.GetAllWithType<DerivedClass>();

        // title
        EditorGUILayout.Space();
        GUILayout.Label("CLASS LIST", EditorStyles.largeLabel);

        // title
        EditorGUILayout.Space();
        GUILayout.Label("Derived classes:", EditorStyles.boldLabel);

        // layout labels
        GUILayout.BeginHorizontal();
        GUILayout.Label("Name", GUILayout.MinWidth(35));
        GUILayout.Label("Value", GUILayout.MinWidth(35));
        GUILayout.EndHorizontal();

        // derived classes list
        if (derived.Count <= 0)
            GUILayout.Label("Class list empty", EditorStyles.centeredGreyMiniLabel);
        else
        {
            foreach (DerivedClass x in derived)
            {
                GUILayout.BeginHorizontal();
                x.Name = GUILayout.TextField(x.Name, GUILayout.MinWidth(35));
                x.Value = GUILayout.TextField(x.Value, GUILayout.MinWidth(35));
                GUILayout.EndHorizontal();
            }
        }

        // add derived stat
        EditorGUILayout.Space();
        GUILayout.BeginHorizontal();
        GUILayout.Label("Values:", GUILayout.Width(50f));
        addDerived.name = GUILayout.TextField(addDerived.name, GUILayout.MinWidth(35));
        addDerived.value = GUILayout.TextField(addDerived.value, GUILayout.MinWidth(35));
        GUILayout.EndHorizontal();
        if (GUILayout.Button("Add derived class"))
        {
            collection.TestList.Add(
                DerivedClass.NewInstance(addDerived.name, addDerived.value));
            addDerived = new derivedValues();
        }

        // default stats title
        EditorGUILayout.Space();
        GUILayout.BeginHorizontal();
        GUILayout.Label("All classes as default:", EditorStyles.boldLabel);
        if (collection.TestList == null) Debug.Log("NULL");
        GUILayout.Label(collection.TestList.Count.ToString());
        GUILayout.EndHorizontal();

        EditorGUILayout.Space();
        if (GUILayout.Button("Delete all instances"))
            collection.TestList.Clear();

        GUILayout.BeginHorizontal();
        GUILayout.Label("Name", GUILayout.MinWidth(35));
        GUILayout.EndHorizontal();

        // all classes list
        if (collection.TestList.Count <= 0)
            GUILayout.Label("Class list empty", EditorStyles.centeredGreyMiniLabel);
        else
        {
            foreach (BaseClass x in collection.TestList)
            {
                GUILayout.BeginHorizontal();
                x.name = GUILayout.TextField(x.name, GUILayout.MinWidth(35));
                GUILayout.EndHorizontal();
            }
        }
    }
}

如果我按下 Play 并将一个新的派生实例添加到列表中,它会被正确识别,直到我再次按下 Play 时,派生实例被移动到基本类型。

我尝试了很多东西,但似乎都没有奏效,所以感谢您尝试帮助我。

【问题讨论】:

  • 您能否添加您实际生成并添加新实例的代码部分?
  • 我不完全理解这个问题。我很快使用了您的三个脚本,为每个脚本创建了一个实例,并将它们添加到 TestList 字段中。不管我多久启动一次播放模式,我总是会看到两个条目:NewBaseClass (BaseClass)NewDerivedClass (DerivedClass)
  • 我用您要求的代码更新了帖子。我不知道是否值得一提,但我还有一个CollectionA 的自定义检查器。我使用它只是为了帮助我更好地查看更改并修改 NameValue 等值。
  • 那么在这种情况下,Inspector 当然也可以是 iisue 的一部分
  • 我已将其添加到帖子中

标签: c# unity3d


【解决方案1】:

好的,我想通了。我没有从 2 个类中创建资产。作为资产的 ScriptableObjects 无法引用非资产/预制实例而不会明显丢失引用,因此我的 BaseClass 列表中的数据丢失了。

结论:为您制作资产的 ScriptableObject 字段制作 ScriptableObject 包装器是不够的,但您还需要从字段中制作资产,这样引用才不会丢失。

@derHugo 感谢您抽出宝贵时间。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-09-23
    • 1970-01-01
    • 1970-01-01
    • 2011-05-07
    • 1970-01-01
    • 2019-10-25
    • 2014-03-22
    • 2012-09-08
    相关资源
    最近更新 更多