我需要一个自定义检查员的字典

问题描述 投票:0回答:2

所以我需要一个字典结构来保存自定义编辑器中的东西。我知道不允许使用词典,因此我尝试创建自己的类型,发现不允许使用泛型。但是List是。所以从这开始我创建了一个扩展List的类,并添加了一个小功能,允许我将这个对象视为一个Dictionary。这是代码:

public class CustomDictionary : List<CustomDictionaryItem>
 {
     public void Add (string key, UnityEngine.Object value)
     {
         this.Add (new CustomDictionaryItem (key, value));
     }
     public bool ContainsKey (string key)
     {
         return this.Any (sdi => sdi.Key == key);
     }

     public bool Remove (string key)
     {
         return this.RemoveAll (sdi => sdi.Key == key) != 0;
     }
     public UnityEngine.Object this [string key] {
         get {
             if (ContainsKey (key))
                 return (UnityEngine.Object)this.First (sdi => sdi.Key == key).Value;
             return null;
         }
         set {
             if (ContainsKey (key)) {
                 CustomDictionaryItem item = this.First (sdi => sdi.Key == key);
                 item.Value = value;
             } else
                 Add (key, value);
         }
     }
     public List<string> Keys {
         get {
             return this.Select (sdi => sdi.Key).ToList ();
         }
     }
     public List<UnityEngine.Object> Values {
         get {
             return this.Select (sdi => (UnityEngine.Object)sdi.Value).ToList ();
         }
     }
 }
 [Serializable]
 public class CustomDictionaryItem
 {
     [SerializeField]
     private string m_key;
     public string Key{ get { return m_key; } set { m_key = value; } }
     [SerializeField]
     private UnityEngine.Object m_value;
     public UnityEngine.Object Value{ get { return m_value; } set { m_value = value; } }
     public CustomDictionaryItem (string key, UnityEngine.Object value)
     {
         m_key = key;
         m_value = value;
     }
 }

我希望它是诚实的(而不是UnityEngine.Object)键入,但我试图删除泛型,希望它可以工作。它没有。游戏开始后不会保存这些值。

PS:这个“公开列表m_customDictionary;”工作正常,但它没有我想要的功能。有任何想法吗?谢谢!

c# dictionary serialization unity3d
2个回答
1
投票

看起来Unity有一个问题,认识到这个类是从List派生的。作为一种解决方法,我建议将List作为成员而不是派生。如果您希望能够遍历CustomDictionary,则可以添加简单地返回List GetEnumerator()的GetEnumerator()方法。

[Serializable]
public class CustomDictionary
{
    [SerializeField]
    private List<CustomDictionaryItem> l;

    public void Add (string key, UnityEngine.Object value)
    {
        l.Add (new CustomDictionaryItem (key, value));
    }
    public bool ContainsKey (string key)
    {
        return l.Any (sdi => sdi.Key == key);
    }

    public bool Remove (string key)
    {
        return l.RemoveAll (sdi => sdi.Key == key) != 0;
    }
    public UnityEngine.Object this [string key] {
        get {
            if (ContainsKey (key))
                return (UnityEngine.Object)l.First (sdi => sdi.Key == key).Value;
            return null;
        }
        set {
            if (ContainsKey (key)) {
                CustomDictionaryItem item = l.First (sdi => sdi.Key == key);
                item.Value = value;
            } else
                Add (key, value);
        }
    }
    public List<string> Keys {
        get {
            return l.Select (sdi => sdi.Key).ToList ();
        }
    }
    public List<UnityEngine.Object> Values {
        get {
            return l.Select (sdi => (UnityEngine.Object)sdi.Value).ToList ();
        }
    }

    public List<CustomDictionaryItem>.Enumerator GetEnumerator() {
        return l.GetEnumerator();
    }
}

编辑:经过深思熟虑之后,有一种方法可以使这个字典变得通用。这很难看,我甚至为编写这样的代码感到有点惭愧,但它会起作用。

这是诀窍:Unity不会在编辑器中呈现泛型类。但是从派生类派生的类没有问题。因此,我们的想法是创建一个从GenericDictionary派生的虚拟非泛型字典。同样关于GenericDictionaryItem,我们将创建非泛型虚拟,简单地从通用虚拟导出。

这是基本的想法。以下是陷阱:

如果我们可以将从GenericDictionaryItem派生的虚拟定义为GenericDictionary子类,那将是理想的。事实证明我们做不到。 Unity不会在编辑器中显示这样的类。所以我在这里使用的解决方案是手动创建一个必须从Entry派生的GenericDictionaryItem类型,并将其作为第三个GenericDictionary泛型参数传递,除了Key和Value。

接下来,在C#中我们不能使用泛型类型Entry非默认构造函数。所以我用于此的解决方案是将SetKV(key, value)方法添加到GenericDictionaryItem,可以用作构造函数。 Entry将仅使用默认构造函数。也许更优雅的解决方案是使用一些工厂对象,我只是快速做到了。

最后,注意你不能比较通用密钥与==,必须使用Equals(Object)。重要的是要记住,因为参考类型的equals默认实现是参考比较。因此它可以与值类型(int,Color,Vector3 ...)以及字符串一起使用,但如果您决定使用Equals作为键,则必须为GameObject等引用键类型实现GameObject。 C#Dictionary为这种情况提供IEqualityComparer,在这里我没有提供它,但如果你真的想要你可以做到。

最后,代码是:

[Serializable]
public class GenericDictionaryItem<K, V>
{
    [SerializeField]
    private K m_key;
    public K Key{ get { return m_key; } set { m_key = value; } }
    [SerializeField]
    private V m_value;
    public V Value{ get { return m_value; } set { m_value = value; } }
    public GenericDictionaryItem (K key, V value)
    {
        m_key = key;
        m_value = value;
    }

    public GenericDictionaryItem () : this (default(K), default(V)) {
    }

    // In C# we cannot use non default constructor with types passed as generic argument.
    // So as a workaround we will use this function instead of constructor.
    internal void SetKV(K key, V value) {
        m_key = key;
        m_value = value;
    }
}
[Serializable]
// Generic arguments: K - key type, V - value type, Entry - must be a class derived from
// GenericDictionaryItem<K, V> and implementing a default constructor.
// K must implement Equals method!
public class GenericDictionary<K, V, Entry> where Entry : GenericDictionaryItem<K, V>, new()
{

    [SerializeField]
    private List<Entry > l;

    public void Add (K key, V value)
    {
        // Cannot use non default constructor with generic type.
        // This is a workaround
        var e = new Entry();
        e.SetKV(key, value);

        l.Add (e);
    }
    public bool ContainsKey (K key)
    {
        return l.Any (sdi => sdi.Key.Equals(key));
    }

    public bool Remove (K key)
    {
        return l.RemoveAll (sdi => sdi.Key.Equals(key)) != 0;
    }
    public V this [K key] {
        get {
            if (ContainsKey (key))
                return (V)l.First (sdi => sdi.Key.Equals(key)).Value;
            return default(V);
        }
        set {
            if (ContainsKey (key)) {
                Entry item = l.First (sdi => sdi.Key.Equals(key));
                item.Value = value;
            } else
                Add (key, value);
        }
    }
    public List<K> Keys {
        get {
            return l.Select (sdi => sdi.Key).ToList ();
        }
    }
    public List<V> Values {
        get {
            return l.Select (sdi => (V)sdi.Value).ToList ();
        }
    }

    public List<Entry>.Enumerator GetEnumerator() {
        return l.GetEnumerator();
    }
}

// Test class
public class DictionaryTest : MonoBehaviour {

    // Create dummy entry type
    [Serializable]
    public class Entry : GenericDictionaryItem<string, UnityEngine.Object> {
    }

    // Create dummy dictionary type
    // Pay attention - we pass 3 arguments key type, value type and entry type
    [Serializable]
    public class MyDictionary : GenericDictionary<string, UnityEngine.Object, Entry> {
    }

    // And now we will see the dictionary in editor
    public MyDictionary d;

    void Start() {

        d.Add("test1", null);
        d.Add("test2", null);

        Debug.Log(d.ContainsKey("test1"));
        Debug.Log(d.ContainsKey("test2"));
        Debug.Log(d.ContainsKey("test3"));
    }
}

0
投票

虽然有点晚,但我刚刚在Unity资源商店找到了这个免费资产。它是在4.7版本的时候添加的。这是SerializableDictionary。 Unity的可序列化字典类。

从商店:

Unity无法序列化本机词典。在脚本中使用此类可以在检查器中显示字典。

  • 源自词典
  • 使用任何可序列化类型作为键或值
  • 无需创建自定义检查器

我的计划是看它是否仍然有效。我的想法是,它为我们提供了建立的起点。

© www.soinside.com 2019 - 2024. All rights reserved.